Manual verification of XML Signature

2019-03-16 01:10发布

I can successfully do manual reference validation (canonicalize every referenced element --> SHA1 --> Base64 --> check if it's the same of DigestValue content) but I fail with the verification of the SignatureValue. Here's the SignedInfo to canonicalize and hash:

<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>

Ater removing all the spaces between tags (and so getting the whole element on a single line), I obtain this sha1 digest (in Base64):

6l26iBH7il/yrCQW6eEfv/VqAVo=

Now I expect to find the same digest after the decryption of the SignatureValue content, but I get a differente and longer value:

MCEwCQYFKw4DAhoFAAQU3M24VwKG02yUu6jlEH+u6R4N8Ig=

Here's some java code for the decyption:

      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));

The thing that confuses me much is that the two digests have different size, but of course I also need to obtain exactly the same value from the two calculations. Any suggestions? Thank you.

2条回答
够拽才男人
2楼-- · 2019-03-16 01:23

MCEwCQYFKw4DAhoFAAQU3M24VwKG02yUu6jlEH+u6R4N8Ig= is Base64 encoding for a DER-encoded ASN.1 structure: a SEQUENCE containing first an AlgorithmIdentifier (which states that this is SHA-1, with no parameters since SHA-1 accepts none), then an OCTET STRING which contains the actual 20-byte value. In hexadecimal, the value is: dccdb8570286d36c94bba8e5107faee91e0df088.

This ASN.1 structure is part of the standard RSA signature mechanism. You are using RSA decryption to access that structure, which is non-standard. You are actually lucky to get anything at all, since RSA encryption and RSA signature are two distinct algorithms. It so happens that they both feed on the same kind of key pairs, and that the "old-style" (aka "PKCS#1 v1.5") signature and encryption schemes use similar padding techniques (similar but not identical; it is already a bit surprising that the Java implementation of RSA did not choke on the signature padding when used in decryption mode).

Anyway, 6l26iBH7il/yrCQW6eEfv/VqAVo= is Base64 encoding for a 20-byte value, which, in hexadecimal, is: ea5dba8811fb8a5ff2ac2416e9e11fbff56a015a. This is what you get by hashing the XML structure you show above, after having removed all whitespace between tags. Removing all whitespace is not proper canonicalization. Actually, as far as I know, whitespace is affected only between attributes, within the tags, but external whitespace must be kept unchanged (except for line ending normalization [the LF / CR+LF thing]).

The value which was used for the signature generation (the dccdb85...) can be obtained by using the XML object you show and by removing the leading spaces. To be clear: you copy+paste the XML into a file, then remove the leading spaces (0 to 3 spaces) on each line. You make sure that all end-of-lines use a single LF (0x0A byte) and you remove the final LF (the one just after </ds:SignedInfo>). The resulting file must have length 930 bytes, and its SHA-1 hash is the expected dccdb85... value.

查看更多
Bombasti
3楼-- · 2019-03-16 01:35

Looking at your particular XML token, I can tell you a few things.

  • You are using the Canonicalization method Exclusive XML Canonicalization Version 1.0. This is a very IMPORTANT factor in ensuring that you produce the right digest values and signature.

  • You are using the same Canonicalization method both for computing the reference digests, and for canonicalizing the SignedInfo before producing the signature.

The specification for Exclusive XML Canonicalizaiton Version 1.0 is produced by W3C and can be found at its respective W3C Recommendation. If you are computing your values manually, be sure that you are conforming exactly to the specification, because canonicalization is a hard thing to get right, and is very important to do this correctly, otherwise your values will be incorrect.

I just wrote an extensive article describing the XML Signature validation process. The article is located at my blog. It describes the process in much more detail than my answer, as there are many intricacies to XML Signature. It also contains links to prevalent specifications and RFCs.

查看更多
登录 后发表回答