了解 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



要对消息进行解密,我们需要添加公钥,值为 +22;如果结果超出了数字范围,就加上或减去 26,直到值有效。(换句话说,要对消息进行解密,我们需要添加公钥,并用结果对 26 取模。)
加密后的值      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



RSA 的工作原理与此相同,只是使用求幂的方法而不是加法,此外,数字的位数比较多。

使用 RSA 生成一个摘要:H(M),并使用我的私钥进行加密,则 {H(M)}private-key 就是我的签名。您收到消息 M 后,可以生成摘要 H'(M),并使用我的公钥来对签名进行解密,就得到我生成的 H(M)。如果 H(M) 和 H'(M) 相同,表明两封消息 M 是相同的。而且,您会发现拥有私钥的人,也就是“我”,是消息的发件人。

签名的格式

XML-DSIG 使用一个单独的命名空间,我们假设示例中存在以下声明:
    xmlns:ds="http://www.w3.org/2000/09/xmldsig#"

顶层的 <ds:Signature> 元素是相当简单的。此元素具有的信息包括要签名的内容、签名、用于创建签名的密钥以及存储任意信息的空间。
    <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>


我们将看到它们的复杂性是依次递增的。
     Id
     ds:SignatureValue
     ds:Object
     ds:SignedInfo
     ds:KeyInfo
ds:Signature/@Id 属性

全局 Id 属性允许文档包含多个签名,并提供了标识特殊实例的方法。业务策略中经常使用多个签名,例如某个旅行申请必须同时得到经理和旅行办公室的批准。
ds:SignatureValue 元素

此元素包含实际签名。由于签名通常是二进制数据,XML DSIG 指定签名值通常是具有 Base64 编码内容的简单元素:
    <element name="SignatureValue" type="ds:SignatureValueType"/> 
    <complexType name="SignatureValueType">
      <simpleContent>
        <extension base="base64Binary">
          <attribute name="Id" type="ID" use="optional"/>
        </extension>
      </simpleContent>
    </complexType>


为了解释 SignatureValue,需要了解 SignedInfo 元素中的内容,也就是我们下面将要讨论的内容。迄今为止,SignatureValue 仅是多个字节的不透明字符串:
    <SignatureValue>
        WvZUJAJ/3QNqzQvwne2vvy7U5Pck8ZZ5UTa6pIwR7GE+PoGi6A1kyw==
    </SignatureValue>

ds:Signature/ds:Object 元素

正如下面我们将要看到的,XML DSIG 可以包含多个项。项通常可以独立存在,例如一个 Web 页面或 XML 业务文档,但有时可以将项视为要进行签名的“真实”内容的元数据。例如,数据可能是签名的某个“属性”,例如生成签名时使用的时间戳。

ds:Object 元素可以用于在签名中存放以下数据:
    <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>


Id 属性允许签名具有多个可以被独立寻址的对象。MimeType 用于标识数据,这样其他处理器也可以使用此数据,但不适用于 DSIG 处理器。

Encoding 指定如何预处理内容;目前只定义了 Base64 编码。

下面是两个(具有相同内容的)对象,可以用作提示文档签名时间的简单指示器。对于联机投标、拍卖或其他具有提交期限的活动,签名中提供这一功能的服务可能是非常有用的:
    <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>

ds:SignedInfo 元素

您听说过这样一句格言吗:“计算机科学中的任何问题都可以用间接的方法来解决”?正如我们就要看到的那样,XML DSIG 是验证这句格言的最好例子。

ds:SignedInfo 的内容可以分为两个部分:关于 SignatureValue 的信息和关于应用程序内容的信息,如以下 XML 架构片断所示:
   <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>


XML 的语法要求不是很严格。例如,属性的顺序和引用值的方式都不是很严格。对于 XML 处理软件,以下两个示例完全等效:
    <a foo='yes' boo="no"/>
    <a boo="no" foo="yes"  ></a>

(细心的读者可以尝试找到我添加的其他两个不同之处。) 但签名需要消息摘要,此类不同之处会产生重大影响。

为了按照此原则进行工作,内容必须是“标准的”。标准化或 C14N 是在所有可能的输出选项中选取一个路径的过程,这样,无论可能涉及到哪种中间 XML 软件,发件人和收件人可以生成完全相同的字节值。C14N 是一个比较深奥的主题,请参阅有关它的文章。

ds:SignedInfo/ds:CanonicalizationMethod 元素指定如何重新构造准确字节流。ds:SignedInfo/ds:SignatureMethod 元素指定用于创建签名的签名类型,例如 Kerberos 或 RSA。综上所述,这两个元素告诉我们如何创建和保护摘要,使其不被修改。

下面是一个示例:
    <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"/>

        ...
ds:Reference 元素

ds:SignatureValue 元素包含仅包含 ds:SignedInfo 元素的签名:签名摘要中只包含 ds:SignedInfo 的内容。那么如何对其他内容进行签名呢?奥妙在于 ds:Reference 元素。

