"http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
"http://www.w3c.org/TR/1999/REC-xpath-19991116"
>
ancestor-or-self::my:SignerID
图8 一个稍微复杂一些的签名文档
迄今为止讨论的所有xml签名特性在.NET Framework 1.x和2.0中都可用。xml加密和X.509证书集成对于版本2.0而言是新增特性。通过X.509证书集成,可以更容易地将X.509证书用于xml签名。通过新增的X509CertificateEx类和相关的类,可以更容易地操纵和使用证书,并且xml签名对象模型在适当的时候使用这些类。在本文的结尾,我们将对X.509集成进行更详细的讨论。xml加密是另外一个W3C标准。正如xml签名指定了有关创建xml形式的数字签名的格式和处理模型一样,xml加密对如何加密xml形式的数据进行了标准化。xml数字签名是通过Signedxml类驱动的,而xml加密是使用新的Encryptedxml类执行的。尽管xml加密可以用来加密任意数据,但它最常用于加密其他xml。当以这种方式使用时,您将在文档的加密方式上拥有很多的灵活性。例如,可以用不同的密钥加密xml文档的不同节点,同时将某些节点保留为明文。而且,由于用Encryptedxml类加密某些内容会产生xml,因此您甚至可以加密已经加密的结果,该过程称为“超级加密”。让我们考察一下一些已经加密的xml(参见图9)。一件可以立刻注意到的有趣的事情是,xml加密标准对某些元素(包括KeyInfo元素)使用xml签名命名空间。
一个简单的文档:
Hello
World
经过加密的文档:
>
EncryptedData是通过加密xml生成的根元素,并且它包含有关数据的所有信息。EncryptedData包含三个主要的子元素:EncryptionMethod指定用来加密数据的算法;KeyInfo元素提供有关使用哪个密钥来解密数据的信息;CipherData或CipherReference元素则包含实际的加密信息。EncryptionMethod元素指定用来加密和解密密码数据的算法,这些算法由URI指定,就像xml签名标准一样。EncryptionMethod同时用于加密数据和加密密钥,但并非每个算法都可以同时用于这两个场合。图10显示了每个算法可以用在哪个场合。请注意,高级加密标准(Advanced Encryption Standard,AES)还可用于192位和128位密码;您只需在URI中更改密钥大小。
Encryptedxml类的URI属性 | 加密数据 | 加密密钥 | |
AES | xmlEncAES256Url | √ | |
xmlEncAES256KeyWrapUrl | √ | ||
DES | xmlEncDESUrl | √ | |
TripleDES | xmlEncTripleDESUrl | √ | |
xmlEncTripleDESKeyWrapUrl | √ | ||
RSA | xmlEncRSA1_5Url | √ |
图10 使用加密算法的场合
在加密或解密任何数据之前,加密引擎需要知道应当使用哪个密钥来加密和解密。可以用两种方式标识密钥,最容易的方式是为该密钥分配一个名称,并且在KeyInfo元素内部放置一个KeyName元素。解密文档的应用程序可以获得KeyName标记,并提供与给定的名称相匹配的密钥。该应用程序不仅提供密钥的名称,还可以将密钥作为EncryptedKey直接嵌入到KeyInfo元素中。EncryptedKey与EncryptedData包含相同的元素:加密方法、用来解密该密钥的密钥以及构成加密密钥的密码数据。加密密钥通常与命名密钥结合使用,以作为随机会话密钥。首先,生成一个随机会话密钥并使用它来加密xml;然后,用需要解密文档的众所周知的命名密钥加密会话密钥本身;最后,将该命名密钥插入到加密会话密钥的KeyInfo元素中,并且将该加密会话密钥附加到加密数据中。图9中的示例说明了这一点。xml数据的加密方法是256位的AES,而AES算法的密钥也已经被加密。EncryptedKey元素包含有关如何加密AES算法的密钥的信息,在该示例中,它是使用名为recipients_public_key的RSA密钥加密的。使用xml加密的应用程序必须将该名称映射到实际的密钥。
可以将实际的加密数据嵌入到EncryptedData元素中,或者将其放到单独的位置,然后从EncryptedData中引用它。如果要将密码数据直接放到EncryptedData中,则会将其作为Base64编码的二进制文件放到CipherData元素中。图9中的示例使用了一个CipherData元素。另一个方案是将加密数据放到EncryptedData元素外部。可以将密码文本放在从该文档中的另一个元素到远程Web站点的任何位置。在这两种情况下,都将使用CipherReference元素而不是CipherData元素(参见图11)。
CipherReference的位置 | URI格式 | 密码文本格式 |
相同文档中 | #order | Base64字符串 |
远程Web站点 | http://www.example.com/order.bin | 二进制 |
图11 CipherReferences
对远程Web站点的CipherReferences提出了一个有趣的安全方案。在解密xml的过程中,解密引擎必须转到任意Web站点并下载密码文本。由于要解密的文档可能不会受到与完成解密的代码同等程度的信任,因此应当在沙箱中完成该操作。这是通过向Encryptedxml对象提供它将在解析任何CipherReferences时使用的证据来完成的。通过沙箱可以更安全地执行代码,因为解密应用程序可能不具有与提供加密数据的站点相同的权限。例如,如果应用程序试图解密不受信任的站点,并且该不受信任的站点不能够访问位于安全的、受信任站点上的某些受信任的数据,则它可以通过包含密码引用,让解密应用程序为它访问该文件。由于.NET安全策略是以证据为中心的,因此所提供的证据通常应当至少包含Site、Zone和Url对象,如下所示:
// Create evidence based on the referring document
Evidence evidence = new Evidence();
evidence.AddHost(new Zone(SecurityZone.Internet));
evidence.AddHost(new Site("untrustedsite"));
evidence.AddHost(new Url("untrustedsite/encrypted.xml"));
Encryptedxml exml = new Encryptedxml(untrustedDoc, evidence);
现在,让我们考察一下如何使用.NET Framework 2.0中的类。该示例说明了一个销售CD的Web站点,每次购买活动都被归档到一个xml文档中,其中包含有关订购了哪些商品、发货信息和信用卡号的详细信息。图12显示了订单的一些示例xml。
图12 一个CD订单的xml
在该实例中,公司内部的任何人查看订单中的项目是一件可以接受的事情,但是您可能希望对某些更为敏感的数据(例如,发货地址和信用卡信息)进行保密。实际上,您甚至可能希望对它们分别进行加密,以便只有计帐部门能够访问信用卡信息,并且只有发货部门能够访问发货地址。要做到这一点,需要对付款元素下的xml部分进行加密,以便只有计帐部门能够访问它,只有发货部门可以获得的单独密钥将用来加密发货元素。最后,整个订单将用公司中任何人都可以获得的密钥加密。
第一步是创建一个Encryptedxml对象。这是通过传入具有要加密或解密的数据的文档完成的,如下所示:
// Assumes the order is in the order.xml file.
xmlDocument doc = new xmlDocument();
doc.Load("order.xml");
Encryptedxml exml = new Encryptedxml(doc);
在加密xml之前,必须将要使用的密钥映射到它们的相应名称,这些名称将出现在KeyName元素中。可以使用Encryptedxml的AddKeyMapping方法完成该任务:
// Set up the key mapping. Assumes a method called GetBillingKey
// that returns the RSA key for the billing department.
RSA billingKey = GetBillingKey();
exml.AddKeyNameMapping("billing", billingKey);
在设置了密钥-名称映射以后,加密xml就很容易了。第一步是调用Encrypt方法,它完成实际的加密,并且返回一个EncryptedData对象以表示文档的加密部分。之后,您需要调用一个工具方法,将原始xml文档的未加密部分换为新的加密数据:
// Find the element to encrypt.
xmlElement paymentElement =
doc.SelectSingleNode("//order/payment") as xmlElement;
// Encrypt the payment element, passing in the key name.
EncryptedData encryptedPayment =
exml.Encrypt(paymentElement, "billing");
// Swap the encrypted element for the unencrypted element.
Encryptedxml.ReplaceElement(paymentElement, encryptedPayment, true);
>
Sce6lLD+u2f8HzPFyuGxTF32z4mb2ugql3JuJIPAqIP98iYs+Muhqg==
图13 加密了支付信息的CD订单
请观察一下加密数据,您可以看到我们已经描述的各个部分。首先,EncryptionMethod元素显示了AES-256的URI,这意味着该文档用带有256位密钥的AES算法(由RijndaelManaged类实现)加密。Encrypt方法为您生成一个随机的会话密钥;该密钥在KeyInfo元素中加密。通过观察EncryptedKey元素,您可以看到它是用RSA算法并借助于一个名为“billing”的密钥加密的。CipherData元素存放了该密钥的加密值。在KeyInfo的后面是包含加密内容(原来的付款元素)的CipherData。解密刚才说明的文档很容易,这要归功于Encryptedxml的DecryptDocument方法。首先,加载含有加密内容的文档:
// Assumes the encrypted order is in encrypted.xml
xmlDocument doc = new xmlDocument("encrypted.xml");
Encryptedxml exml = new Encryptedxml(doc, documentEvidence);
接下来,设置密钥-名称映射,如下所示:
// Set up the key mapping. Assumes a method called GetBillingKey
// that returns the RSA key for the billing department.
RSA billingKey = GetBillingKey();
exml.AddKeyNameMapping("billing", billingKey);
最后,调用DecryptDocument。该方法将负责解密密码文本,并且将加密的xml替换为它的解密内容。
// Decrypt the encrypted xml in the document
exml.DecryptDocument();
在幕后,加密引擎将在调用DecryptDocument时寻找任何EncryptedData元素,在这种情况下,它只找到付款元素下面的那个元素。在找到该元素以后,它将查看KeyInfo子句并且看到它持有加密的256位AES密钥。加密密钥的KeyInfo将显示它是用名为billing的RSA密钥加密的。然后,它检查密钥映射表以寻找名为billing的密钥。我们为一个名为billing的密钥添加了一个密钥映射,因此引擎随后将解密加密密钥。既然引擎具有密钥,那么它将解密CipherData。然后,用解密的CipherData的结果来替换EncryptedData元素。当DecryptDocument被调用时,引擎将对它在该文档中找到的每个EncryptedData元素执行这一过程。
安全方面的难题之一是信任。在网络中安全地分发和存储受信任的公钥并不容易,但是,Windows提供了大量基础结构来解决该问题。加密API(Cryptographic API,CAPI)提供了对分发、操纵和存储X.509证书的支持。CAPI提供的支持也称为公钥基础结构(Public Key Infrastructure,PKI)。
X.509证书提供有关密钥的其他信息。证书指定了密钥的颁发者、密钥的接收者、证书何时有效以及实际的密钥信息。证书是由证书颁发机构(Certificate Authorities,CA)颁发的,CA是您给予信任以担保证书持有者身份的实体(例如,某个外部公司或您所在公司的IT部门)。如果您具有一个有效证书,它包含由您信任的CA颁发的属于您的计帐部门的公钥,则您可以确保该公钥确实是计帐部门的。
通过Windows PKI支持,您可以安全地在计算机上的证书存储器中存储证书,在网络计算机上的证书存储器中添加和移除证书,在网络计算机上添加和移除受信任的CA,获得和验证有关单个证书的信息等等。例如,这使您可以将证书(包含您所在公司计帐部门的公钥)分发到公司网络中的所有计算机。在.NET Framework 1.x中,必须调用非托管API才能利用该支持的大部分功能。在.NET Framework 2.0中,可以在托管代码中通过X509CertificateEx类和相关的类使用这些API中的大多数API。xml签名类直接支持X509CertificateEx类。要用证书创建xml签名,只需从该证书的PrivateKey属性中获得私钥,然后将其用作Signedxml对象的签名密钥。
// Use the private key from the certificate. Assumes a Signedxml
// object in sig and an X509CertificateEx object in cert.
sig.SigningKey = cert.PrivateKey;
当然,该证书必须具有相关联的私钥,否则PrivateKey属性返回null。您还可以通过使用KeyInfoX509Data类将X.509证书信息添加到已签名的KeyInfo元素中,如以下代码所示:
// Add X.509 certificate info to the KeyInfo element. Assumes a
// Signedxml object in sig and an X509CertificateEx in cert.
KeyInfoX509Data keyInfoX509 =
new KeyInfoX509Data(cert, X509IncludeOption.EndCertOnly);
sig.KeyInfo.AddClause(keyInfoX509);
要验证用证书的私钥签名的xml签名,可以调用Signedxml类的CheckSignature方法的新重载,该新重载采用一个X509CertificateEx对象和一个布尔值。如果该布尔值被设置为true,则该方法将针对证书中的公钥验证签名,并且通过检查密钥使用率以及生成到受信任的根颁发者的链条来验证该证书。
// Check the signature against the cert and verify the cert. Assumes a
// Signedxml object in sig and an X509CertificateEx object in cert.
bool verified = sig.CheckSignature(cert, true);
还可以使用不带任何参数的CheckSignature方法来验证签名,但是它不会同时验证X.509证书(如果使用了一个这样的证书对xml进行签名)。作为一个单独的步骤,您必须根据KeyInfo重新创建X509CertificateEx对象并对其进行验证。有关出于测试目地创建X.509证书的更多信息,请参阅.NET Framework SDK文档中的证书创建工具(Makecert.exe)主题。
.NET Framework 2.0向签名引擎中添加了几个新的转换和规范化算法。这些算法包括Exclusive C14N规范化算法、xml解密转换和LTA转换。图14包含.NET Framework 2.0签名引擎中提供的转换和规范化算法的完整表格。
Class | Description |
xmlDecryptionTransform | Decrypts encrypted xml |
xmlDsigBase64Transform | Decodes base64 encoded data |
xmlDsigC14NTransform | Performs C14N canonicalization (see http://www.w3.org/TR/xml-c14n for more information) |
xmlDsigEnvelopedSignatureTransform | Removes an enveloped signature from a document |
xmlDsigExcC14NTransform | Performs exclusive C14N canonicalization (see http://www.w3.org/TR/2002/REC-xml-exc-c14n-20020718 for more information) |
xmlDsigXPathTransform | Applies an XPath filter to the input xml |
xmlDsigXsltTransform | Applies an XSLT transform to the input xml |
xmlLicenseTransform | Implements the LTA transform |
xmlDsigC14NWithCommentsTransform | Performs C14N canonicalization, but leaves comments in the canonicalized xml |
xmlDsigExcC14NWithCommentsTransform | Performs exclusive C14N canonicalization, but leaves comments in the canonicalized xml |
图14 转换和规范化算法库
我们在这里讨论了xml签名标准的基础知识以及它在.NET Framework 1.x中是如何实现的。我们还讨论了.NET Framework 2.0中的一些新功能,包括对xml加密标准的支持以及对xml签名的X.509证书集成。通过这些积木技术,可以使用标准与其他应用程序互操作,并且将标准支持内置到您自己的应用程序中。