使用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; }
问题根源与解决方案
核心问题
- 签名后手动修改XML:签名完成后移除
Id="_0"并清空URI的操作,直接破坏了签名的完整性。xml-crypto计算摘要时基于的是包含Id="_0"的Assertion节点,验证方计算时会用修改后的节点,自然和原签名的摘要值不匹配。 - 自定义空格处理干扰规范化:
removeXMLSpaces函数手动修改XML空格,会和xml-crypto自带的c14n规范化算法产生冲突,导致签名和验证时的源内容不一致。
修复步骤
删除签名后的手动修改代码
直接返回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; }移除手动空格处理
删掉removeXMLSpaces函数的调用,让xml-crypto通过指定的c14n算法自动处理XML格式,确保签名和验证时的规范化结果一致。按需配置Reference参数
如果确实需要空URI或无Id的Assertion,要在签名前就调整好:- 若需要空URI:在
addReference时直接设置uri: "" - 若不需要Assertion的Id:在传入
signXml函数前就移除该属性
- 若需要空URI:在
验证方法
修复后用以下代码测试签名有效性:
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
相关产品推荐
相关产品推荐

