手动验证XML签名

8

我可以成功地进行手动参考验证(规范化每个引用元素--> SHA1 --> Base64 --> 检查它是否与DigestValue内容相同),但是我在验证SignatureValue时失败了。这是要规范化和哈希的SignedInfo:

<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
 <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod>
 <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></ds:SignatureMethod>
 <ds:Reference URI="#element-1-1291739860070-11803898">
  <ds:Transforms>
   <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform>
  </ds:Transforms>
  <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod>
  <ds:DigestValue>d2cIarD4atw3HFADamfO9YTKkKs=</ds:DigestValue>
 </ds:Reference>
 <ds:Reference URI="#timestamp">
  <ds:Transforms>
   <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform>
  </ds:Transforms>
  <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod>
  <ds:DigestValue>YR/fZlwJdw+KbyP24UYiyDv8/Dc=</ds:DigestValue>
 </ds:Reference>
</ds:SignedInfo>

在删除标签之间的所有空格(因此将整个元素放在单行上)后,我得到了这个Base64编码的sha1摘要:

6l26iBH7il/yrCQW6eEfv/VqAVo=

现在我希望在解密SignatureValue内容后找到相同的摘要,但我得到了一个不同且更长的值:

MCEwCQYFKw4DAhoFAAQU3M24VwKG02yUu6jlEH+u6R4N8Ig=

这是一些Java代码进行解密:

      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();    
  DocumentBuilder builder = dbf.newDocumentBuilder();  
  Document doc = builder.parse(new File(inputFilePath));
  NodeList nl = doc.getElementsByTagName("ds:SignatureValue");
  if (nl.getLength() == 0) {
     throw new Exception("Cannot find SignatureValue element");
   }
  String signature = "OZg96GMrGh0cEwbpHwv3KDhFtFcnzPxbwp9Xv0pgw8Mr9+NIjRlg/G1OyIZ3SdcOYqqzF4/TVLDi5VclwnjBAFl3SEdkyUbbjXVAGkSsxPQcC4un9UYcecESETlAgV8UrHV3zTrjAWQvDg/YBKveoH90FIhfAthslqeFu3h9U20=";
  X509Certificate cert = X509Certificate.getInstance(new FileInputStream(<a file path>));
  PublicKey pubkey = cert.getPublicKey();
  Cipher cipher = Cipher.getInstance("RSA","SunJCE");
  cipher.init(Cipher.DECRYPT_MODE, pubkey);
  byte[] decodedSignature = Base64Coder.decode(signature);
  cipher.update(decodedSignature);
  byte[] sha1 = cipher.doFinal();


  System.out.println(Base64Coder.encode(sha1));

让我困惑的是这两个摘要的大小不同,但是我需要从这两个计算中得到完全相同的值。有什么建议吗?谢谢。


“在删除标签之间的所有空格后”...这样对吗?从http://www.w3.org/TR/2001/REC-xml-c14n-20010315#Example-WhitespaceInContent看来,您可能删除了太多的空格。 - Laurence Gonsalves
谢谢你的答复。我理解你的观点,但是正如我在问题开头所说的那样,只有通过删除那些空格,我才能成功地对同一SOAP消息进行两个引用的验证(这不可能是偶然的),所以我必须认为它是正确的。 - Johnca
1
规范化远不止于移除空白。它处理命名空间前缀问题、属性顺序,以及通常改变物理文件的字节顺序(因此也会改变哈希摘要),但不会改变 XML 信息集(即 XML 的含义负载)。 - m0sa
当然。我使用org.apache.xml.security.c14n.Canonicalizer,但之后我需要删除空格以获得与xml中相同的摘要。 - Johnca
好的,我找到了为什么解密的摘要如此之大:它由前缀+实际摘要组成(http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/Overview.html)。实际摘要位于最后20个字节。现在我有两个大小相等的摘要,但它们仍然不同 :( - Johnca
2个回答

8
"

MCEwCQYFKw4DAhoFAAQU3M24VwKG02yUu6jlEH+u6R4N8Ig= 是一个DER编码的ASN.1结构的Base64编码: 一个 SEQUENCE 包含首先是一个AlgorithmIdentifier(它声明这是SHA-1, 因为SHA-1不需要参数,所以没有参数), 然后是一个包含实际20字节值的OCTET STRING. 十六进制表示的值为: dccdb8570286d36c94bba8e5107faee91e0df088

"
这个ASN.1结构是RSA签名机制的一部分。您正在使用RSA 解密 来访问该结构,这是非标准的。您实际上很幸运能得到任何东西,因为RSA加密RSA签名是两种不同的算法。它们恰好都使用相同类型的密钥对,并且“旧式”(又称“PKCS#1 v1.5”)签名和加密方案使用类似的填充技术(类似但并非完全相同;Java实现RSA时未在解密模式下使用签名填充时出错已经有点令人惊讶了)。
无论如何,6l26iBH7il/yrCQW6eEfv/VqAVo= 是一个20字节值的Base64编码,其十六进制表示为:ea5dba8811fb8a5ff2ac2416e9e11fbff56a015a。这是在删除标签之间所有空格后,对你上面展示的XML结构进行哈希处理得到的结果。删除所有空格并不是正确的规范化方法。实际上,据我所知,空格只会影响标签内部属性之间的空格,而外部空格必须保持不变(除了行末换行符 [LF / CR+LF])。
生成签名所使用的值(dccdb85...)可以通过使用您展示的XML对象并删除前导空格来获得。要清楚:将XML复制粘贴到文件中,然后删除每行开头的前导空格(0到3个空格)。确保所有行末都使用单个LF(0x0A字节),并删除最后一个LF(即</ds:SignedInfo>后面的那个)。生成的文件长度必须为930个字节,并且其SHA-1哈希值是期望的dccdb85...值。

非常感谢您清晰明了的回答!!!“去除前导空格”的想法来自哪里?无论如何,我想知道为什么org.apache.xml.security.c14n.Canonicalizer不会将它们移除,以及为什么我需要两个不同的XML表示形式来完成这两个任务:对于参考验证,我必须删除标签之间的所有空格,而对于签名验证,我必须按照您所写的方式进行操作。无论如何,这个XML消息是从部署了servlet的JBoss发送并由部署了Web服务的JBoss接收的。也许JBoss做出了非标准的工作? - Johnca
1
我在一个网页上看到了关于“移除前导空格”的内容,该网页试图解释规范化(我不记得是哪个网页了)。规范化会保持文本内容不变,包括空格(Apache规范化器在不删除它们方面是正确的)。由于空格很麻烦,因此_建议_最初构建XML时不要缩进(即没有前导空格)。我猜原始的XML没有前导空格,在某个时候添加了它们(在签名计算之后),作为“阅读帮助程序”(混淆了签名)。 - Thomas Pornin

0

看了你的特定XML令牌,我可以告诉你一些事情。

  • 你正在使用规范化方法独占 XML 规范化版本 1.0。这是确保生成正确数字摘要和签名的非常重要的因素。

  • 你在计算引用数字摘要和在生成签名之前规范化SignedInfo时都在使用相同的规范化方法。

独占 XML 规范化版本 1.0 的规范由 W3C 制定,并可在其各自的 W3C 建议 中找到。如果你正在手动计算值,请确保你完全符合规范,因为规范化是一件难以做好的事情,正确地执行非常重要,否则你的值将是不正确的。

我刚刚写了一篇详细的文章,描述了XML签名验证过程。该文章位于我的博客。它比我的答案更详细地描述了这个过程,因为XML签名有许多复杂性。它还包含了流行规范和RFC的链接。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接