Search code examples
python-3.xlxmlxml-signaturepython-cryptography

Python xmlsec XML Signature Value mismatch


I am new to xml signatures and currently I am using xmlsec to generate a signed xml. I do this with some modifications on the sample code:

from lxml import etree
import xmlsec

parser = etree.XMLParser(remove_blank_text=True)
template = etree.parse('unsigned.xml', parser).getroot()

signature_node = xmlsec.tree.find_node(template, xmlsec.constants.NodeSignature)
ctx = xmlsec.SignatureContext()
key = xmlsec.Key.from_file('keys/private_key.pem', xmlsec.constants.KeyDataFormatPem)
ctx.key = key
sig_ = ctx.sign(signature_node)
formated = etree.tostring(template)
with open('signed_test.xml', 'wb') as the_file:
    the_file.write(formated)

Now I have signed XML, and from here I am trying to learn where or how the values get generated. I am following this for context. I am able to verify the DigestValue and now I am trying to get the SignatureValue. From the link and some other questions here in stackoverflow, I just need to:

  1. Canonized the whole SignedInfo element
  2. Hash the result
  3. Sign the Hash

In order to get the signature value. I am canonizing the SignedInfo Element using lxml:

from lxml import etree

parser = etree.XMLParser(remove_blank_text=True)
xmlTree = etree.parse('signed_info.xml', parser)
root = xmlTree.getroot()
formated = etree.tostring(root, method='c14n', exclusive=True)
# Write to file
with open('canon_sinfo.xml', 'wb') as the_file:
    the_file.write(formated)

For info the following is the resulting SignedInfo:

<SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"></SignatureMethod><Reference URI="#obj"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#base64"></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod><DigestValue>izbIdQ4tSAg6VKGpr1zd6kU9QpVQi/Bcwxjxu/k2oKk=</DigestValue></Reference></SignedInfo>

I am using cryptography to try and generate the SignatureValue however, I cannot get the same result as that of what xmlsec generated.

Here is my code snippet in doing so:

sign_info = '''String of the Sign Info'''
digest_sinfo = hashes.Hash(hashes.SHA256(), backend=default_backend())
digest_sinfo.update(bytes(sign_info, 'utf-8'))
digested_sinfo = digest_sinfo.finalize()

# Load private_key here...

# Sign the message
signature_1v15 = private_key.sign(
    digested_sinfo,
    padding.PKCS1v15(),
    hashes.SHA256()
)

I also tried loading the SignedInfo into a seperate file, however I still get a mismatched SignatureValue. How do I accomplish this?


Solution

  • Your issue is that you do the hashing twice. The sign() function does the hashing for you so you can skip the middle part.

    Just call the sign() with your canonized SignedInfo element:

    signature_1v15 = private_key.sign(
        sign_info,
        padding.PKCS1v15(),
        hashes.SHA256()
    )