如何将带数字签名的XML嵌入另一XML且不使签名失效?
如何确保签名XML插入批量文件后仍有效
我使用Python的signxml模块(enveloped方法)对单个XML事件文件签名,将其插入批量XML文件后签名失效,验证时出现“invalid asymmetric cipher”错误。需要保持当前的输出格式,但让签名在插入后依然有效。
问题原因
enveloped类型的XML签名是基于当前文档的结构和节点上下文计算摘要的,当把已签名的事件XML插入到批量文档中时:
- 被签名节点的父层级、文档结构发生了变化,验证时无法按照原始签名的上下文正确提取目标节点
- 不同命名空间下的ID引用可能出现解析问题,导致签名验证时找不到对应的被签名节点
解决方案
1. 调整操作顺序:先插入事件,再签名目标节点
不要先单独签名事件XML,而是先将未签名的事件插入到批量文档的对应位置,再针对最终文档中的目标节点执行签名操作。这样签名是基于最终的文档结构计算的,后续不会因为结构变化导致签名失效。
2. 确保命名空间和ID的正确性
- 批量文档和事件文档使用了不同的命名空间,查询节点时必须指定完整的命名空间,避免找不到目标节点
- 确保
id属性值在整个最终XML文档中唯一,防止URI引用冲突
修改后的代码实现
from lxml import etree from signxml import XMLSigner, methods from cryptography.hazmat.primitives.serialization import pkcs12 # 提取私钥和证书 private_key, cert, _ = pkcs12.load_key_and_certificates(pfx_data, cert_password) # 加载未签名的事件XML event_xml_str = ''' <Reinf xmlns="http://www.reinf.esocial.gov.br/schemas/evt4020PagtoBeneficiarioPJ/v2_01_02"> <EvtPJ id="ID123"> <cnpj>123456789</cnpj> </EvtPJ> </Reinf> ''' event_root = etree.fromstring(event_xml_str) # 加载批量XML main_xml_str = ''' <Reinf xmlns="http://www.reinf.esocial.gov.br/schemas/envioLoteEventosAssincrono/v1_00_00"> <lote> <eventos> <evento id="ID123"> </evento> </eventos> </lote> </Reinf> ''' main_root = etree.fromstring(main_xml_str) # 找到批量文档中的<evento>节点(指定命名空间) evento_ns = "http://www.reinf.esocial.gov.br/schemas/envioLoteEventosAssincrono/v1_00_00" evento_element = main_root.find(f".//{{{evento_ns}}}evento[@id='ID123']") if evento_element is not None: # 将未签名的事件插入到<evento>节点中 evento_element.append(event_root) # 定位最终文档中的<EvtPJ>节点(指定事件的命名空间) evt_ns = "http://www.reinf.esocial.gov.br/schemas/evt4020PagtoBeneficiarioPJ/v2_01_02" evt_pj_element = main_root.find(f".//{{{evt_ns}}}EvtPJ[@id='ID123']") # 配置XMLSigner,针对目标节点签名 signer = XMLSigner(method=methods.enveloped, signature_algorithm="rsa-sha256", digest_algorithm="sha256") # 签名时引用目标节点的ID,签名会自动添加到目标节点的父节点中 signed_main_root = signer.sign(evt_pj_element, key=private_key, cert=[cert], reference_uri="#ID123") else: print("未找到匹配ID的<evento>节点") # 生成最终XML并保存 final_xml = etree.tostring(main_root, pretty_print=False, xml_declaration=False, encoding="utf-8") with open("xml_final.xml", "wb") as file: file.write(final_xml)
效果说明
修改后的代码会先将未签名事件插入批量文档,再基于最终文档中的<EvtPJ>节点生成签名,此时签名的上下文是最终的批量文档结构,插入操作不会破坏签名的有效性,验证时可以正确定位被签名节点并通过校验。
内容的提问来源于stack exchange,提问作者Victor Akahane
相关产品推荐
相关产品推荐

