System.Security.Cryptography.Xml.SignedXml 类
本文提供了此 API 参考文档的补充说明。
此类 SignedXml 是万维网联盟 (W3C) XML 签名语法和处理规范的 .NET 实现,也称为 XMLDSIG (XML 数字签名)。 XMLDSIG 是一种基于标准的可互操作方式,用于对 XML 文档或可从统一资源标识符(URI)寻址的所有或部分 XML 文档或其他数据进行签名和验证。
SignedXml每当需要以标准方式在应用程序或组织之间共享签名的 XML 数据时,请使用该类。 使用此类签名的任何数据都可以通过 XMLDSIG W3C 规范的符合性实现进行验证。
类 SignedXml 允许创建以下三种类型的 XML 数字签名:
签名类型 | 说明 |
---|---|
信封签名 | 签名包含在正在签名的 XML 元素中。 |
Enveloping 签名 | 签名的 XML 包含在元素中 <Signature> 。 |
内部分离签名 | 签名和签名 XML 位于同一文档中,但两个元素均不包含另一个元素。 |
还有第四种签名称为外部分离签名,即数据和签名位于单独的 XML 文档中。 类不支持 SignedXml 外部分离签名。
XML 签名的结构
XMLDSIG 创建一个 <Signature>
元素,其中包含 XML 文档的数字签名或其他可从 URI 寻址的数据。 该 <Signature>
元素可以选择包含有关在何处查找密钥的信息,该密钥将验证签名以及用于签名的加密算法。 基本结构如下所示:
<Signature xmlns:ds="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="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>Base64EncodedValue==</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>AnotherBase64EncodedValue===</SignatureValue>
</Signature>
此结构的主要部分包括:
<CanonicalizationMethod>
元素指定将元素从 XML/text 重写
Signature
为字节的规则进行签名验证。 .NET 中的默认值用于 http://www.w3.org/TR/2001/REC-xml-c14n-20010315标识可信算法。 此元素由 SignedInfo.CanonicalizationMethod 属性表示。<SignatureMethod>
元素指定用于生成签名和验证的算法,该算法应用于元素
<Signature>
以生成值<SignatureValue>
。 在前面的示例中,该值 http://www.w3.org/2000/09/xmldsig#rsa-sha1 标识 RSA PKCS1 SHA-1 签名。 由于 SHA-1 发生冲突问题,Microsoft 建议使用基于 SHA-256 或更高版本的安全模型。 此元素由 SignatureMethod 属性表示。<SignatureValue>
元素指定元素的
<Signature>
加密签名。 如果未验证此签名,则阻止的某些<Signature>
部分被篡改,并且文档被视为无效。 只要<CanonicalizationMethod>
该值可信,此值就高度抵御篡改。 此元素由 SignatureValue 属性表示。URI
元素的属性<Reference>
使用 URI 引用指定数据对象。 此属性由 Reference.Uri 属性表示。
不指定
URI
属性(即将 Reference.Uri 属性设置为null
)意味着接收应用程序应知道对象的标识。 在大多数情况下,null
URI 将导致引发异常。 除非应用程序与需要 URI 的协议进行互操作,否则不要使用null
URI。将
URI
属性设置为空字符串表示文档的根元素正在签名,这是一种信封签名形式。URI
如果属性值以 #开头,则该值必须解析为当前文档中的元素。 此表单可以与任何受支持的签名类型(信封签名、封装签名或内部分离签名)一起使用。其他任何内容都被视为外部资源分离签名,类不支持 SignedXml 。
<Transforms>
元素包含描述签名者如何获取摘要的数据对象的元素的有序列表
<Transform>
。 转换算法类似于规范化方法,但不重写<Signature>
元素,而是重写元素属性<Reference>
标识URI
的内容。 该<Transforms>
元素由 TransformChain 类表示。每个转换算法都定义为采用 XML(XPath 节点集)或字节作为输入。 如果当前数据的格式与转换输入要求不同,则应用转换规则。
每个转换算法都定义为生成 XML 或字节作为输出。
如果最后一个转换算法的输出未以字节为单位定义(或未指定转换),则 规范化方法 将用作隐式转换(即使元素
<CanonicalizationMethod>
中指定了其他算法)。转换算法的值 http://www.w3.org/2000/09/xmldsig#enveloped-signature 对解释为从文档中删除
<Signature>
元素的规则进行编码。 否则,信封签名的验证程序将消化文档,包括签名,但签名者会在应用签名之前消化文档,从而导致不同的答案。
<DigestMethod>
元素标识要应用于由元素属性标识
URI
的转换内容的<Reference>
摘要(加密哈希)方法。 此属性表示 Reference.DigestMethod 。
选择规范化方法
除非与需要使用不同值的规范进行互操作,否则建议使用默认的 .NET 规范化方法,即 XML-C14N 1.0 算法,其值为 http://www.w3.org/TR/2001/REC-xml-c14n-20010315。 XML-C14N 1.0 算法需要由 XMLDSIG 的所有实现支持,特别是因为它是要应用的隐式最终转换。
有一些版本的规范化算法支持保留注释。 不建议使用注释保留规范化方法,因为它们违反了“看到的内容”原则。 也就是说,元素中的 <Signature>
注释不会更改签名的执行方式的处理逻辑,而只会更改签名值。 与弱签名算法结合使用时,允许包含注释,使攻击者不必自由地强制发生哈希冲突,使篡改的文档看起来是合法的。 在 .NET Framework 中,默认情况下仅支持内置规范化程序。 若要支持其他或自定义规范化程序,请参阅该 SafeCanonicalizationMethods 属性。 如果文档使用不在属性所表示 SafeCanonicalizationMethods 的集合中的规范化方法,则 CheckSignature 该方法将返回 false
。
注意
极其防御的应用程序可以删除它不希望签名者从 SafeCanonicalizationMethods 集合中使用的任何值。
引用值是否安全篡改?
是的 <Reference>
,这些值可以安全篡改。 .NET 在 <SignatureValue>
处理任何 <Reference>
值及其关联的转换之前验证计算,并会提前中止以避免潜在的恶意处理指令。
选择要签名的元素
如果可能,建议对属性使用“” URI
值(或将 Uri 属性设置为空字符串)。 这意味着整个文档被视为摘要计算,这意味着整个文档不受篡改的保护。
通常以定位点(如 #foo)的形式查看 URI
值,引用其 ID 属性为“foo”的元素。 遗憾的是,这很容易被篡改,因为这只包括目标元素的内容,而不是上下文。 滥用此区别称为 XML 签名包装 (XSW)。
如果应用程序认为注释是语义(处理 XML 时不常见),则应使用“#xpointer(/)”而不是“”和“#xpointer(id('foo')”而不是“#foo”。 #xpointer 版本被解释为包括注释,而短名称表单不包括注释。
如果需要接受仅部分保护的文档,并且希望确保读取受签名保护的相同内容,请使用 GetIdElement 该方法。
有关 KeyInfo 元素的安全注意事项
可选 <KeyInfo>
元素(即 KeyInfo 属性)中的数据(即包含用于验证签名的密钥)不应受信任。
具体而言,当值表示裸 RSA、DSA 或 ECDSA 公钥时 KeyInfo ,尽管报告签名有效的方法, CheckSignature 但文档可能已被篡改。 之所以发生这种情况,是因为执行篡改的实体只需生成新密钥,并使用该新密钥重新对被篡改的文档进行签名。 因此,除非应用程序验证公钥是否为预期值,否则文档应被视为被篡改。 这要求应用程序检查文档中嵌入的公钥,并针对文档上下文的已知值列表对其进行验证。 例如,如果已知用户可以理解该文档是颁发的,则针对该用户使用的已知密钥列表检查密钥。
还可以使用 CheckSignatureReturningKey 该方法(而不是使用 CheckSignature 该方法)在处理文档后验证密钥。 但是,为了获得最佳安全性,应事先验证密钥。
或者,请考虑尝试用户注册的公钥,而不是读取元素中 <KeyInfo>
的内容。
有关 X509Data 元素的安全注意事项
可选 <X509Data>
元素是元素的 <KeyInfo>
子元素,包含 X509 证书的一个或多个 X509 证书或标识符。 元素中的数据 <X509Data>
也不应本质上是受信任的。
使用嵌入 <X509Data>
元素验证文档时,.NET 仅验证数据是否解析为 X509 证书,该证书的公钥可用于验证文档签名。 与调用CheckSignature参数设置为false
的方法verifySignatureOnly
不同,不会执行吊销检查,也不会检查链信任,也不会验证过期。 即使应用程序提取证书本身并将其传递给CheckSignature参数设置为false
的方法verifySignatureOnly
,该方法仍不足以防止文档篡改。 仍需要验证证书是否适合正在签名的文档。
使用嵌入式签名证书可以提供有用的密钥轮换策略,无论是在 <X509Data>
节中还是在文档内容中。 使用此方法时,应用程序应手动提取证书并执行类似于:
证书是通过证书颁发机构(CA)直接或通过证书链颁发的,该证书颁发机构(CA)将其公共证书嵌入到应用程序中。
在没有其他检查(如已知使用者名称)的情况下使用 OS 提供的信任列表不足以防止篡改SignedXml。
在文档签名时(或“现在”用于近实时文档处理)验证证书是否已过期。
对于支持吊销的 CA 颁发的长期证书,请验证证书是否未吊销。
证书使用者已根据当前文档进行验证。
选择转换算法
如果要与指定特定值(如 XrML)的规范进行互操作,则需要遵循规范。 如果你有一个信封签名(如签名整个文档时),则需要使用 http://www.w3.org/2000/09/xmldsig#enveloped-signature (由 XmlDsigEnvelopedSignatureTransform 类表示)。 也可以指定隐式 XML-C14N 转换,但不是必需的。 对于包围或分离签名,无需转换。 隐式 XML-C14N 转换负责一切。
随着 Microsoft 安全公告 MS16-035 引入的安全更新,.NET 已限制默认情况下可在文档验证中使用的转换,导致CheckSignature始终返回false
不受信任的转换。 特别是,由于恶意用户的滥用,不再需要其他输入(在 XML 中指定为子元素)的转换。 W3C 建议避免 XPath 和 XSLT 转换,这是受这些限制影响的两个主要转换。
外部引用的问题
如果应用程序未验证外部引用是否适合当前上下文,则可以以提供许多安全漏洞(包括拒绝服务、分布式反应拒绝服务、信息泄露、签名绕过和远程代码执行)的方式滥用这些引用。 即使应用程序要验证外部引用 URI,资源仍存在两次加载问题:应用程序读取资源时,一次 SignedXml 读取它。 由于无法保证应用程序读取和文档验证步骤具有相同的内容,因此签名不提供可信度。
鉴于外部引用的风险, SignedXml 在遇到外部引用时将引发异常。 有关此问题的详细信息,请参阅知识库(KB)文章3148821。