java - CMS 签名 SHA1+RSA 与 PEM 格式 - Java

标签 java openssl cryptography

我需要通过 xml 文件输入生成具有 PE​​M 格式的 SHA1+RSA 分离签名的 CMS。我需要在运行时通过 Java 代码来实现这一点。我需要避免使用 OpenSSL 等外部工具。这是因为我们需要使用Java生成的签名来调用一些服务,并在签名过程中发生异常(输入的xml每天都在变化)时正确管理错误。

我必须签署的文件如下所示:

<header>    
 <generationTime>2017-04-17T00:00:01-03:00</generationTime>
 <expirationTime>2017-04-17T23:59:59-03:00</expirationTime>
</header>

使用 OpenSSL,使用私钥和证书,执行以下命令生成签名:

openssl cms -sign -in tra.xml -inkey MyPrivateKey -signer myCertificate.pem -out tra.xml.cms -outform PEM -nodetach

在这种情况下生成的 PEM 签名是:

-----BEGIN CMS-----
MIIGdAYJKoZIhvcNAQcCoIIGZTCCBmECAQExDTALBglghkgBZQMEAgEwgaIGCSqG
SIb3DQEHAaCBlASBkTxoZWFkZXI+ICAgIA0KPGdlbmVyYXRpb25UaW1lPjIwMTct
MDQtMTdUMDA6MDA6MDEtMDM6MDA8L2dlbmVyYXRpb25UaW1lPg0KPGV4cGlyYXRp
b25UaW1lPjIwMTctMDQtMTdUMjM6NTk6NTktMDM6MDA8L2V4cGlyYXRpb25UaW1l
Pg0KPC9oZWFkZXI+DQqgggNOMIIDSjCCAjKgAwIBAgIII0Or3JGYSY4wDQYJKoZI
hvcNAQENBQAwODEaMBgGA1UEAwwRQ29tcHV0YWRvcmVzIFRlc3QxDTALBgNVBAoM
BEFGSVAxCzAJBgNVBAYTAkFSMB4XDTE3MDEyNjE2MDIwNFoXDTE5MDEyNjE2MDIw
NFowMDETMBEGA1UEAwwKYWNjZXNvQUZJUDEZMBcGA1UEBRMQQ1VJVCAyMDI5OTUw
Mzk2OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPhtRXT8FQPvcFvQ
CUSZaHTtcc864DsvP3zedpcr1gDLyJRMMlKnV0mZVJXEeC6eo6AV71kv2QpFUUp3
OGUAS/zJGXByCJ2trV/pXrvppmJvAJARlfw6KoqQBY+YYoIinIzCbUHdvoPwub2K
o7081VlmLxUffiDElbAi3gi41z/W6pD57i3U1uPjS45HRvIn7Vcv4epcH3x9+IDC
DEbZ0hsKIiuJrH4RO1k50gSSaXjvAQSG8kbEXMQ89AxAeynI8jk964JpHc0qLj6y
1sfvAyCSPq8ZFURribdboZi8G6oAccIM1pyMKA13+AcPkOFy0SyotjnFgrK2MMVZ
+vEgNwECAwEAAaNgMF4wDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBSzstP//em6
3t6NrxEhnNYgffJPbzAdBgNVHQ4EFgQUEuAlczdaDyI7hGuwyqR6ipLvTikwDgYD
VR0PAQH/BAQDAgXgMA0GCSqGSIb3DQEBDQUAA4IBAQA+Pg0RQ7J5qiViZMk94tgD
WAgTT0iIoVm65Xn2/czlBgefhxY6l4SKqQCONJpAMCUI2mEG6qgOg/u+GbN3pR+p
1FSC2yDETRIpf9nekooTEot6A9r2Huykd4Sp3QHZEly9Sx3+3ek+w7Mg0k/+AtgT
JodP0ArzCQyvBJCR8ZTTHjUazf2/9o0iEqQIKyp1vn2vv3JlMONBb7+ALqzCXgCb
FVjFpF8PpZyWM/+J6WVrU19hB3wsdyhLh0M5CiBQ19aGC8R/0bWm2w2P3awOn8r4
r/duYqdGzK/7zTpjtvk0VKax6/Pe5WIFLKXTP9LGpxbCQjxKpbVxMbzx1pDPGBjF
MYICVDCCAlACAQEwRDA4MRowGAYDVQQDDBFDb21wdXRhZG9yZXMgVGVzdDENMAsG
A1UECgwEQUZJUDELMAkGA1UEBhMCQVICCCNDq9yRmEmOMAsGCWCGSAFlAwQCAaCB
5DAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xNzA0
MTcxMzM3MDVaMC8GCSqGSIb3DQEJBDEiBCBL5i3jl4+rfSfo/Pcu/CbI6JHGj0jg
UGI/EucH7LBM6jB5BgkqhkiG9w0BCQ8xbDBqMAsGCWCGSAFlAwQBKjALBglghkgB
ZQMEARYwCwYJYIZIAWUDBAECMAoGCCqGSIb3DQMHMA4GCCqGSIb3DQMCAgIAgDAN
BggqhkiG9w0DAgIBQDAHBgUrDgMCBzANBggqhkiG9w0DAgIBKDANBgkqhkiG9w0B
AQEFAASCAQAMNHskWrhZCu/DmFQLCrAweTEacCwTJdYOx+704PS6DkflXQLpD9q4
B0Psxx6gmN7HkHkrY4bD250TefZpKyD7IfJjdNQEz4SzgmtgMTl2a0JvlgpWSNjq
au0WMkFXnoSo0oJ3s4FSHWAe15DlNFQn9HbKfjI/sIHpkhgA0u/Kr6ZHUSIEnfxS
KVNxQ224uvFPGCggHnPIdtBRFgGn44J1hRyiYm0BLqJO5sAwV23gWB8OztsuBHqj
imi4WWXnCVPk7/6BMGNuLpUH3bH6nfIPDfSL7bb7vXRhcQrjTU8o38/C3gDsJr2A
4JNHkIjPMoo4l+wlS66MJQpOXadjYaFi
-----END CMS-----

