PAYATHON 2026

xmlsec 签名后验证失败,如何排查?

数据阿明

结论

从这段代码看,可以先调整签名节点的插入顺序:将 signature_node 加入 XML 树后,再调用 ctx.sign(signature_node)。当前代码则是在签名完成后才执行 root.append(signature_node)。签名时节点还不在最终文档上下文中,验证时却已位于根节点下。命名空间上下文的变化可能影响 SignedInfo 的规范化结果,进而导致 SignatureValue 校验失败。

可以按下面的顺序处理:

signature_node = xmlsec.template.create(
    root,
    c14n_method=xmlsec.Transform.C14N,
    sign_method=xmlsec.Transform.RSA_SHA256,
    ns="ns3",
)

# 在签名前将签名节点放入最终文档位置
root.append(signature_node)

# 在此处添加 Reference、Transform、KeyInfo 等模板内容
# ...

ctx.sign(signature_node)

在调用 ctx.sign() 前,应完成模板节点、Reference、Transform 和 KeyInfo 的配置。签名后不要再移动签名节点,也不要修改它或其祖先的命名空间上下文。

为什么插入时机可能影响签名

XML Signature 会先按照指定的 canonicalization 算法处理 SignedInfo,再计算签名。示例中使用的是 xmlsec.Transform.C14N。规范化结果可能受到节点位置和可见命名空间声明的影响。

原始 XML 的根元素声明了默认命名空间 http://xxx.xxx.com/shipment。如果签名模板在脱离最终树上下文时完成签名,之后才被插入根元素下,那么签名和验证阶段看到的命名空间上下文可能不同。即使验证时使用的是同一个内存 XML 对象,节点追加后执行的验证仍处于新的上下文中,因此不能排除命名空间变化造成的问题。

先判断失败发生在哪一步

错误中的 xmlSecOpenSSLEvpSignatureVerify 和 Signature verification failed 指向 RSA 签名值校验阶段。通常,这表示验证端根据 SignedInfo 计算出的签名输入与 SignatureValue 不匹配,也可能是验证时使用的公钥与签名私钥不匹配。

这和 Reference 摘要校验失败是两类问题。若 Reference 摘要不匹配,应检查被引用的节点、Transform、签名后是否修改了 XML 内容,以及 DigestValue 能否通过同一处理流程重新计算。当前日志明确显示签名值校验失败,可以先检查签名节点的最终位置、规范化上下文,以及私钥和证书是否配对。

建议的排查步骤

  1. 将 root.append(signature_node) 移到 ctx.sign(signature_node) 之前,确保签名时模板已经处于文档中的最终位置。
  2. 签名后不要移动签名节点,也不要修改可能影响 SignedInfo 规范化的命名空间或内容。
  3. 重新解析写出的 raw_signed.xml 后再执行验证,确认序列化并重新解析后仍能通过,而不只是当前内存对象验证成功。
  4. 确认 myprivatekey.pem 对应 test.crt 中的公钥。验证时从 X509Certificate 取出的证书也必须与私钥配对。证书能够被解析,并不能证明它与签名私钥匹配。
  5. 如果调整顺序后错误变成 Reference 摘要校验失败,再检查 URI=""、XPath2 Filter2 Transform、选中的 Header 和 Body,以及签名后 XML 是否发生变化。local-name() 可以匹配默认命名空间下的元素名称。若要严格限制命名空间,也可以使用带命名空间前缀的 XPath,并确认 XPath2 Transform 的处理方式符合接收方要求。

注意事项

ctx.verify() 执行成功,只能说明提供的公钥能够验证签名值,并且签名引用的内容通过了相应校验。它不能代替证书链、有效期或业务身份验证。若要验证证书信任,还需要在应用层单独验证证书。

备注:内容仅供参考。