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

使用xml-crypto v4.0.1签名SAML XML时摘要值不匹配问题

SAML XML签名摘要不匹配问题排查与解决

环境信息

  • xml-crypto版本:^4.0.1
  • Node.js版本:v16.14.0

错误现象

签名SAML XML时出现验证错误:

invalid signature: for uri calculated digest is 2U1suBt1sOA2olbnbMK1gC/3FHk= but the xml to validate supplies digest OGXcEIgUP1W+Hv9ghexl8gdMtrI=

生成的签名后XML

<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol">
    <saml:Assertion MajorVersion="1" MinorVersion="1"
        AssertionID="_1" Issuer="mydomain.com"
        IssueInstant="2023-08-13T03:01:27.265Z" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"
        Id="_0">
        <saml:Conditions NotBefore="2023-08-13T03:01:27.266Z"
            NotOnOrAfter="2023-08-14T03:01:27.263Z" />
        <saml:AuthenticationStatement AuthenticationMethod="urn:oasis:names:tc:SAML:1.0:am:password"
            AuthenticationInstant="2023-08-13T03:01:27.268Z">
            <saml:Subject>
                <saml:NameIdentifier Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
                    NameQualifier="urn:mydomain.com">123356</saml:NameIdentifier>
                <saml:SubjectConfirmation>
                    <saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
                </saml:SubjectConfirmation>
            </saml:Subject>
        </saml:AuthenticationStatement>
        <saml:AttributeStatement>
            <saml:Subject>
                <saml:NameIdentifier Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
                    NameQualifier="urn:mydomain.com">123356</saml:NameIdentifier>
                <saml:SubjectConfirmation>
                    <saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
                </saml:SubjectConfirmation>
            </saml:Subject>
            <saml:Attribute AttributeName="version" AttributeNamespace="mydomain.com">
                <saml:AttributeValue>1</saml:AttributeValue>
            </saml:Attribute>
        </saml:AttributeStatement>
    </saml:Assertion>
    <samlp:Status>
        <samlp:StatusCode Value="samlp:Success" />
    </samlp:Status>
    <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo>
            <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
            <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
            <Reference URI="#_0">
                <Transforms>
                    <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
                </Transforms>
                <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
                <DigestValue>OGXcEIgUP1W+Hv9ghexl8gdMtrI=</DigestValue>
            </Reference>
        </SignedInfo>
        <SignatureValue>
            HKJ/iE4XpJlwQW9H0zP8yK6SZO/HazYl/YpE09GTwDFcsgPRots+nosEeVtVU0wLkYW6wNgwf79LEoTiEComatyuRhVWg3ZtZpsdSkrkfR74M2B9aECoPS8k5tqArQbJl0KO...[redacted]...tsRlIJX9nMtdg==
        </SignatureValue>
    </Signature>
</samlp:Response>

当前使用的工具函数

去除XML空格函数

function removeXMLSpaces(string){
  let xmlString = string;
  xmlString = xmlString.replace(/>\s*/g, ">");
  xmlString = xmlString.replace(/\s*</g, "<");
  xmlString = xmlString.replace(/\r\n?/g, "\n");
  xmlString = xmlString.replace(/(\r\n\t|\n|\r\t)/gm, "");
  return xmlString;
}

XML签名函数

function signXml(xml, options) {
  console.log({ options });

  const sig = new SignedXml(options);

  // sig.keyInfoProvider = new KeyProvider();

  sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";

  sig.canonicalizationAlgorithm = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";

  sig.addReference({
    xpath: "//*[local-name(.)='Assertion']",
    transforms: ["http://www.w3.org/2000/09/xmldsig#enveloped-signature"]
  });

  sig.computeSignature(xml);

  fs.writeFileSync(process.cwd() + "/xml/signed.xml", sig.getSignedXml());

  let currentSignedXML = sig.getSignedXml();
  currentSignedXML = currentSignedXML.replace("Id=\"_0\"","").replace("URI=\"#_0\"","URI=\"\"");

  return currentSignedXML;
}

问题根源与解决方案

核心问题

  1. 签名后手动修改XML:签名完成后移除Id="_0"并清空URI的操作,直接破坏了签名的完整性。xml-crypto计算摘要时基于的是包含Id="_0"的Assertion节点,验证方计算时会用修改后的节点,自然和原签名的摘要值不匹配。
  2. 自定义空格处理干扰规范化:removeXMLSpaces函数手动修改XML空格,会和xml-crypto自带的c14n规范化算法产生冲突,导致签名和验证时的源内容不一致。

修复步骤

  1. 删除签名后的手动修改代码
    直接返回xml-crypto生成的原始签名XML,不要做任何字符串替换:

    function signXml(xml, options) {
      console.log({ options });
    
      const sig = new SignedXml(options);
    
      sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
      sig.canonicalizationAlgorithm = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
    
      sig.addReference({
        xpath: "//*[local-name(.)='Assertion']",
        transforms: ["http://www.w3.org/2000/09/xmldsig#enveloped-signature"]
      });
    
      sig.computeSignature(xml);
    
      const signedXml = sig.getSignedXml();
      fs.writeFileSync(process.cwd() + "/xml/signed.xml", signedXml);
    
      return signedXml;
    }
    
  2. 移除手动空格处理
    删掉removeXMLSpaces函数的调用,让xml-crypto通过指定的c14n算法自动处理XML格式,确保签名和验证时的规范化结果一致。

  3. 按需配置Reference参数
    如果确实需要空URI或无Id的Assertion,要在签名前就调整好:

    • 若需要空URI:在addReference时直接设置uri: ""
    • 若不需要Assertion的Id:在传入signXml函数前就移除该属性

验证方法

修复后用以下代码测试签名有效性:

const fs = require('fs');
const { SignedXml, FileKeyInfo } = require('xml-crypto');

function verifyXml(xml, publicKeyPath) {
  const sig = new SignedXml();
  sig.keyInfoProvider = new FileKeyInfo(publicKeyPath);
  sig.loadSignature(xml);
  const isValid = sig.checkSignature(xml);
  if (!isValid) {
    console.error(sig.validationErrors);
  }
  return isValid;
}

const signedXml = fs.readFileSync(process.cwd() + "/xml/signed.xml", 'utf8');
console.log("验证结果:", verifyXml(signedXml, "path/to/public.key"));

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 00:45:28