На Хабре есть великолепная статья "Побег из Крипто Про. Режиссерская версия, СМЭВ-edition", но наступил 2019 год и все УЦ стали выдавать ЭЦП по ГОСТ 34.10-2012 вместо ГОСТ 34.10-2001.
Под катом рассказ как можно модифицировать свой софт на Bouncy Castle для поддержки работы с ключами по новым гостам.

О юридических тонкостях подписывания документов через Bouncy Castle и прочих СКЗИ я ничего не знаю и общаться не готов. Перед использованием кода в продакшене проконсультируйтесь с юристом.
А зачем это вообще надо? Хорошо написанно оригинальной статье. Повторяться не буду.

Все известные мне УЦ выдают ключи с сертификатами на подобных токенах. На токен записан контейнер КриптоПро с приватным ключом и сертификатом. При экспорте ключа через CryptoPro CSP он экспортируется в особый «КриптоПро pfx» не совместимый ни с чем.
Просьбы выдать ключ в стандартном pfx или любом другом типовом контейнере УЦ игнорируют.
Если кто знает УЦ выдающие подписи в стандартных контейнерах поделитесь координатами в комментариях. Хороших людей не стыдно попиарить.
Для преобразования контейнера КриптоПро в стандартный pfx мы как и в оригинальной статье будем использовать P12FromGostCSP. Старые взломанные версии не работают с ключами по 2012 Госту. Идем на сайт авторов и покупаем новую.
Итак мы получили стандартный pfx с ключом и сертификатом.
Обновление Bouncy Castle
Обновляем Bouncy Castle до 1.60 Старые версии могут не поддерживать алгоритмы ГОСТ 2012.
Инициализация Bouncy Castle
Разбор pfx
Алиасы обязательно изменяем. Утилита P12FromGostCSP задает всегда один и тот же алиас «csp_exported» и при обработке уже второго ключа будут проблемы.
Для удобства работы ключ из pfx необходимо загрузить в стандартный Java KeyStore и дальше работать только с ним.
Загрузка KeyStore
Сохранение ключа с сертификатом в KeyStore
Загрузка ключей и сертификатов из KeyStore
Подпись файла
Есть вариант JcaContentSignerBuilder(«GOST3411WITHECGOST3410-2012-512») для 512 битовых ключей. Мои УЦ выдают 256 битные ничего не спрашивая и игнорируют уточняющие вопросы.
Проверка подписи
Проверка подписи полностью аналогична проверке по Госту 2001 года. Можно ничего не менять.
В результате всех вышеописанных действий мы получили относительно легкий способ избавиться от тяжкой ноши Крипто Про в 2019 году.
Под катом рассказ как можно модифицировать свой софт на Bouncy Castle для поддержки работы с ключами по новым гостам.

Disclaimer
О юридических тонкостях подписывания документов через Bouncy Castle и прочих СКЗИ я ничего не знаю и общаться не готов. Перед использованием кода в продакшене проконсультируйтесь с юристом.
А зачем это вообще надо? Хорошо написанно оригинальной статье. Повторяться не буду.
Получение ключа с Токена

