从Crypto Pro逃脱。 GOST 34.10-2012版

在哈布雷(Habré)上有一篇宏伟的文章“ 从Crypto Pro逃脱。导演的版本,SMEV版本 ”,但是到了2019年,所有CA都开始按照GOST 34.10-2012而不是GOST 34.10-2001发行数字签名。

在剪辑中,讲述了如何在Bouncy Castle上修改软件以支持为新访客使用密钥的故事。

图片

免责声明


我对通过Bouncy Castle和其他密码信息保护系统签署文档的法律复杂性一无所知,因此我不准备交流。 在生产中使用该代码之前,请咨询律师。

为什么还要这样做? 写得很好的原始文章。 我不会重复自己。

从令牌获取密钥


图片

我所知道的所有CA都使用相似令牌的证书颁发密钥。 带有私钥和证书的cryptoPro容器写在令牌上。 通过CryptoPro CSP导出密钥时,会将其导出到与任何东西都不兼容的特殊“ CryptoPro pfx”。

在标准pfx或任何其他典型CA容器中发出密钥的请求将被忽略。
如果有人知道标准容器中的CA签发签名,请在注释中共享坐标。 好人不以促进为耻。

要将CryptoPro容器转换为标准pfx,如原始文章中所述,我们将使用P12FromGostCSP。 旧的被黑版本无法与2012 Gost的密钥一起使用。 我们转到作者网站并购买一个新网站

因此,我们获得了带有密钥和证书的标准pfx。

充气城堡更新

我们将Bouncy Castle更新为1.60。旧版本可能不支持GOST 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中,然后才可以使用它。

下载密钥库

 FileInputStream is = new FileInputStream(keystorePath); keystore = KeyStore.getInstance(KeyStore.getDefaultType()); char[] passwd = keystorePassword.toCharArray(); keystore.load(is, passwd); 

在证书库中保存带有证书的证书

 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 

对于512位密钥,有一个JcaContentSignerBuilder选项(“ GOST3411WITHECGOST3410-2012-512”)。 我的CA不问任何问题就发出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 GOST验证完全相似。 您无法更改任何内容。

总结


由于上述所有措施,我们有了相对简单的方法来摆脱2019年Crypto Pro的沉重负担。

Source: https://habr.com/ru/post/zh-CN440882/


All Articles