jarsigner默认签名算法不一致及Windows Crypto API适配问题咨询
问题背景
jarsigner默认签名算法已更新为更安全的SHA256withDSA,原SHA1withDSA因不安全被禁用。使用jarsigner -verify -verbose -certs xyz.jar验证新签名的JAR包时,结果符合预期:
Digest algorithm: SHA-256 Signature algorithm: SHA256withDSA, 1024-bit key (weak)
但使用OpenSSL验证同一JAR包的签名文件LOCALSIG.DSA时,却显示签名算法为SHA1withDSA:
执行openssl cms -cmsout -inform DER -print -in LOCALSIG.DSA输出:
digestAlgorithms: algorithm: sha256 (2.16.840.1.101.3.4.2.1) signature: algorithm: dsaWithSHA1 (1.2.840.10040.4.3) sig_alg: algorithm: dsaWithSHA1 (1.2.840.10040.4.3)
执行openssl pkcs7 -inform der -print_certs -text -in LOCALSIG.DSA输出:
Signature Algorithm: dsaWithSHA1
问题1
为何两个工具报告的签名算法不一致?此前二者结果一致,是否jarsigner生成了模糊格式?
问题2
基于Windows加密API开发的自定义JAR验证器,此前运行正常,但处理该新JAR包时,调用CryptVerifyDetachedMessageSignature()返回未文档化错误50 = ERROR_NOT_SUPPORTED,推测因格式问题导致。该如何处理?是在代码中适配异常签名,还是从源头上修复JAR包?
当前临时解决方案:显式指定jarsigner -sigalg SHA1withDSA,使用的是基于OpenJDK的SAPMachine jarsigner,而非Oracle官方版本。
问题解答
问题1:工具报告不一致的原因
这并非jarsigner生成了模糊格式,而是JAR签名的PKCS#7/CMS结构存在两个独立的算法标识:
- 一个是用于计算文件内容摘要的算法(对应OpenSSL输出的
digestAlgorithms,此处为SHA-256) - 另一个是用于对摘要进行签名的算法标识(对应OpenSSL输出的
signature/sig_alg,此处显示为dsaWithSHA1)
jarsigner采用Java体系的命名逻辑,将“摘要算法+签名算法”合并显示为SHA256withDSA;而OpenSSL直接读取CMS结构中的签名算法OID——DSA的传统OID(1.2.840.10040.4.3)原本绑定SHA1,即使实际使用SHA256签名,部分工具仍会显示这个传统OID,导致两者报告出现差异。
问题2:Windows API验证失败的处理方案
代码层面适配
Windows CryptoAPI的CryptVerifyDetachedMessageSignature()对DSA+SHA256的组合支持存在局限性,尤其是旧版本Windows系统。可尝试手动拆分验证流程:
- 手动解析CMS签名结构,分离出原始待验证数据、签名值和证书
- 调用
CryptCreateHash()指定SHA256算法,计算原始数据的摘要 - 使用
CryptVerifySignature()直接验证DSA签名与计算出的摘要是否匹配,绕开API对CMS格式的自动解析限制
源头修复JAR包
若代码适配成本过高,可从签名环节优化:
- 若必须使用DSA密钥,更换为2048位及以上长度的DSA密钥(1024位已被标记为弱密钥),确保密钥支持SHA256摘要算法
- 优先更换签名算法为
SHA256withRSA,该组合在各工具和系统API中的兼容性更好 - 升级SAPMachine到最新版本,检查是否修复了CMS签名OID的写入逻辑问题
临时解决方案
当前可通过显式指定签名算法绕过问题:
jarsigner -sigalg SHA1withDSA <你的签名参数>
内容的提问来源于stack exchange,提问作者Simpleton

