使用较新版本的 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.51
和 bcprov-jdk15on:1.51
,以及 java 邮件,并遵循 bcmail
包中的示例。但是,我仍然收到来自 Cyclone 的错误消息integrity-check-failed
。
我相当肯定错误在于我的签名方式。当我禁用签名并仅使用加密时,它会正确处理。此外,我可以正确接收来自远程服务器的签名响应并验证签名,这就是我获取错误消息的方式(来自 MimeMultiPart 上的内容处置)。
证书由 openssl/self signed/etc 创建,存储在 pkcs12 文件中 制定了无限强度政策senderKey
是 BCRSAPrivateCrtKey
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来加密和解密数据