Все известные мне УЦ выдают ключи с сертификатами на подобных токенах. На токен записан контейнер КриптоПро с приватным ключом и сертификатом. При экспорте ключа через CryptoPro CSP он экспортируется в особый «КриптоПро pfx» не совместимый ни с чем.
Просьбы выдать ключ в стандартном pfx или любом другом типовом контейнере УЦ игнорируют.
Если кто знает УЦ выдающие подписи в стандартных контейнерах поделитесь координатами в комментариях. Хороших людей не стыдно попиарить.
Для преобразования контейнера КриптоПро в стандартный pfx мы как и в оригинальной статье будем использовать P12FromGostCSP. Старые взломанные версии не работают с ключами по 2012 Госту. Идем на сайт авторов и покупаем новую.
Итак мы получили стандартный pfx с ключом и сертификатом.
Обновление Bouncy Castle
Обновляем Bouncy Castle до 1.60 Старые версии могут не поддерживать алгоритмы ГОСТ 2012.
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.60</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk15on</artifactId> <version>1.60</version> </dependency>
Инициализация Bouncy Castle
static { BouncyCastleProvider bcProvider = new BouncyCastleProvider(); String name = bcProvider.getName(); Security.removeProvider(name); // remove old instance Security.addProvider(bcProvider); }
Разбор pfx
KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC"); ByteArrayInputStream baos = new ByteArrayInputStream(pfxFileContent); keyStore.load(baos, password.toCharArray()); Enumeration<String> aliases = keyStore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); if (keyStore.isKeyEntry(alias )) { Key key = keyStore.getKey(alias , keyPassword.toCharArray()); java.security.cert.Certificate certificate = keyStore.getCertificate(alias ); addKeyAndCertificateToStore((PrivateKey)key, (X509Certificate)certificate); } }
Алиасы обязательно изменяем. Утилита P12FromGostCSP задает всегда один и тот же алиас «csp_exported» и при обработке уже второго ключа будут проблемы.
Для удобства работы ключ из pfx необходимо загрузить в стандартный Java KeyStore и дальше работать только с ним.
Загрузка KeyStore
FileInputStream is = new FileInputStream(keystorePath); keystore = KeyStore.getInstance(KeyStore.getDefaultType()); char[] passwd = keystorePassword.toCharArray(); keystore.load(is, passwd);
Сохранение ключа с сертификатом в KeyStore
public void addKeyAndCertificateToStore(PrivateKey key, X509Certificate certificate) { synchronized (this) { keystore.setKeyEntry(alias.toLowerCase(), key, keyPassword.toCharArray(), new X509Certificate[] {certificate}); FileOutputStream out = new FileOutputStream(keystorePath); keystore.store(out, keystorePassword.toCharArray()); out.close(); } }
Загрузка ключей и сертификатов из KeyStore
Enumeration<String> aliases = keystore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); if (keystore.isKeyEntry(alias)) { Key key = keystore.getKey(alias, keyPassword.toCharArray()); keys.put(alias.toLowerCase(), key); //any key,value collection Certificate certificate = keystore.getCertificate(alias); if (certificate instanceof X509Certificate) certificates.put(alias.toLowerCase(), (X509Certificate) certificate); //any key,value collection } }
Подпись файла
CMSProcessableByteArray msg = new CMSProcessableByteArray(dataToSign); List certList = new ArrayList(); certList.add(cert); Store certs = new JcaCertStore(certList); CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); ContentSigner signer = new org.bouncycastle.operator.jcajce.JcaContentSignerBuilder("GOST3411WITHECGOST3410-2012-256").setProvider("BC").build(privateKey); gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()).build(signer, certificate)); gen.addCertificates(certs); CMSSignedData sigData = gen.generate(msg, false); byte[] sign = sigData.getEncoded(); //result here
Есть вариант JcaContentSignerBuilder(«GOST3411WITHECGOST3410-2012-512») для 512 битовых ключей. Мои УЦ выдают 256 битные ничего не спрашивая и игнорируют уточняющие вопросы.
Проверка подписи
byte[] data = ...; //signed file data byte[] signature = ...;//signature boolean checkResult = false; CMSProcessable signedContent = new CMSProcessableByteArray(data); CMSSignedData signedData; try { signedData = new CMSSignedData(signedContent, signature); } catch (CMSException e) { return SIGNATURE_STATUS.ERROR; } SignerInformation signer; try { Store<X509CertificateHolder> certStoreInSing = signedData.getCertificates(); signer = signedData.getSignerInfos().getSigners().iterator().next(); Collection certCollection = certStoreInSing.getMatches(signer.getSID()); Iterator certIt = certCollection.iterator(); X509CertificateHolder certHolder = (X509CertificateHolder) certIt.next(); X509Certificate certificate = new JcaX509CertificateConverter().getCertificate(certHolder); checkResult = signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(certificate)); } catch (Exception ex) { return SIGNATURE_STATUS.ERROR; }
Проверка подписи полностью аналогична проверке по Госту 2001 года. Можно ничего не менять.
Резюме
В результате всех вышеописанных действий мы получили относительно легкий способ избавиться от тяжкой ноши Крипто Про в 2019 году.