В этот статье я расскажу, как подписать произвольное сообщение приватным ключом и сертификатом по алгоритму ГОСТ Р 34.11/34.10-2001 присоединённой (attached) подписью на языке Java.
Для проекта электронного документооборота мне потребовалось сделать подпись алгоритмом ГОСТ. Несмотря на то, что появился он давным-давно, к своему удивлению, я не смог найти в сети ни одного завершённого примера, который бы получал на вход сообщение, ключ и сертификат, а на выходе давал бы подписанное сообщение.
Все найденные примеры или использовали стороннее платное ПО КриптоПро, или не собирались с современными версиями Java, или подписанные сообщения потом не валидировались.
В общем, я потратил много времени, чтобы разобраться, и решил, что полный готовый пример кому-нибудь да пригодится.
Для подписи нам нужны сертификат и приватный ключ.
Мне их передали в формате pfx, из него составные части надо извлечь.
Я всё делал на windows и использовал сборку OpenSsl с поддержкой ГОСТ. Для других ОС, думаю, действия будут аналогичными. В OpenSsl с версии 1.1.0 встроенную поддержку ГОСТ убрали, её надо подключать замороченным способом, который у меня с ходу не взлетел. Поэтому я просто скачал старую версию 1.0.2.
В конфиг openssl.cfg нужно добавить строки:
Запускаем консоль и вводим команду (без неё у меня конфиг на находился):
Экспортируем ключ в формат pkcs12:
Переводим ключ в формат pkcs8:
Экспортируем сертификат:
При выполнении команд будет запрошен пароль от pfx, его, разумеется, надо знать.
Для подписывания на Java я использовал библиотеку BouncyCastle, она поддерживает ГОСТ.
У меня проект на Maven, я добавил в pom.xml зависимости:
Код метода подписывания:
Код метода чтения ключа из файла:
Код метода чтения сертификата из файла:
Ну и, наконец, пример подписи:
Полученный .dat файл успешно проходит проверку подписи, например, здесь.
Надеюсь, этот пример будет полезен. Если потребуется, перевести на другой язык, думаю, не составит труда.
Для проекта электронного документооборота мне потребовалось сделать подпись алгоритмом ГОСТ. Несмотря на то, что появился он давным-давно, к своему удивлению, я не смог найти в сети ни одного завершённого примера, который бы получал на вход сообщение, ключ и сертификат, а на выходе давал бы подписанное сообщение.
Все найденные примеры или использовали стороннее платное ПО КриптоПро, или не собирались с современными версиями Java, или подписанные сообщения потом не валидировались.
В общем, я потратил много времени, чтобы разобраться, и решил, что полный готовый пример кому-нибудь да пригодится.
Для подписи нам нужны сертификат и приватный ключ.
Мне их передали в формате pfx, из него составные части надо извлечь.
Я всё делал на windows и использовал сборку OpenSsl с поддержкой ГОСТ. Для других ОС, думаю, действия будут аналогичными. В OpenSsl с версии 1.1.0 встроенную поддержку ГОСТ убрали, её надо подключать замороченным способом, который у меня с ходу не взлетел. Поэтому я просто скачал старую версию 1.0.2.
В конфиг openssl.cfg нужно добавить строки:
openssl_conf = openssl_def
[openssl_def]
engines = engine_section
[engine_section]
gost = gost_section
[gost_section]
engine_id = gost
dynamic_path = gost.dll
default_algorithms = ALL
CRYPT_PARAMS = id-Gost28147-89-CryptoPro-A-ParamSet
Запускаем консоль и вводим команду (без неё у меня конфиг на находился):
set OPENSSL_CONF=C:\папка_с_openssl\bin\openssl.cfg
Экспортируем ключ в формат pkcs12:
openssl pkcs12 -in my.pfx -nocerts -nodes -out my.pem
Переводим ключ в формат pkcs8:
openSSL pkcs8 -in my.pem -topk8 -nocrypt -out key.pk8
Экспортируем сертификат:
openssl pkcs12 -in my.pfx -nokeys -out my.cer
При выполнении команд будет запрошен пароль от pfx, его, разумеется, надо знать.
Для подписывания на Java я использовал библиотеку BouncyCastle, она поддерживает ГОСТ.
У меня проект на Maven, я добавил в pom.xml зависимости:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.59</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.59</version>
</dependency>
Код метода подписывания:
public static byte[] signWithGost3410(byte[] data, X509Certificate certificate, byte[] encodedPrivateKey) throws Exception {
X509Certificate[] certificates = new X509Certificate[1];
certificates[0] = certificate;
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
KeyFactory keyFactory = KeyFactory.getInstance("ECGOST3410", "BC");
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
CMSTypedData msg = new CMSProcessableByteArray(data);
Store certStore = new JcaCertStore(Arrays.asList(certificates));
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner signer = new org.bouncycastle.operator.jcajce.JcaContentSignerBuilder("GOST3411withECGOST3410").setProvider("BC").build(privateKey);
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()).build(signer, (X509Certificate) certificates[0]));
gen.addCertificates(certStore);
CMSSignedData sigData = gen.generate(msg, true);
return sigData.getEncoded();
}
Код метода чтения ключа из файла:
public static byte[] readEncodedKeyFromPk8File(String filename) throws Exception {
byte[] content = Files.readAllBytes(Paths.get(filename));
ArrayList<String> lines = new ArrayList<>(Arrays.asList(new String(content).split("\n")));
lines.remove(0);
lines.remove(lines.size() -1);
String base64 = String.join("", lines);
byte[] encoded = Base64.getDecoder().decode(base64);
return encoded;
}
Код метода чтения сертификата из файла:
public static X509Certificate readX509CertificateFromCerFile(String filename) throws Exception {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
Certificate certificate = factory.generateCertificate(new FileInputStream(filename));
return (X509Certificate) certificate;
}
Ну и, наконец, пример подписи:
@Test
public void signTest() throws Exception{
Security.addProvider(new BouncyCastleProvider());
byte[] key = readEncodedKeyFromPk8File("key.pk8");
X509Certificate certificate = readX509CertificateFromCerFile("my.cer");
byte[] data = Files.readAllBytes(Paths.get("my.xml"));
byte[] signedData = signWithGost3410(data, certificate, key);
try(FileOutputStream stream = new FileOutputStream("signed.dat")){
stream.write(signedData);
}
}
Полученный .dat файл успешно проходит проверку подписи, например, здесь.
Надеюсь, этот пример будет полезен. Если потребуется, перевести на другой язык, думаю, не составит труда.
Комментарии (5)
Balyk
18.04.2018 09:15Очень интересует аналогичный вопрос, но проработанный на netcore 2. Нужен код, который мог бы работать на Windows и Linux.
LPDem Автор
18.04.2018 09:58У BouncyCastle есть пакет для net core, по идее, всё должно быть примерно так же:
www.nuget.org/packages/BouncyCastle.NetCore
malloydev
Насколько я помню, сейчас все начинают переходить на ГОСТ Р 34.10-2012, а этот алгоритм поддерживается только на новых версиях gost движка и ставится он только на OpenSSL 1.1.x. Так что в ближайшее время скорее всего вам придется все таки ставить «замороченным способом»
LPDem Автор
Будем решать проблемы по мере их появления )