如何使用C#加密解密XML文档
.NETFramework提供了几种类,可用于对 XML 数据进行加密和解密,以及创建和验证 XML数字签名。这些类提供了维护 XML 数据的保密性和完整性的方法。在这里,我们只涉及如何使用.NETFramework本身提供了的EncryptedXml类进行加密和解密。该类提供了一些方法,能够让用户使用不同的算法进行加密和解密XML。
要使用EncryptedXml类进行加密和解密,我们需要搞清楚两个主题。
1. 加密算法的选取。
2. 如何进行加密。
涉及到加密算法,一般分为两种类型的算法,对称加密算法(SymmetricAlgorithm)和非对称加密算法(AsymmetricAlgorithm)。两种算法的区别在于,使用对称加密算法进行加密和解密,加密过程和解密过程都是使用同一个密钥;对于非对称加密和解密,则是使用一个相互匹配密钥对----公钥和私钥进行加解密。【顺便简单的提及一下非对称加密方式和原理。对于这种类型的加密,加密密钥和解密密钥是相对的说法,如果用加密密钥加密那么只有解密密钥才能恢复,如果用解密密钥加密则只有加密密钥能解密,所以它们被称为密钥对,其中的一个可以在网络上发送、公布,叫做公钥,而另一个则只有密钥对的所有人才持有,叫做私钥,非对称公开密钥系统又叫做公钥系统。非对称加密算法的基本原理是,如果发信方想发送只有收信方才能解读的加密信息,发信方必须首先知道收信方的公钥,然后利用收信方的公钥来加密原文;收信方收到加密密文后,使用自己的私钥才能解密密文。显然,采用不对称加密算法,收发信双方在通信之前,收信方必须将自己早已随机生成的公钥送给发信方,而自己保留私钥。】另外,这两种加密在效率上有比较大的区别,前者效率高,后者效率要相对低一些。所以,加密大量数据,一般都是采用对称加密算法将数据进行加密,为了使加密更加安全,再使用非对称加密算法将加密数据的对称加密密钥进行加密,这样,在进行网络传输时,即保证了加密数据的效率,又保证了加密数据的安全性,当然,在没有将加密的数据进行网络传输时,就没有必要使用非对称加密了,普通的对称加密就可以满足需求。(大家可以仔细Google一下。)
对于这两种加密,常见对称加密算法有DES, AES(又称为Rijndael,其密钥长度分128,192,256位这三种), TripleDES等;非对称加密算法常见的有RSA算法。这几种加密算法对应的类分别是:DESCryptoServiceProvider,RijndaelManaged,TripleDESCryptoServiceProvider以及RSACryptoServiceProvider。前有三种都从SymmetricAlgorithm继承过来,最后一种从AsymmetricAlgorithm继承过来。
好了,基本上关于加密解密,我们已以有一个大致的了解,那么,下面详细的描述一下如何进行加密和解密。
在下面的描述中,我参考了以下文档:
XMLEncryption Syntax and Processing(W3C XML加解密标准文档)
XML加密和数字签名(该主题下含有XML各种类型的加解密)
假定现在有如下XML文档(test.xml),我们要对其进行加密/解密:
加密的大致流程如下:
1. 通过从磁盘加载 XML文件创建 XmlDocument 对象。XmlDocument对象包含要加密的 XML 元素。
2. 在 XmlDocument对象中找到指定的元素,然后创建一个新的 XmlElement 对象来表示要加密的元素。
3. 创建 EncryptedXml类的新实例,并使用它通过指定的加密算法对XmlElement 进行加密。
4. 构造一个 EncryptedData对象,用XML 加密元素的 URL 标识符、EncryptedKey信息等填充它,并将加密数据填充到该结构体里面去。(很重要,很繁琐的步骤)
5. 用 EncryptedData元素替换原始 XmlDocument 对象中的元素。
我们直接拿test.xml来加密,要加密的元素是<number>,加密粒度是该结点,加密数据采用最简单的DES加密,加密后的XML文档变为:
从该加密后的XML里面可以看得出来,<EncryptedData>这个节点里面的内容为加密后的核心内容,但这里面真正被加密的内容在哪?很明显,<CipherValue>中3oIDgOJMW0/Ev3duGiCvsVdDgPzP7X0399xwVKMR8MQUGO1AMTIlFA==这一长串就是加密后的真正数据。那其它部分都是些什么内容?这里,我们需要熟悉一下W3C关于加密的标准。可参见XMLEncryption Syntax and Processing文档。
从加密后的XML里面可以看出,<EncryptedData>节点包含了被加密后的数据和其它信息,W3C为其定义的标准结构如下:
(其中,“?”表示出现0次或1次,“+”表示出现1次或多次,“*”表示0次或多次,空元素标签表示该元素的内容为空)
其中:
1. <EncryptionMethod>用于描述加密方式。一般情况下,需要设置该项。
2. <ds:KeyInfo>用于描述用于加密数据的key。一般情况下,需要设置该项的<KeyName>节点。
3. <EncryptedKey>用于描述将加密数据的key①进行加密的新key②的信息。有点绕,实际上,它出现于这样的情形:当我们使用对称加密key(如:DES加密)对数据进行加密,为了加密更加安全,然后采用另外一种高级加密方式(如:RSA加密),对这个key再进行一次加密,并将这个key加密后的生成的加密数据保存到<EncryptedKey>里面的<CipherData>节点下面。解密时,需要通过给出key②,先将原始加密数据的key①解密出来,再将原始数据进行解密。要注意一点,<EncryptedKey>的数据结构实际上和<EncryptedData>的数据结构是类似的。给出一个简单的例子,如:
这里面<CipherValue>xyzabc</CipherValue>中的数据实际上就是被加密后的key.
MSDN上所给出的DEMO称这种方式为非对称加密方式,但我认为有些不妥,因为加密真正数据的部分并没有采用非对称加密方式,而只是将key①使用了非对称加密。
4. 元素<CipherData>代表加密数据的结构,其中的<CipherValue>代表真正加密后的数据。
对于以上所有结构,在.NET Framework的类里面都有对应的类。
……
(个人感觉,这些类实在是太多了,有点抽象过度的嫌疑。)
另外,我们需要了解一下加密粒度(Encryption Granularity)。加密粒度分两种,一是加密节点内部的数据,另一个加密整个节点。比如,我们可以选择只加密<number>的内容19834209,当然也可以选择加密<number>整个结点。
实际上,通过上面的内容可以看出,加密XML最核心的东西实际上只有两个部分,第一是将原始数据进行加密,第二是构建<EncryptedData>节点内的数据。
OK,整个加密的流程以及相关的背景知识介绍得差不多了。下面通过封装一个加解密的类(GTXXmlCrypt)来说明如何使用EncryptedXml进行加解密。
首先,需要描述我们所使用的EncryptedXml类里面几个重要的方法,这几个方法也是在这个封装类里面用到的,简单描述一下,详细请见MSDN:
1. publicbyte[] EncryptData(XmlElementinputElement,SymmetricAlgorithmsymmetricAlgorithm,boolcontent);
a) 通过给定的对称加密算法,对指定的节点进行加密,content指明是否是加密整个节点还是只加密节点内容。
b) 在该类中,用于加密节点的方法只能通过称加密算法,而不能够通过非对称加密节点,可能他们认为使用非对称加密的实际含义是:先通过对称加密将数据进行加密,再用非对称加密将前者(对称加密的key)进行加密,这种方式才叫非对称加密。
2. publicbyte[] DecryptData(EncryptedDataencryptedData,SymmetricAlgorithmsymmetricAlgorithm);
a) 通过给定的对称解密算法(加密和解密的key是一样的),将数据进行解密,同样,只能使用对称加密方式。
3. publicvoid DecryptDocument();
a) 解密整个文档,但它使用的前提是:在被加密后的文档中,<EncryptedData>节点必须含有<KeyInfo>,而且其中的<KeyName>必须有值。可以通过将要封装的类实验。
4. publicstatic void ReplaceElement(XmlElementinputElement,EncryptedDataencryptedData,boolcontent);
a) 当加密完后,需要用EncryptedData来替换加密的节点。
5. publicstatic byte[] DecryptKey(byte[] keyData, SymmetricAlgorithmsymmetricAlgorithm);
a) 该方法通过对称加密算法解密“加密原数据的密钥”。
6. publicstatic byte[] DecryptKey(byte[] keyData, RSA rsa, bool useOAEP);
a) 该方法通过非对称加密算法解密“加密原数据的密钥”。
我们需要封装的GTXXmlCrypt类有以下需求:
1. 能够加载一个xml文件进行加解密;
2. 能够某个XmlDocument进行加解密;
3. 能够对单个指定的节点进行加解密;
4. 能够指定各种加解密的方式,如指定加密方式为DES,AES,RSA等;
该类的框架如下:(请原谅我只给出了一个代码的架子,也没有用类图的形式表示出来,因为我会将所有代码全部贴上来。)
该类基本上比较简单,我们只重点分析一下加密的核心功能。
加密某个节点分以下步骤:
<!--[if !supportLists]-->1.<!--[endif]-->创建 EncryptedXml 类的新实例,并使用它通过对称密钥对 XmlElement进行加密。EncryptData 方法将加密的元素作为加密字节的数组返回。
<!--[if !supportLists]-->2.<!--[endif]-->构造一个 EncryptedData 对象,然后用 XML加密元素的 URL 标识符填充它。此 URL 标识符使解密方知道 XML 包含一个加密元素。可以使用 XmlEncElementUrl字段指定 URL 标识符。
<!--[if !supportLists]-->3.<!--[endif]-->创建 EncryptionMethod 对象,该对象被初始化为用来生成密钥的加密算法的URL标识符。将 EncryptionMethod 对象传递给 EncryptionMethod属性。
<!--[if !supportLists]-->4.<!--[endif]-->如果用户给加密数据的key设置了一个名称,那就创建一个KeyInfo的对象,并通过KeyInfoName为其指定一个名称。
<!--[if !supportLists]-->5.<!--[endif]-->将加密的元素数据添加到 EncryptedData 对象中。
<!--[if !supportLists]-->6.<!--[endif]-->用EncryptedData 元素替换原始 XmlDocument对象中的元素。
使用RSA加密的整体流程和这个类似,只不过多了一步加密sessionkey(加密原始数据的key)的过程。在这里我就不再赘述了,请直接看源代码。
以下是解密的代码:
解密分两步:
<!--[if !supportLists]-->1. <!--[endif]-->如果给出了keyName和解密密钥,那么直接使用EncryptedXml的DecryptDocument方法。
<!--[if !supportLists]-->2. <!--[endif]-->否则,我们就需要一个节点一个节点的解密了。对于非对称的解密,一般直接使用DecryptDocument方法就可以了。
整个代码如下:
GTXXmlCrypt代码:
测试代码:
主函数:
如果该代码存在问题或者您有好的建议,请第一时间通知我,让我再将这份代码完善一下。
哎,CSDN的这个blog编辑器实在是太令人失望了,格式实在是太难调整了。