正如前面的 ds:SignedInfoType 的架构定义所示,签名可以具有多个引用。这将允许单个 XML DSIG 包含多个对象:MIME 消息中的所有部分、XML 文件和可以将 XML 文件转换为 HTML 的 XSLT 脚本等。

ds:Reference 元素引用其他内容。此元素包含内容摘要、如何生成摘要的指示(例如 SHA1)以及在生成摘要之前转换内容时所用的规范。转换使 XML DSIG 变得非常灵活。下面是架构片段:
   <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>


Type 属性可以提供处理提示,但通常没什么用处。

URI 指向被引用的实际内容。由于是 URI,因此可以获得 Web 的所有功能。例如,可以对 MSDN 主页上的内容进行签名:
    <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>


还可以引用 XML 文档中的内容,例如前面所示的时间戳:
    <ds:Reference URI="#ts-text">
        <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
        <ds:DigestValue>pN3j2OeC0+/kCatpvy1dYfG1g68=</ds:DigestValue>
    </ds:Reference>

当然,也可以在同一个签名内同时采用这两个引用。

最常见的用法是 URI 片段与 WS-Security 一起使用,以对 SOAP 消息进行签名:
    <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>


正如您可能预期的那样,ds:DigestMethod 指定散列算法,而 ds:DigestValue 则是内容散列的 Base64 值。

ds:Reference 元素中作用最大的部分是可能出现的转换集。ds:Transforms 就是 ds:Transform 元素的列表,每个元素指定一个处理步骤。该架构定义了一个转换数组,其中包含的 ds:XPath 具有定义好的结构:
   <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>


转换的内容将取决于 Algorithm 属性。例如,如果要对简单的 XML 进行签名,那么最可能需要进行指定 C14N 算法的某种转换:
  <ds:Transforms>
    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n"/>
  </ds:Transforms>


XML DSIG 定义了几种转换,其中包括 XPath 转换,使用此转换可以很容易地对文档的某一部分进行签名,例如忽略所有文本,只对标记进行签名:
    <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>


定义的其他转换包括内嵌 XSLT 样式表、对加密数据进行解密等。
ds:KeyInfo 元素

至此,我们知道如何引用内容、如何对内容进行转换和散列以及如何创建包含(保护)内容的签名。使用间接方法撤回受保护的内容:ds:SignatureValue 包含 ds:SignedInfo,后者包含的 ds:References 中又包含应用程序数据的摘要值。如果更改了这些元素中的任何元素,数学计算将被终止,而且无法验证签名。

现在要做的最后一件事是标识签名者,或者至少标识生成签名的密钥(或者为了获得更好的加密性,标识保护摘要不被修改的密钥)。这是 ds:KeyInfo 元素要发挥的作用:
  <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>


正如我们所看到的那样,XML DSIG 支持多种不同密钥类型和密钥基础结构,而 WS-Security 则在此基础上更进一步。我们将只看到两样东西:一个简单的名称和一个 X.509 证书。在为封闭环境生成自定义应用程序时,可以使用 ds:KeyName:
    <element name="KeyName" type="string"/>

这取决于验证签名的过程,验证的过程中将把名称映射到内部存储中,并获取对应的密钥。ds:KeyName 的值通常包括电子消息地址或目录项。

可以通过 ds:X509Data 元素支持 X.509 证书。签名者可以在这个元素中嵌入证书(采用 Base64 编码)或者嵌入可以标识证书的任何其他形式:主题名称、颁发者名称和序列号、密钥标识符或其他格式。签名者还可以包括当前的证书吊销列表 (CRL),以显示在对文档进行签名时签名者的身份是有效。下面的架构片段显示标识 X.509 证书的其他方法:
   <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>

由于不同的应用程序使用不同的架构来存储和检索证书,XML 数字签名经常会通过在同一个 ds:KeyInfo 元素中内嵌同一个密钥的多个名称来包括这些名称。在本示例中,我们既提供了用户容易理解的名称(用于 GUI 应用程序来实现弹出窗口),还在颁发者表单中提供了唯一标识符和序列号(用于目录搜索):
    <ds:KeyInfo>
        <ds:KeyName>
            [email protected]
        </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>


小结

本文对 XML DSIG 规范进行了深入探讨,通过使用架构定义来描述可用功能,并介绍了生成和验证 XML DSIG 文档的处理过程。本文从基本签名元素 (ds:SignedInfo) 入手,探讨了它如何通过引用应用程序内容来保护该内容。在结尾部分,我们探讨了 ds:KeyInfo 元素,了解应用程序如何验证签名以及确认签名者身份的有效性。这三个方面提供了保护 XML(和其他)内容完整性的最基本、最低级别的组件。不用感到奇怪,它们的灵活性意味着直接使用它们可能是相当复杂的。

XML DSIG 的最常用策略之一当然是和 WS-Security 规范一起使用。这样就为数据保护和用户验证提供了更加面向应用程序的视图。要学习如何在 WS-Security 内部使用 XML DSIG,请参阅了解 WS-Security

你可能感兴趣的:(应用服务器,算法,xml,Security,企业应用)