使用Python为Zatca哈希XML文件与SDK结果不一致问题
ZATCA UBL 2.1 XML哈希结果与SDK不一致问题
我按照ZATCA官方要求处理UBL 2.1格式的XML发票哈希,但自己写的Python代码输出和官方SDK结果不匹配。根据ZATCA文档,正确的哈希步骤为:
- 移除
<Invoice>下的<ext:UBLExtensions/>块 - 移除
<invoice>下cbc:ID值为QR的<cac:AdditionalDocumentReference/>块 - 移除
<invoice>下的<cac:Signature/>块 - 采用C14N11标准对处理后的Invoice做规范化
- 用SHA256对规范化后的字符串生成二进制哈希值
- 对二进制哈希值做Base64编码得到最终摘要
我的Python代码
f = open("invoice_00045.xml", "r") st = f.read() # Canonicalize xmldsig = chilkat2.XmlDSig() canonXml = xmldsig.CanonicalizeXml(st,"C14N",False) encoded_str = canonXml.encode() # create sha3-256 hash objects obj_sha3_256 = hashlib.sha256(encoded_str) hashed = obj_sha3_256.hexdigest() # print in hexadecimal encoded_base64_bytes = b64encode(bytes.fromhex(hashed)).decode()
输出对比
- Python代码输出:
R0J8+RX8HHdTiyjJSwstbKetQnI14KY1eugsBKsNXao= - SDK输出:
LzcxChG74WtAw3jO3WQz9b+P44J0ivT/N97PLBELxYU=
问题分析与修正方案
你的代码存在几个关键错误,直接导致结果不一致:
- 未执行前三个节点移除步骤:代码直接读取原始XML就做规范化,完全跳过了ZATCA要求的节点删除操作,这是核心差异来源。
- 规范化标准错误:使用了
C14N(C14N 1.0)而非要求的C14N11(C14N 1.1),两个标准的规范化规则有差异。 - 哈希流程冗余错误:SHA256哈希后先转十六进制字符串,再转回字节做Base64,这会改变原始二进制哈希值的结构。正确流程是直接对SHA256输出的二进制做Base64编码。
- 文件编码未指定:读取XML时未指定编码(比如UTF-8),可能导致字符解析不一致。
修正后的代码示例(使用lxml处理节点)
import lxml.etree as ET import hashlib import base64 # 读取XML并指定编码 tree = ET.parse("invoice_00045.xml") root = tree.getroot() ns = { 'ext': 'urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2', 'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2', 'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2' } # 步骤1:移除<ext:UBLExtensions>块 for elem in root.xpath('./ext:UBLExtensions', namespaces=ns): root.remove(elem) # 步骤2:移除cbc:ID为QR的<cac:AdditionalDocumentReference>块 for elem in root.xpath('./cac:AdditionalDocumentReference[cbc:ID="QR"]', namespaces=ns): root.remove(elem) # 步骤3:移除<cac:Signature>块 for elem in root.xpath('./cac:Signature', namespaces=ns): root.remove(elem) # 步骤4:C14N11规范化(根据ZATCA要求移除注释) canon_xml = ET.tostring(root, method='c14n2', with_comments=False, encoding='utf-8') # 步骤5-6:SHA256哈希+Base64编码 sha256_hash = hashlib.sha256(canon_xml).digest() base64_result = base64.b64encode(sha256_hash).decode('utf-8') print(base64_result)
内容的提问来源于stack exchange,提问作者Zahra
相关产品推荐
相关产品推荐