这就是我需要通过 Java 7 生成的内容。

我已经阅读并使用 Bouncy CaSTLe 和 java.security.cert 标准 API 进行了多次测试,但我无法生成相同的签名结果。我检查了 Bouncy CaSTLe 的内部表示,API 使用 DER 格式来保存签名。并且在示例中始终显示如何验证签名,但没有显示如何生成签名并将其保存在文件中或以 PEM 形式打印。

这是我如何生成 BC 签名的示例,但结果与我需要的有很大不同:

public static String encryptSHA1RSA(String xmlPayload) throws Exception {
  List             certList = new ArrayList();
  CMSTypedData     msg = new CMSProcessableByteArray(xmlPayload.getBytes());
  certList.add(CMSEncryptor.getSingCert());
  Store certs = new JcaCertStore(certList);
  CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
  ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(CMSEncryptor.getPrivateKey());
  gen.addSignerInfoGenerator( new JcaSignerInfoGeneratorBuilder(
    new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())
    .build(sha1Signer, CMSEncryptor.getSingCert()));

  gen.addCertificates(certs);
  CMSSignedData sigData = gen.generate(msg, true);

  ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded());
  FileOutputStream fos = new FileOutputStream(getSecurityFolderPath() + "/tra.test.cms");
  DEROutputStream dos = new DEROutputStream(fos);
  dos.writeObject(asn1.readObject());

  dos.flush();
  dos.close();
  asn1.close();

  return Base64Util.encodeBase64(new String(sigData.getEncoded()));
}

如果我用 base64 编码结果也会不同:

return Base64Util.encodeBase64(new String(sigData.getEncoded()));

任何提示将不胜感激

最佳答案

我使用 BouncyCaSTLe (bcprov-jdk15on) 1.56 和 Java 1.7 进行了测试

要将您的签名转换为 PEM 格式,您可以使用 BouncyCaSTLe 的 JcaPEMWriter (或者只是 PEMWriter 对于旧版本),如下所示:

import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;

// ... used the same code you posted above ...

// *** NOTE: if you want a detached signature, change the second parameter to false ***
CMSSignedData sigData = gen.generate(msg, false);

// write sigData to output.pem file, using a pem writer
ContentInfo ci = ContentInfo.getInstance(ASN1Sequence.fromByteArray(sigData.getEncoded()));
JcaPEMWriter writer = new JcaPEMWriter(new FileWriter("output.pem"));
writer.writeObject(ci);
writer.close();

结果略有不同,因为 BouncyCaSTLe 生成的文件带有 BEGIN PKCS7END PKCS7 header (而不是 BEGIN CMS>结束 CMS):

-----BEGIN PKCS7-----
MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAaCA
JIAEgYg8aGVhZGVyPgo8Z2VuZXJhdGlvblRpbWU+MjAxNy0wNC0xN1QwMDowMDow

... lots of base64 lines ...

WHkpUQDxQj+v/SbMGa5+U7VC8+HNOfgFOba+U56QLhbhDEeaaozwATXveRkqhsdn
AAAAAAAA
-----END PKCS7-----

但输出文件无论如何都是有效的数字签名。 OpenSSL 和 BouncyCaSTLe 都可以读取两者(PKCS7 和 CMS header )。因此,除非您确实需要 BEGIN CMS header ,否则我相信这就足够了。


如果您不想写入文件并获取 String相反,您可以使用 java.io.StringWriterJcaPEMWriter 结合:

StringWriter sw = new StringWriter();
JcaPEMWriter writer = new JcaPEMWriter(sw);
writer.writeObject(ci);
writer.close();
String pemString = sw.toString(); // pemString will be the PEM formatted string (with BEGIN PKCS7 header)

关于java - CMS 签名 SHA1+RSA 与 PEM 格式 - Java,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43454473/

相关文章:

c - 我如何获取作为 C 中客户端问候的一部分出现的 session ID,SSL_SESSION_get_id 仅返回协商的 session ID

security - 服务器上的密文

security - 一种方式身份验证时间戳和随机数是什么意思?

go - Go中AES-GCM + Base64后无法解密

java - 在 pom.xml 中外部化属性的最佳方法是什么

java - 使用 deeplearning4j 在 Iris 数据集上表现糟糕

java - 如何使用 ServletScopes.scopeRequest() 和 ServletScopes.continueRequest()?

c++ - 从内存中加载 CA 证书

java - 模拟@AuthenticationPrincipal进行单元测试

python - 如何使用 Python 输出仅证书 PKCS#7