使用较新版本的 Bouncy Castle 时,接收方无法验证 SMIME

Posted

技术标签:

【中文标题】使用较新版本的 Bouncy Castle 时,接收方无法验证 SMIME【英文标题】:SMIME can't be validated by receiver when using newer version of Bouncy Castle 【发布时间】:2014-10-23 03:08:54 【问题描述】:

我正在使用 BC 对 SMIME 消息进行加密和签名,以便与 AS2 一起使用。我们的代码适用于绝对古老版本的充气城堡bcmail-1.4:125。升级到任何更新的东西会导致消息的接收者(不是太古老的 Cyclone 服务器)无法验证消息。 (例如,maven 中最早的 v 也会导致这种情况。这些是没有 API 更改的版本(例如 1.38)。

自从我们使用 JDK 1.7(和 1.8)以来,我一直在尝试将其更新为更新版本的 BC、java-mail 等。我已将所有充气城堡升级为 bcmail-jdk15on:1.51bcprov-jdk15on:1.51 ,以及 java 邮件,并遵循 bcmail 包中的示例。但是,我仍然收到来自 Cyclone 的错误消息integrity-check-failed

我相当肯定错误在于我的签名方式。当我禁用签名并仅使用加密时,它会正确处理。此外,我可以正确接收来自远程服务器的签名响应并验证签名,这就是我获取错误消息的方式(来自 MimeMultiPart 上的内容处置)。

证书由 openssl/self signed/etc 创建,存储在 pkcs12 文件中 制定了无限强度政策 senderKeyBCRSAPrivateCrtKey senderCert org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject

失败:当前代码是这样的,使用bcmail-jdk15on:1.51 & etc

SMIMESignedGenerator gen = new SMIMESignedGenerator();
gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder()
           .setProvider("BC")
           .build("SHA1withRSA", senderKey, senderCert));
