了解 XML 数字签名
适用于:Web 服务规范(WS-Security 规范以及其他规范)
Microsoft .NET Framework
摘要:本文探讨 XML 数字签名规范,介绍其处理模型和某些功能。此外,还可以通过本文更加详细、深入地了解有关 WS-Security 规范如何实现其消息安全功能。(本文包含一些指向英文站点的链接。)
目录
简介
不涉及复杂数学的数字签名加密
签名格式
小结
简介
数字签名十分重要,因为它可以提供端到端的消息完整性保证,还可以提供有关消息发件人的验证信息。为了达到较好的效果,签名必须是应用程序数据的一部分,这样可以在创建消息时生成签名,并可以在最终使用和处理消息时对签名进行验证。
除消息保密性功能以外,SSL/TLS 还提供消息完整性保障,但这项功能仅在传送过程中有效。服务器(或更常见的对等接收方)接收消息后,必须“解除”SSL 保护才能处理消息。
更为有趣的是,SSL 只在通信端点之间起作用。如果在开发新的 Web 服务时使用传统的 HTTP 服务器(例如 IIS 或 Apache)作为网关,或者与具有 SSL 加速器的大型企业进行通信时,消息完整性可以一直得到保障,直到 SSL 连接终止。
可以拿传统信函进行类比说明。当将支票邮寄到电话公司时,通常会在支票(即“消息”)上签名,并将支票放入信封中以达到保密和邮寄的目的。收到消息后,电话公司会拆开信封,将信封扔在一边,然后处理支票。当然,也可以让消息成为信封的一部分,例如将付款贴在明信片上邮寄过去,但这样做显然很愚蠢。
XML 签名可以定义一系列 XML 元素,这些元素可以内嵌或以其他方式附加在任何 XML 文档中。这样,收件人可以验证收到的消息与发件人原本发送的消息是否相同。
XML 签名的语法和处理规范(本文中缩写为 XML DSIG)是由 W3C 和 IETF 联合制定的。自 2002 年 2 月以来,它一直是正式的 W3C 推荐规范,并得到了广泛的应用:.NET Framework 中的 System.Security.Cryptography.Xml 就应用了此规范。在 WS-Security 具有的验证、内容完整性和内容保密性三种功能当中,XML DSIG 可以提供完整性,并可用于进行发件人验证。
不涉及复杂数学的数字签名加密
在深入了解 XML DSIG 之前,需要了解一些基本加密算法。本节将探讨这些概念,但是不要害怕:并不会涉及复杂数学。
数字签名对某些内容提供完整性检查。如果原始内容的某个字节已经被修改(例如在价格上多加了个零,“2”改成了“4”,或者“否”改成了“是”等),那么签名验证将失败。下面是它的工作原理。
首先需要“散列”消息。加密散列使用的是任意字节流,并将其转换为某个固定长度的值,这个值称为“摘要”。摘要是一个单向过程:从计算角度来说,无法通过散列重新创建消息,也不可能找到可以产生相同摘要值的两封不同消息。
最常用的散列机制是 SHA1,即“安全散列算法”。此算法由美国政府创建,并在 1995 年作为标准发布。访问 http://www.itl.nist.gov/fipspubs/fip180-1 可以获得完整的规范。SHA1 可以处理 2**64 字节以内的任何消息,并生成一个 20 字节的结果。(这意味着将有 2**160 个可能的摘要值;比较起来,目前估计宇宙中的质子数目大约是 2**250。)
所以,如果我生成消息 M 并创建摘要(用 H(M) 代表“M 的散列”),您将收到 M 和 H(M),您可以创建自己的摘要 H'(M),如果两个摘要值匹配,表明您收到了我发送的消息。要保护 M 使其不被修改,我只需要保护 H(M),使它不被修改。
那该怎么做呢? 有两种常用的方法:第一种是将共享密钥混合在摘要中,即创建 H(S+M)。您收到消息后,可以使用自己的 S 副本来创建 H'(S+M)。新的摘要称为 HMAC,即“散列后的消息验证代码”(Hashed Messsage Authentication Code)。
在使用 HMAC 时,完整性保护的有效性取决于攻击者计算出 S 的能力。因此,S 应该不容易被猜出,并应该经常更改 S。符合以上要求的最好方法之一是使用 Kerberos。在 Kerberos 中,中央机构将在两个实体希望通信时分配包含临时会话密钥的“票”。此会话密钥将用作共享密钥。在要将签名发送给您时,我将得到一张票,以和你通信。我打开票中的属于我的那一部分来获得 S,然后将消息、消息的 HMAC 和票中您的那一部分发送给您。您打开票(使用原来通过 Kerberos 注册的密钥),获取 S 以及有关我身份的信息。您现在可以获得消息 M,生成自己的 H'(S+M),并查看它们是否匹配。如果匹配,表明您原封不动地收到了我的消息,并且 Kerberos 将通知您我是谁。
另外一个保护摘要的方法是使用公钥加密算法,例如 RSA。公钥加密算法中有两个密钥:一个是只有持有者知道的私钥,另一个是要与密钥持有者通信的任何人都知道的公钥。在公钥加密算法中,使用私钥加密的任何内容都可以使用公钥解密,反之亦然。
下面通过一个简单的示例来说明公钥加密算法的工作原理。在此示例中,我们将消息内容限制为从 a 到 z 的字母,并给这些字母指派 0 到 26 的值。要对消息进行加密,我们需要添加私钥的值;在本示例中,此值为 +4:
字母 h e l l o数值 8 5 12 12 15私钥 4 4 4 4 4加密后的值 12 9 16 16 19
加密后的值 12 9 16 16 19公钥 22 22 22 22 22原始加密后的值 34 31 38 38 41规范化的值 8 5 12 12 15纯文本 h e l l o
<element name="Signature" type="ds:SignatureType"/> <complexType name="SignatureType"> <sequence> <element ref="ds:SignedInfo"/> <element ref="ds:SignatureValue"/> <element ref="ds:KeyInfo" minOccurs="0"/> <element ref="ds:Object" minOccurs="0" maxOccurs="unbounded"/> </sequence> <attribute name="Id" type="ID" use="optional"/> </complexType>
<element name="SignatureValue" type="ds:SignatureValueType"/> <complexType name="SignatureValueType"> <simpleContent> <extension base="base64Binary"> <attribute name="Id" type="ID" use="optional"/> </extension> </simpleContent> </complexType>
<SignatureValue> WvZUJAJ/3QNqzQvwne2vvy7U5Pck8ZZ5UTa6pIwR7GE+PoGi6A1kyw== </SignatureValue>
<element name="Object" type="ds:ObjectType"/> <complexType name="ObjectType" mixed="true"> <sequence minOccurs="0" maxOccurs="unbounded"> <any namespace="##any" processContents="lax"/> </sequence> <attribute name="Id" type="ID" use="optional"/> <attribute name="MimeType" type="string" use="optional"/> <attribute name="Encoding" type="anyURI" use="optional"/> </complexType>
<ds:Object Id="ts-bin" Encoding="http://www.w3.org/2000/09/xmldsig#base64"> V2VkIEp1biAgNCAxMjoxMTowMyBFRFQgMjAwMwo </ds:Object> <ds:Object Id="ts-text"> Wed Jun 4 12:11:06 EDT </ds:Object>
<element name="SignedInfo" type="ds:SignedInfoType"/> <complexType name="SignedInfoType"> <sequence> <element ref="ds:CanonicalizationMethod"/> <element ref="ds:SignatureMethod"/> <element ref="ds:Reference" maxOccurs="unbounded"/> </sequence> <attribute name="Id" type="ID" use="optional"/> </complexType>
<ds:SignedInfo> <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n"/> <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<element name="Reference" type="ds:ReferenceType"/> <complexType name="ReferenceType"> <sequence> <element ref="ds:Transforms" minOccurs="0"/> <element ref="ds:DigestMethod"/> <element ref="ds:DigestValue"/> </sequence> <attribute name="Id" type="ID" use="optional"/> <attribute name="URI" type="anyURI" use="optional"/> <attribute name="Type" type="anyURI" use="optional"/> </complexType>
<ds:Reference URI="http://msdn.microsoft.com"> <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> <ds:DigestValue>HB7i8RaV7ZvuUlaTzZVx0S3POpU=</ds:DigestValue> </ds:Reference>
<ds:Reference URI="#ts-text"> <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> <ds:DigestValue>pN3j2OeC0+/kCatpvy1dYfG1g68=</ds:DigestValue> </ds:Reference>
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP:Header> <wsse:Security> xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/07/secext"> ... <ds:Signature> ... <ds:SignedInfo> <ds:Reference URI='#Body'> ... </ds:Reference> ... <ds:SignedInfo> ... </ds:Signature> ... </wsse:Security> </SOAP:Header> <SOAP:Body Id='Body'> ... </SOAP:Body> </SOAP:Envelope>
<element name="Transforms" type="ds:TransformsType"/> <complexType name="TransformsType"> <sequence> <element ref="ds:Transform" maxOccurs="unbounded"/> </sequence> </complexType> <element name="Transform" type="ds:TransformType"/> <complexType name="TransformType" mixed="true"> <choice minOccurs="0" maxOccurs="unbounded"> <any namespace="##other" processContents="lax"/> <element name="XPath" type="string"/> </choice> <attribute name="Algorithm" type="anyURI" use="required"/> </complexType>
<ds:Transforms> <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n"/> </ds:Transforms>
<ds:Transforms> <ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"> <XPath>not(self::text())</XPath> </ds:Transform> <ds:Transform Algorithm="http://www.w3.org/TR/2001/10/xml-exc-c14n/> </ds:Transforms>
<element name="KeyInfo" type="ds:KeyInfoType"/> <complexType name="KeyInfoType" mixed="true"> <choice maxOccurs="unbounded"> <element ref="ds:KeyName"/> <element ref="ds:KeyValue"/> <element ref="ds:RetrievalMethod"/> <element ref="ds:X509Data"/> <element ref="ds:PGPData"/> <element ref="ds:SPKIData"/> <element ref="ds:MgmtData"/> <any processContents="lax" namespace="##other"/> <!-- (1,1) elements from (0,unbounded) namespaces --> </choice> <attribute name="Id" type="ID" use="optional"/> </complexType>
<element name="X509Data" type="ds:X509DataType"/> <complexType name="X509DataType"> <sequence maxOccurs="unbounded"> <choice> <element name="X509IssuerSerial" type="ds:X509IssuerSerialType"/> <element name="X509SKI" type="base64Binary"/> <element name="X509SubjectName" type="string"/> <element name="X509Certificate" type="base64Binary"/> <element name="X509CRL" type="base64Binary"/> <any namespace="##other" processContents="lax"/> </choice> </sequence> </complexType> <complexType name="X509IssuerSerialType"> <sequence> <element name="X509IssuerName" type="string"/> <element name="+X509SerialNumber" type="integer"/> </sequence> </complexType>
<ds:KeyInfo> <ds:KeyName> rsalz@datapower.com </ds:KeyName> <ds:X509Data> <ds:X509SubjectName> cn=Rich Salz, o=DataPower, c=US </ds:X509SubjectName> <ds:X509IssuerSerial> <ds:IssuerName> ou=Development, o=DataPower, c=US </ds:IssuerName> <ds:SerialNumber>32</ds:SerialNumber> </ds:X509IssuerSerial> </ds:X509Data> </ds:KeyInfo>