You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Python加密实现与xmlsec1生成的XML签名值不一致?

问题:XML签名时Python实现与xmlsec1生成的SignatureValue不一致

我正在对接一个基于XML的API,使用xmlsec1工具可成功完成XML请求签名,命令如下:

xmlsec1 --sign --lax-key-search --privkey-pem test_privkey.pem,test_cert.pem --output xml/signed.xml xml/template.xml

对应的template.xml内容为:

<root><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></CanonicalizationMethod><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"></SignatureMethod><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></Transform><Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod><DigestValue></DigestValue></Reference></SignedInfo><SignatureValue/></Signature></root>

但改用Python代码实现签名时,生成的SignatureValue与xmlsec1版本不同(DigestValue一致),提交至API后验证失败。尝试使用signxml库也得到同样结果。调试用的XML_FILENAME内容为<root></root>,对应的Python代码如下:

# Load the private key
with open(KEY_PATH, "rb") as key_file:
    private_key = load_pem_private_key(key_file.read(), None)

# Load the XML file
tree = etree.parse(f"xml/{XML_FILENAME}")
root = tree.getroot()

# Create a Signature element
signature_element = etree.Element("Signature")
signature_element.attrib["xmlns"] = "http://www.w3.org/2000/09/xmldsig#"

# Create a SignedInfo element
signed_info_element = etree.SubElement(signature_element, "SignedInfo")

# Create a CanonicalizationMethod element
canonicalization_method_element = etree.SubElement(signed_info_element, "CanonicalizationMethod")
canonicalization_method_element.attrib["Algorithm"] = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"

# Create a SignatureMethod element
signature_method_element = etree.SubElement(signed_info_element, "SignatureMethod")
signature_method_element.attrib["Algorithm"] = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"

# Create a Reference element
reference_element = etree.SubElement(signed_info_element, "Reference")
reference_element.attrib["URI"] = ""

# Create Transforms
transforms_element = etree.SubElement(reference_element, "Transforms")
transform_element1 = etree.SubElement(transforms_element, "Transform")
transform_element1.attrib["Algorithm"] = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
transform_element2 = etree.SubElement(transforms_element, "Transform")
transform_element2.attrib["Algorithm"] = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"

# Create DigestMethod
digest_method_element = etree.SubElement(reference_element, "DigestMethod")
digest_method_element.attrib["Algorithm"] = "http://www.w3.org/2001/04/xmlenc#sha256"

# Compute the digest value for the entire XML document and add it to the DigestValue element
digest = hashlib.sha256(etree.tostring(root, method="c14n")).digest()
digest_value_element = etree.SubElement(reference_element, "DigestValue")
digest_value_element.text = b64encode(digest).decode()

# Canonicalize the SignedInfo XML
c14n_signed_info = etree.tostring(signed_info_element, method="c14n")

# Create a SHA256 digest of the SignedInfo
digest = hashlib.sha256(c14n_signed_info).digest()

# Sign the digest
signature = private_key.sign(
    digest,
    padding.PKCS1v15(),
    hashes.SHA256()
)

# Embed the SignatureValue in the Signature
signature_value_element = etree.SubElement(signature_element, "SignatureValue")
signature_value_element.text = b64encode(signature).decode()

# Add the Signature element to the root of the document
root.append(signature_element)

# Save the signed XML
tree = etree.ElementTree(root)
with open('xml/signed.xml', 'wb') as f:
    tree.write(f)

原因分析与解决方法

核心原因:SignedInfo处理逻辑的两处关键差异

  1. 双重哈希错误:你先对规范化后的SignedInfo做SHA256哈希,再调用私钥签名时又指定了hashes.SHA256()参数,这相当于执行了双重哈希。而xmlsec1会直接对规范化后的SignedInfo做一次SHA256哈希再签名,两者的签名输入完全不同,自然会得到不同的SignatureValue。
  2. 手动构建XML的细节差异:手动创建SignedInfo元素时,命名空间绑定顺序、空白符处理可能和xmlsec1的规范处理不一致,导致规范化后的SignedInfo字节流存在细微差别(虽然DigestValue一致,但SignedInfo的规范化结果是签名的核心输入)。

解决步骤

1. 修复双重哈希问题

修改签名逻辑,直接传入规范化后的SignedInfo字节流,不再提前手动哈希:

# 移除手动哈希步骤
# digest = hashlib.sha256(c14n_signed_info).digest()

# 直接对规范化后的SignedInfo签名
signature = private_key.sign(
    c14n_signed_info,  # 传入原始规范化字节流
    padding.PKCS1v15(),
    hashes.SHA256()
)

2. 对齐SignedInfo的规范化逻辑

手动构建XML容易出现细节偏差,建议使用signxml库并严格对齐xmlsec1的参数配置:

from signxml import XMLSigner
from lxml import etree

# 加载私钥和证书
with open("test_privkey.pem", "rb") as f:
    private_key = f.read()
with open("test_cert.pem", "rb") as f:
    cert = f.read()

# 加载原始XML模板
tree = etree.parse("xml/template.xml")
root = tree.getroot()

# 完全对齐xmlsec1的签名配置
signer = XMLSigner(
    method="enveloped",
    signature_algorithm="rsa-sha256",
    digest_algorithm="sha256",
    canonicalization_method="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
)

# 执行签名
signed_root = signer.sign(root, key=private_key, cert=cert)

# 保存签名后的XML
etree.ElementTree(signed_root).write(
    "xml/signed_python.xml", 
    encoding="utf-8", 
    xml_declaration=True,
    method="c14n"  # 确保保存时使用规范格式
)

3. 验证规范化字节流(排查细节差异)

如果仍存在差异,可以分别提取xmlsec1和Python生成的SignedInfo部分,用以下方式对比:

  • 对xmlsec1生成的SignedInfo执行:xmlsec1 --c14n signed.xml | xxd
  • 对Python生成的SignedInfo执行:etree.tostring(signed_info_element, method="c14n") | xxd
    对比输出的十六进制内容,确认是否存在命名空间顺序、空白符等细微差异。

内容的提问来源于stack exchange,提问作者Bafsky

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 23:57:05