什么是 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>
        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>

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

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

 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我来依次回答你的两个问题。 ## 1. openssl使用 OpenSSL是一个强大的开源加密工具包,可以用于加密、解密、签名和验证数据等操作。 具体使用方法如下: 1. 安装OpenSSL:可以从官网下载安装包,根据操作系统安装。 2. 生成密钥对:使用openssl命令生成公私钥对。 ```bash openssl genrsa -out rsa_private_key.pem 2048 openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem ``` 3. 加密数据:使用公钥加密数据。 ```bash openssl rsautl -encrypt -in plaintext.txt -out ciphertext.txt -inkey rsa_public_key.pem -pubin ``` 4. 解密数据:使用私钥解密数据。 ```bash openssl rsautl -decrypt -in ciphertext.txt -out plaintext.txt -inkey rsa_private_key.pem ``` 5. 签名数据:使用私钥对数据进行签名。 ```bash openssl dgst -sha256 -sign rsa_private_key.pem -out signature.bin plaintext.txt ``` 6. 验证签名:使用公钥对签名进行验证。 ```bash openssl dgst -sha256 -verify rsa_public_key.pem -signature signature.bin plaintext.txt ``` ## 2. C#加密数字签名 C#也提供了加密数字签名的功能,可以使用System.Security.Cryptography命名空间下的类来实现。 具体使用方法如下: 1. 生成密钥对:使用RSACryptoServiceProvider类生成公私钥对。 ```csharp RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048); string privateKey = rsa.ToXmlString(true); string publicKey = rsa.ToXmlString(false); ``` 2. 加密数据:使用公钥加密数据。 ```csharp byte[] data = Encoding.UTF8.GetBytes("Hello World!"); byte[] encryptedData = rsa.Encrypt(data, false); ``` 3. 解密数据:使用私钥解密数据。 ```csharp byte[] decryptedData = rsa.Decrypt(encryptedData, false); string plaintext = Encoding.UTF8.GetString(decryptedData); ``` 4. 签名数据:使用私钥对数据进行签名。 ```csharp byte[] data = Encoding.UTF8.GetBytes("Hello World!"); byte[] signature = rsa.SignData(data, new SHA256CryptoServiceProvider()); ``` 5. 验证签名:使用公钥对签名进行验证。 ```csharp bool verified = rsa.VerifyData(data, new SHA256CryptoServiceProvider(), signature); ``` 以上就是关于openssl使用和C#加密数字签名的简单介绍,希望能对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值