// gen.addCertificates(new JcaCertStore(list(senderCert))); old v. doesn't add certs
MimeMultipart smime = gen.generate(part); // MimeBodyPart passed in to function
MimeBodyPart tmpBody = new MimeBodyPart();
tmpBody.setContent(signedData);
tmpBody.setHeader("Content-Type", signedData.getContentType()

以前的工作代码看起来像这样并使用bcmail-1.4:1.25。升级到 1.3x 也会导致另一端在解密时失败(不管我在哪个 jdk 上运行,1.6 - 1.8)

MimeBodyPart body = new MimeBodyPart();
body.setDataHandler(new DataHandler(new ByteArrayDataSource(bytes[], contentType, null);));
SMIMESignedGenerator sGen = new SMIMESignedGenerator();
// SHA1 resolves to "1.3.14.3.2.26", FWIW
sGen.addSigner(senderKey, senderCert, getBouncyCastleAlgorithmId("SHA1"));
MimeMultipart signedData = sGen.generate(part, "BC");
// this is then encrypted & streamed, no issues there

通用设置代码

byte[] data = Files.readAllBytes(filePath);
MimeBodyPart part = new MimeBodyPart();
ByteArrayDataSource dataSource = new ByteArrayDataSource(data, "application/EDIFACT", null);
part.setDataHandler(new DataHandler(dataSource));
part.setHeader("Content-Transfer-Encoding", "8bit");
part.setHeader("Content-Type", "application/EDIFACT");

我感觉这与我添加(或操作)senderCert 的方式有关,senderCert 是本地应用程序的 X509。

更新

我通过删除证书使新代码更符合旧代码:

它不再在签名消息中包含证书。旧版本没有 整个 mime 多部分内容现在完全与以前的长度(1095 字节)相同 格式(标题等)现在完全相同 签名部分现在几乎相同。但是,有一部分似乎会根据时间(???)而变化,并且每次都会发生变化。我还无法让 openssl 验证此消息,不知道为什么。

这是示例输出,FWIW。 [] 中的文本是唯一更改的部分。

------=_Part_1_1448572667.1409621469842
Content-Type: application/EDIFACT
Content-Transfer-Encoding: 8bit

this is a test

------=_Part_1_1448572667.1409621469842
Content-Type: application/pkcs7-signature; name=smime.p7s; smime-type=signed-data
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime.p7s"
Content-Description: S/MIME Cryptographic Signature

MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAMYIBpDCCAaAC
AQEwgZ4wgZAxCzAJBgNVBAYTAmNuMREwDwYDVQQIDAhzaGFuZ2hhaTESMBAGA1UEBwwJY2hhbmdu
aW5nMREwDwYDVQQKDAhwb3dlcmUyZTEOMAwGA1UECwwFaXRkZXYxEjAQBgNVBAMMCWFiLWNsaWVu
dDEjMCEGCSqGSIb3DQEJARYUYWItY2xpZW50QG15Q29ycC5jb20CCQClDAGwq37A/jAJBgUrDgMC
GgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTQwOTAyMDEz
M[TA5]WjAjBgkqhkiG9w0BCQQxFgQUG6KkoqPBvE7Kd9dB0eop/aUTya0wDQYJKoZIhvcNAQEBBQAE
gYB[h9N4maow9aoTQ8QBGgXEYE+xgXSmRPy+ufIsMpuS0Yys/1t3AfXSSI7WKgLMRKYXve8gdb4Gn
dqecHzkBwBq4hebt9YK+E30E6DpZpCwErsgDVaU/ExBA5gauPWneysy+s2bE5Y6pNZ7Qf3kGU5kI
UjlOF/LUNkCsgT5z//]5N6QAAAAAAAA==
------=_Part_1_1448572667.1409621469842--

【问题讨论】:

【参考方案1】:

经过大量调试和文件转储等,我证明它与摘要计算有关。在签名正文部分的特定位置是内容 MIC(摘要的 base64)。不知何故,这个值与对方所做的不匹配......

一旦我有了这个,并在谷歌上呆了一段时间,我终于找到了more information on sourceforge 确认了这一点。很有帮助,因为它提到了我的特定版本的 BC。引用:

问题在于 BC >= 1.27 将“规范化”所有 不与内容传输编码二进制一起发送。

这是什么意思?

在 S/MIME rfc 中它说所有消息都应该转换为 计算 MIC 之前的“规范”形式。 “规范”形式 短信是EOL用CR、LF表示。 rfc 是 对其他内容类型的规范形式保持沉默。许多 S/MIME 实现(例如 openssl、1.27 之后的 Bouncy Castle) 错误地假设所有消息的规范形式除了 那些用内容传输编码二进制发送的是每个 LF 前面应该有一个 CR。

因此,如果使用 BC 1.25 发送包含裸 LF 字符的消息,那么 如果消息被接收到,MIC 验证将失败 使用 BC >= 1.27 或 openssl smime 或许多其他 S/MIME 的应用程序 实现。

OpenAS2 应该被修复为使用内容传输编码二进制文件。

只有在 MIME 正文部分上设置编码时,这似乎才有效。我通过执行 JCA 路由验证了相同的结果(在服务器上失败)手动,轻量级路由,还有 CMS 路由。

根据这些信息,我对发件人进行了简单的更改......

MimeBodyPart part = //.. make mime body part from file
part.setHeader("Content-Transfer-Encoding", "binary");

有趣的是,更改与 SMIMESignedGenerator() 相关的任何内容似乎都没有效果:

gen = SMIMESignedGenerator("binary");  // nothing, even though the docs say to set this

我的最终签名函数如下所示,任何有兴趣的人:

SMIMESignedGenerator gen = new SMIMESignedGenerator();
SignerInfoGenerator sigGen = new JcaSimpleSignerInfoGeneratorBuilder()
        .setProvider(BC)
        .build("SHA1withRSA", senderKey, senderCert);
gen.addSignerInfoGenerator(sigGen);
MimeMultipart smime = gen.generate(part);
MimeBodyPart tmpBody = new MimeBodyPart();
tmpBody.setContent(smime);
tmpBody.setHeader("Content-Type", smime.getContentType());
return tmpBody;

原始文件只有一行:

this is a test

得到签名的输入是这样的:

Content-Type: application/EDIFACT
Content-Transfer-Encoding: binary

this is a test

调试信息:

data bytes:
436F6E74656E742D547970653A206170706C69636174696F6E2F454449464143540D0
A436F6E74656E742D5472616E736665722D456E636F64696E673A2062696E6172790D
0A0D0A74686973206973206120746573740A

digest mic: 
   "algorithmId":   "1.3.14.3.2.26"
   "digest bytes":  "CEC2C6614A481DFDF45C801FD6F2A51BC53D3FDF"
   "digest base64": "zsLGYUpIHf30XIAf1vKlG8U9P98="

不是这不附加签名,或添加任何功能,并使用 v1 x509 证书。既然一切都恢复正常了,我可能会更改这些内容。

我真的希望这一切都更加透明...... BC 内部是间接间接,虽然我明白为什么。它在内部仍然比旧版本更好。我不能说我没有找到很多示例,但 BC 测试用例似乎不是最好的(例如,我找不到一个在 expected SMIME'ing。也许我错过了)

【讨论】:

我为此苦苦挣扎了一个多星期,然后您的回答为我指明了正确的方向:我的电子邮件模板的文本正文部分仅使用 LF 格式化,这导致 Outlook 上的签名验证失败而我构建代码的更简单的测试用例(单行文本)运行良好 太棒了,很高兴这对您有所帮助。这对调试来说是一场噩梦,我可以想象使用 outlook 一定会更糟! :D 在 Thunderbird 中,每个签名都是无效的,我必须将算法更改为 SHA1withRSA 和内容标头。非常感谢

以上是关于使用较新版本的 Bouncy Castle 时,接收方无法验证 SMIME的主要内容,如果未能解决你的问题,请参考以下文章

使用 Bouncy Castle 库会导致输出 .jar 文件大小大幅增加

如何使用Bouncy Castle Crypto API来加密和解密数据

在 C# 中使用 Bouncy Castle 加密/解密

从 Bouncy Castle 中的文本创建 RSA 公钥的问题

Bouncy Castle Java PGP加解密

markdown CSS 3D Bouncy Castle