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);
XML加密示例
现在,让我们考察一下如何使用.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);
这将产生图13中所示的加密XML。

