如何使用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),我们要对其进行加密/解密:
19834209 02/02/2002
加密的大致流程如下:
1. 通过从磁盘加载 XML文件创建 XmlDocument 对象。XmlDocument对象包含要加密的 XML 元素。
2. 在 XmlDocument对象中找到指定的元素,然后创建一个新的 XmlElement 对象来表示要加密的元素。
3. 创建 EncryptedXml类的新实例,并使用它通过指定的加密算法对XmlElement 进行加密。
4. 构造一个 EncryptedData对象,用XML 加密元素的 URL 标识符、EncryptedKey信息等填充它,并将加密数据填充到该结构体里面去。(很重要,很繁琐的步骤)
5. 用 EncryptedData元素替换原始 XmlDocument 对象中的元素。
我们直接拿test.xml来加密,要加密的元素是,加密粒度是该结点,加密数据采用最简单的DES加密,加密后的XML文档变为:
3oIDgOJMW0/Ev3duGiCvsVdDgPzP7X0399xwVKMR8MQUGO1AMTIlFA== 02/02/2002
从该加密后的XML里面可以看得出来,这个节点里面的内容为加密后的核心内容,但这里面真正被加密的内容在哪?很明显,中3oIDgOJMW0/Ev3duGiCvsVdDgPzP7X0399xwVKMR8MQUGO1AMTIlFA==这一长串就是加密后的真正数据。那其它部分都是些什么内容?这里,我们需要熟悉一下W3C关于加密的标准。可参见XMLEncryption Syntax and Processing文档。
从加密后的XML里面可以看出,节点包含了被加密后的数据和其它信息,W3C为其定义的标准结构如下:
(其中,“?”表示出现0次或1次,“+”表示出现1次或多次,“*”表示0次或多次,空元素标签表示该元素的内容为空)
? ? ? ? ? ? ? ? ? ?
其中:
1. 用于描述加密方式。一般情况下,需要设置该项。
2. 用于描述用于加密数据的key。一般情况下,需要设置该项的节点。
3. 用于描述将加密数据的key①进行加密的新key②的信息。有点绕,实际上,它出现于这样的情形:当我们使用对称加密key(如:DES加密)对数据进行加密,为了加密更加安全,然后采用另外一种高级加密方式(如:RSA加密),对这个key再进行一次加密,并将这个key加密后的生成的加密数据保存到里面的节点下面。解密时,需要通过给出key②,先将原始加密数据的key①解密出来,再将原始数据进行解密。要注意一点,的数据结构实际上和的数据结构是类似的。给出一个简单的例子,如:
John Smith xyzabc Sally Doe
这里面xyzabc中的数据实际上就是被加密后的key.
MSDN上所给出的DEMO称这种方式为非对称加密方式,但我认为有些不妥,因为加密真正数据的部分并没有采用非对称加密方式,而只是将key①使用了非对称加密。
4. 元素代表加密数据的结构,其中的代表真正加密后的数据。
对于以上所有结构,在.NET Framework的类里面都有对应的类。
EncryptedData Class
EncryptionMethod Class
KeyInfo Class
EncryptedKey Class
CipherData Class
CipherReference Class
KeyInfoName Class
……
(个人感觉,这些类实在是太多了,有点抽象过度的嫌疑。)
另外,我们需要了解一下加密粒度(Encryption Granularity)。加密粒度分两种,一是加密节点内部的数据,另一个加密整个节点。比如,我们可以选择只加密的内容19834209,当然也可以选择加密整个结点。
实际上,通过上面的内容可以看出,加密XML最核心的东西实际上只有两个部分,第一是将原始数据进行加密,第二是构建节点内的数据。
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) 解密整个文档,但它使用的前提是:在被加密后的文档中,节点必须含有,而且其中的必须有值。可以通过将要封装的类实验。
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等;
该类的框架如下:(请原谅我只给出了一个代码的架子,也没有用类图的形式表示出来,因为我会将所有代码全部贴上来。)
namespace GTXUtility.Security.Crypt { public class GTXXmlCrypt { // 定义对称加密类型,对于非对称加密类型,我没有给出重新定义一个结构体, // 原因是因为EncryptedXml类对非对称加密只支持RSA,而且RSA也不是用来加密数据的,而是用来加密key的。 public enum SymmetricAlgTypes { DES = 1, AES128, // Rijndael 128 AES192, // Rijndael 192 AES256, // Rijndael 256 TripleDES, } // 该类通过指定的对称加密类型,创建对应的对称加密key, // 实际上,对于创建加密key,完全可以另外写个类,进行操作,而不应该放在该类里面。 public static SymmetricAlgorithm CreateSymmetricAlgorithm(SymmetricAlgTypes salType); // 该类用于创建RSA key, // 实际上,对于创建加密key,完全可以另外写个类,进行操作,而不应该放在该类里面。 public static RSA CreateRSAAlgorithm(String containerName); // 加密指定的文件,该函数内部会调用EncryptXmlDoc函数。 public static bool EncryptXmlFile( String filePath, // 指定需要加密的文件 String elementName, // 需要指定加密的元素 bool bContent, // 是否只加密元素的内容? object key, // 加密密钥,可以是对称的,也可以是非对称的 String keyName); // 指定加密密钥的名称(可选) // 加密指定的XmlDocument对象,该函数内部会循环调用EncryptXmlNode函数 //(该函数分为对称密钥版和非对称密钥版)加密XmlDocument内部所有符合要求的节点。 public static bool EncryptXmlDoc( XmlDocument xmlDoc, // 指定需要加密的XmlDocument对象 String elementName, // 需要指定加密的元素 bool bContent, // 是否只加密元素的内容? object key, // 加密密钥,可以是对称的,也可以是非对称的 String keyName); // 指定加密密钥的名称(可选) // (核心功能)加密指定的节点(对称加密方式) public static bool EncryptXmlNode( XmlElement elementToEncrypt, // 指定需要加密的XmlElement对象(节点) bool bContent, // 是否只加密元素的内容? SymmetricAlgorithm salgKey, // 对称加密密钥 String keyName); // 指定加密密钥的名称(可选) // (核心功能)加密指定的节点(非对称加密方式) public static bool EncryptXmlNode( XmlElement elementToEncrypt, // 指定需要加密的XmlElement对象(节点) bool bContent, // 是否只加密元素的内容? RSA rsaKey, // 对称加密密钥,用于加密sessionKey到节点中去 String keyName, // 指定加密密钥的名称(可选) SymmetricAlgorithm sessionKey); // 用于加密原始数据的对称密钥(注:加密数据只能用对称加密) // 解密指定的文件,该函数内部会调用DecryptXmlDoc函数。 public static bool DecryptXmlFile(String filePath, object key, String keyName); // 解密指定的XmlDocument对象,该函数内部会循环调用DecryptXmlNode函数 public static bool DecryptXmlDoc(XmlDocument xmlDoc, object key, String keyName); // (核心功能)解密指定的节点(对称加密方式和非对称加密方式统统放在一个函数内) public static bool DecryptXmlNode(XmlElement encryptedElement, SymmetricAlgorithm salgKey); // 该函数用于创建节点对象 private static EncryptionMethod CreateEncryptionMethod(object keyAlg, bool isKeyWrapAlgUri, bool isOaep); // 该类用于解密使用RSA加密时,所使用到的sessionkey,内部调用GenerateSyAlgKey函数 private static SymmetricAlgorithm DecryptKey(XmlDocument xmlDoc, object decryptKey); // 该类用于解密使用RSA加密时,所使用到的sessionkey private static SymmetricAlgorithm GenerateSyAlgKey(byte[] decryptedKeyData, EncryptionMethod encMethod); } }
该类基本上比较简单,我们只重点分析一下加密的核心功能。
public static bool EncryptXmlNode( XmlElement elementToEncrypt, bool bContent, SymmetricAlgorithm salgKey, String keyName) { if (null == elementToEncrypt || null == salgKey) { return false; } try { // 1. Create a new instance of the EncryptedXml class and use it // to encrypt the XmlElement with the symmetric key. EncryptedXml eXml = new EncryptedXml(); byte[] encryptedElement = eXml.EncryptData(elementToEncrypt, salgKey, bContent); // 2. Construct an EncryptedData object and populate it // with the desired encryption information. EncryptedData edElement = new EncryptedData(); edElement.Type = EncryptedXml.XmlEncElementUrl; // 3. Create an EncryptionMethod element so that the receiver // knows which algorithm to use for decryption. edElement.EncryptionMethod = CreateEncryptionMethod(salgKey, false, false); // 4. Set the KeyInfo element to specify the name of the key. if (null != keyName && !keyName.Equals(String.Empty)) { // Create a new KeyInfo element. edElement.KeyInfo = new KeyInfo(); // Create a new KeyInfoName element. KeyInfoName kin = new KeyInfoName(); // Specify a name for the key. kin.Value = keyName; // Add the KeyInfoName element. edElement.KeyInfo.AddClause(kin); } // 5. Add the encrypted element data to the EncryptedData object. edElement.CipherData.CipherValue = encryptedElement; // 6. Replace the element from the original XmlDocument // object with the EncryptedData element. EncryptedXml.ReplaceElement(elementToEncrypt, edElement, bContent); return true; } catch (System.Exception ex) { } return false; }
加密某个节点分以下步骤:
1.创建 EncryptedXml 类的新实例,并使用它通过对称密钥对 XmlElement进行加密。EncryptData 方法将加密的元素作为加密字节的数组返回。
2.构造一个 EncryptedData 对象,然后用 XML加密元素的 URL 标识符填充它。此 URL 标识符使解密方知道 XML 包含一个加密元素。可以使用 XmlEncElementUrl字段指定 URL 标识符。
3.创建 EncryptionMethod 对象,该对象被初始化为用来生成密钥的加密算法的URL标识符。将 EncryptionMethod 对象传递给 EncryptionMethod属性。
4.如果用户给加密数据的key设置了一个名称,那就创建一个KeyInfo的对象,并通过KeyInfoName为其指定一个名称。
5.将加密的元素数据添加到 EncryptedData 对象中。
6.用EncryptedData 元素替换原始 XmlDocument对象中的元素。
使用RSA加密的整体流程和这个类似,只不过多了一步加密sessionkey(加密原始数据的key)的过程。在这里我就不再赘述了,请直接看源代码。
以下是解密的代码:
public static bool DecryptXmlDoc(XmlDocument xmlDoc, object key, String keyName) { try { // First, we try to decrypt xml document with EncryptedXml.DecryptDocument() method, // this method decrypts all elements of the XML document, but its precondition // is that the elements has been specified 's , if this element // hasn't been specified, it will failed and throw a CryptographicException exception. // Second, if we hasn't specified key name, we can only decrypt the document with SymmetricAlgorithm // key, this is restricted to the API of EncryptedXml class. if (null != keyName && !keyName.Equals(String.Empty)) { // Create a new EncryptedXml object. EncryptedXml exml = new EncryptedXml(xmlDoc); // Add a key-name mapping. // This method can only decrypt documents that present the specified key name. exml.AddKeyNameMapping(keyName, key); // Decrypt the element. exml.DecryptDocument(); return true; } else { SymmetricAlgorithm salgKey = null; RSA rsaKey = null; if (key is SymmetricAlgorithm) { salgKey = key as SymmetricAlgorithm; } else if (key is RSA) { rsaKey = key as RSA; salgKey = DecryptKey(xmlDoc, key); } else { return false; } XmlNodeList nodeList = xmlDoc.GetElementsByTagName("EncryptedData"); int nCount = nodeList.Count; for (int ix = 0; ix < nCount; ++ix) { XmlElement encryptedElement = nodeList[0] as XmlElement; if (null != salgKey) { if (!DecryptXmlNode(encryptedElement, salgKey)) { return false; } } } return true; } } catch (System.Exception ex) { } return false;
解密分两步:
1. 如果给出了keyName和解密密钥,那么直接使用EncryptedXml的DecryptDocument方法。
2. 否则,我们就需要一个节点一个节点的解密了。对于非对称的解密,一般直接使用DecryptDocument方法就可以了。
整个代码如下:
GTXXmlCrypt代码:
/*! * @file GTXXmlCrypt.cs * * @brief This file implements the class of GTXXmlCrypt. * This class is used to crypt or decrypt xml file. * * Copyright (C) 2011, GTX. * * @author Liu Lin * @date 2011/6/19 */ using System; using System.IO; using System.Security.AccessControl; using System.Security.Cryptography; using System.Security.Cryptography.Xml; using System.Security.Principal; using System.Xml; namespace GTXUtility.Security.Crypt { /// /// This class is used to encrypt xml file with Symmetric Encryption Algorithm (such as /// DES, AES, TripleDES and so on) and Asymmetric Encryption Algorithm (such as RSA). /// /// /// Expressed in shorthand form, the EncryptedData element has the following structure: /// (where "?" denotes zero or one occurrence; /// "+" denotes one or more occurrences; /// "*" denotes zero or more occurrences; /// and the empty element tag means the element must be empty ) /// /// ? /// /// ? /// ? /// ? /// ? /// ? /// ? /// /// ? /// ? /// /// ? /// /// The is the same with the . /// To get some more informations, please attach the following references. /// http://www.w3.org/TR/xmlenc-core/ /// http://msdn.microsoft.com/zh-cn/library/system.security.cryptography.xml.encryptedxml.aspx /// public class GTXXmlCrypt { /// /// Defines Symmetric Encryption Algorithm types. /// public enum SymmetricAlgTypes { DES = 1, AES128, // Rijndael 128 AES192, // Rijndael 192 AES256, // Rijndael 256 TripleDES, } /// /// Create a Symmetric Encryption Algorithm object. /// /// Specified Symmetric Encryption Algorithm type. /// If create success, returns the Symmetric Encryption Algorithm object. /// Otherwise, return null object. public static SymmetricAlgorithm CreateSymmetricAlgorithm(SymmetricAlgTypes salType) { SymmetricAlgorithm symAlg = null; switch (salType) { case SymmetricAlgTypes.DES: symAlg = SymmetricAlgorithm.Create("DES"); break; case SymmetricAlgTypes.AES128: symAlg = SymmetricAlgorithm.Create("Rijndael"); symAlg.KeySize = 128; break; case SymmetricAlgTypes.AES192: symAlg = SymmetricAlgorithm.Create("Rijndael"); symAlg.KeySize = 192; break; case SymmetricAlgTypes.AES256: symAlg = SymmetricAlgorithm.Create("Rijndael"); symAlg.KeySize = 256; break; case SymmetricAlgTypes.TripleDES: symAlg = SymmetricAlgorithm.Create("TripleDES"); break; default: break; } return symAlg; } /// /// This method has two use. /// First, create a RSA encryption algorithm object when first use it, it'll /// stroe the RSA key into key container which specified by the paramter. /// Second, this method can get the RSA key from container when use for second time. /// The container name must be the same with the first. /// /// The key container name. /// Returns the instance of RSA. /// /// Microsoft recommends to store asymmetric keys in a key container. /// http://msdn.microsoft.com/zh-cn/library/tswxhw92(VS.80).aspx /// public static RSA CreateRSAAlgorithm(String containerName) { RSACryptoServiceProvider rsaKey = null; try { // Create a new CspParameters object to specify a key container. CspParameters cspParams = new CspParameters(); cspParams.KeyContainerName = containerName; cspParams.Flags = CspProviderFlags.UseMachineKeyStore; // Add the key's access privilege CryptoKeySecurity keySecurity = new CryptoKeySecurity(); SecurityIdentifier si = new SecurityIdentifier(WellKnownSidType.LocalSid/*WorldSid*/, null); keySecurity.AddAccessRule(new CryptoKeyAccessRule(si, CryptoKeyRights.FullControl, AccessControlType.Allow)); cspParams.CryptoKeySecurity = keySecurity; // Create a new RSA key and save it in the container. This key will encrypt // a symmetric key, which will then be encrypted in the XML document. rsaKey = new RSACryptoServiceProvider(cspParams); } catch (System.Exception ex) { } return rsaKey; } /// /// Encrypt a xml file's elements by specified key. /// /// The path of xml file which needs to be encrypted. /// The element (node) in xml file which needs to be encrypted. /// Whether to encrypt the element content or not. true to /// encrypt only the contents of the element; false to encrypt the entire element. /// The specified encryption key. /// The key name which used to display the in encrypted xml. /// It can be empty or null. /// If encrypt success, return true, otherwise, return false. /// After encryption success, it'll save the new data into xml file. public static bool EncryptXmlFile( String filePath, String elementName, bool bContent, object key, String keyName) { if (File.Exists(filePath) && Path.GetExtension(filePath).ToLower().Equals(".xml") && null != elementName && !elementName.Equals(String.Empty) && null != key) { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.PreserveWhitespace = true; xmlDoc.Load(filePath); if (EncryptXmlDoc(xmlDoc, elementName, bContent, key, keyName)) { xmlDoc.Save(filePath); return true; } return false; } return false; } /// /// Encrypt a xml document's elements by specified key. /// /// The XmlDocument object which needs to be encrypted. /// The element (node) in xml file which needs to be encrypted. /// Whether to encrypt the element content or not. true to /// encrypt only the contents of the element; false to encrypt the entire element. /// The specified encryption key. /// The key name which used to display the in encrypted xml. /// It can be empty or null. /// If encrypt success, return true, otherwise, return false. public static bool EncryptXmlDoc( XmlDocument xmlDoc, String elementName, bool bContent, object key, String keyName) { SymmetricAlgorithm salgKey = null; RSA rsaKey = null; SymmetricAlgorithm sessionKey = null; // If currnet key is SymmetricAlgorithm, we'll use this key to encrypt elemnet. // Else if currnet key is RSA, we need to create a session key to encrypt elemnet, // and the RSA key is used to encrypt session key. Please pay more attention. if (key is SymmetricAlgorithm) { salgKey = key as SymmetricAlgorithm; } else if (key is RSA) { rsaKey = key as RSA; sessionKey = CreateSymmetricAlgorithm(SymmetricAlgTypes.DES/*AES256*/); } else { return false; } try { XmlNodeList nodeList = xmlDoc.GetElementsByTagName(elementName); int nCount = nodeList.Count; if (0 == nCount) { return false; } for (int ix = 0; ix < nCount; ++ix) { // Please pay more attention about the follows: // If we want to encrypt a node (XmlElement) in node list, the node will be delete // after we encrypt it and the count will be decremented, as a result, each time // we need to encrypt nodeList[0]. // But if we want to encrypt a node's content in node list, the node will NOT be delete // after we encrypt it and the count will NOT be decremented, as a result, each time // we need to encrypt nodeList[ix]. XmlElement elementToEncrypt = bContent ? (nodeList[ix] as XmlElement) : (nodeList[0] as XmlElement); if (null != salgKey) { if (!EncryptXmlNode(elementToEncrypt, bContent, salgKey, keyName)) { return false; } } else if (null != rsaKey) { if (!EncryptXmlNode(elementToEncrypt, bContent, rsaKey, keyName, sessionKey)) { return false; } } } return true; } catch (System.Exception ex) { } finally { // Clear session key. if (null != sessionKey) { sessionKey.Clear(); } } return false; } /// /// Encrypt a xml element (node) by specified Symmetric Algorithm key. /// /// The element which needs to be encrypted. /// Whether to encrypt the element content or not. true to /// encrypt only the contents of the element; false to encrypt the entire element. /// Specified Symmetric Algorithm key. /// The key name which used to display the in encrypted xml. /// It can be empty or null. /// If encrypt success, return true, otherwise, return false. public static bool EncryptXmlNode( XmlElement elementToEncrypt, bool bContent, SymmetricAlgorithm salgKey, String keyName) { if (null == elementToEncrypt || null == salgKey) { return false; } try { // 1. Create a new instance of the EncryptedXml class and use it // to encrypt the XmlElement with the symmetric key. EncryptedXml eXml = new EncryptedXml(); byte[] encryptedElement = eXml.EncryptData(elementToEncrypt, salgKey, bContent); // 2. Construct an EncryptedData object and populate it // with the desired encryption information. EncryptedData edElement = new EncryptedData(); edElement.Type = EncryptedXml.XmlEncElementUrl; // 3. Create an EncryptionMethod element so that the receiver // knows which algorithm to use for decryption. edElement.EncryptionMethod = CreateEncryptionMethod(salgKey, false, false); // 4. Set the KeyInfo element to specify the name of the key. if (null != keyName && !keyName.Equals(String.Empty)) { // Create a new KeyInfo element. edElement.KeyInfo = new KeyInfo(); // Create a new KeyInfoName element. KeyInfoName kin = new KeyInfoName(); // Specify a name for the key. kin.Value = keyName; // Add the KeyInfoName element. edElement.KeyInfo.AddClause(kin); } // 5. Add the encrypted element data to the EncryptedData object. edElement.CipherData.CipherValue = encryptedElement; // 6. Replace the element from the original XmlDocument // object with the EncryptedData element. EncryptedXml.ReplaceElement(elementToEncrypt, edElement, bContent); return true; } catch (System.Exception ex) { } return false; } /// /// Encrypt a xml element (node) by specified RSA key. /// Actually, it w /// /// The element which needs to be encrypted. /// Whether to encrypt the element content or not. true to /// encrypt only the contents of the element; false to encrypt the entire element. /// Specified RSA key, which used to encrypt the session key. /// The key name which used to display the in encrypted xml. /// It can be empty or null. /// The specified Symmetric Algorithm key used to encrypt xml element data. /// If encrypt success, return true, otherwise, return false. /// Actually, I don't think it's a RSA encryption, because the RSA key is only used to /// encrypt the session key, not used to encrypt xml element data. public static bool EncryptXmlNode( XmlElement elementToEncrypt, bool bContent, RSA rsaKey, String keyName, SymmetricAlgorithm sessionKey) { if (null == elementToEncrypt || null == rsaKey) { return false; } try { // 1. Create a new instance of the EncryptedXml class and use it to // encrypt the XmlElement with the a new random symmetric key. // Encrypt the xml's element and get the crypted data. EncryptedXml eXml = new EncryptedXml(); byte[] encryptedElement = eXml.EncryptData(elementToEncrypt, sessionKey, bContent); // 2. Construct an EncryptedData object and populate // it with the desired encryption information. EncryptedData edElement = new EncryptedData(); edElement.Type = EncryptedXml.XmlEncElementUrl; // 3. Create an EncryptionMethod element so that the // receiver knows which algorithm to use for decryption. edElement.EncryptionMethod = CreateEncryptionMethod(sessionKey, false, false); // 4. Encrypt the session key and add it to an EncryptedKey element. EncryptedKey ek = new EncryptedKey(); byte[] encryptedKey = EncryptedXml.EncryptKey(sessionKey.Key, rsaKey, false); ek.CipherData = new CipherData(encryptedKey); ek.EncryptionMethod = CreateEncryptionMethod(rsaKey, false, false); // 5. Set the KeyInfo element to specify the name of the RSA key. if (null != keyName && !keyName.Equals(String.Empty)) { // Create a new KeyInfo element. edElement.KeyInfo = new KeyInfo(); // Create a new KeyInfoName element. KeyInfoName kin = new KeyInfoName(); // Specify a name for the key. kin.Value = keyName; // Add the KeyInfoName element to the EncryptedKey object. ek.KeyInfo.AddClause(kin); } // 6. Add the encrypted key to the EncryptedData object. edElement.KeyInfo.AddClause(new KeyInfoEncryptedKey(ek)); // Add the encrypted element data to the EncryptedData object. edElement.CipherData.CipherValue = encryptedElement; // 7. Replace the element from the original XmlDocument // object with the EncryptedData element. EncryptedXml.ReplaceElement(elementToEncrypt, edElement, bContent); return true; } catch (System.Exception ex) { } return false; } /// /// Encrypt a xml file's elements by specified key. /// /// The path of xml file which needs to be encrypted. /// The element (node) in xml file which needs to be encrypted. /// Whether to encrypt the element content or not. true to /// encrypt only the contents of the element; false to encrypt the entire element. /// The specified encryption key. /// The key name which used to display the in encrypted xml. /// It can be empty or null. /// If encrypt success, return true, otherwise, return false. /// After encryption success, it'll save the new data into xml file. /// /// Decrypt a xml file's elements by specified key. /// /// The path of xml file which needs to be decrypted. /// The specified decryption key which used to encrypt before. /// The key name which used to display the in encrypted xml. /// It can be empty or null. /// If decrypt success, return true, otherwise, return false. /// After decryption success, it'll save the new data into xml file. public static bool DecryptXmlFile(String filePath, object key, String keyName) { if (File.Exists(filePath) && Path.GetExtension(filePath).ToLower().Equals(".xml") && null != key) { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.PreserveWhitespace = true; xmlDoc.Load(filePath); if (DecryptXmlDoc(xmlDoc, key, keyName)) { xmlDoc.Save(filePath); return true; } return false; } return false; } /// /// Decrypt a xml document's elements by specified key. /// /// The XmlDocument object which needs to be decrypted. /// The specified decryption key which used to encrypt before. /// The key name which used to display the in encrypted xml. /// It can be empty or null. /// If decrypt success, return true, otherwise, return false. public static bool DecryptXmlDoc(XmlDocument xmlDoc, object key, String keyName) { try { // First, we try to decrypt xml document with EncryptedXml.DecryptDocument() method, // this method decrypts all elements of the XML document, but its precondition // is that the elements has been specified 's , if this element // hasn't been specified, it will failed and throw a CryptographicException exception. // Second, if we hasn't specified key name, we can only decrypt the document with SymmetricAlgorithm // key, this is restricted to the API of EncryptedXml class. if (null != keyName && !keyName.Equals(String.Empty)) { // Create a new EncryptedXml object. EncryptedXml exml = new EncryptedXml(xmlDoc); // Add a key-name mapping. // This method can only decrypt documents that present the specified key name. exml.AddKeyNameMapping(keyName, key); // Decrypt the element. exml.DecryptDocument(); return true; } else { SymmetricAlgorithm salgKey = null; RSA rsaKey = null; if (key is SymmetricAlgorithm) { salgKey = key as SymmetricAlgorithm; } else if (key is RSA) { rsaKey = key as RSA; salgKey = DecryptKey(xmlDoc, key); } else { return false; } XmlNodeList nodeList = xmlDoc.GetElementsByTagName("EncryptedData"); int nCount = nodeList.Count; for (int ix = 0; ix < nCount; ++ix) { XmlElement encryptedElement = nodeList[0] as XmlElement; if (null != salgKey) { if (!DecryptXmlNode(encryptedElement, salgKey)) { return false; } } } return true; } } catch (System.Exception ex) { } return false; } /// /// Decrypt xml element (node) by specified Symmetric Algorithm key. /// /// The xml element which needs to be decrypted. /// Specified Symmetric Algorithm key. /// If decrypt success, return true, otherwise, return false. /// We can only use Symmetric Algorithm key to decrypt xml element data but RSA, /// because this is restricted to the API of EncryptedXml class. Please note it on MSDN. public static bool DecryptXmlNode(XmlElement encryptedElement, SymmetricAlgorithm salgKey) { try { // 1. Construct an EncryptedData object and populate it // with the desired encryption information. EncryptedData edElement = new EncryptedData(); edElement.LoadXml(encryptedElement); // 2. Create a new instance of the EncryptedXml class and use it // to encrypt the XmlElement with the symmetric key. EncryptedXml eXml = new EncryptedXml(); byte[] rgbOutput = eXml.DecryptData(edElement, salgKey); // 3. Replace the encryptedData element with the plaintext XML element. eXml.ReplaceData(encryptedElement, rgbOutput); return true; } catch (System.Exception ex) { } return false; } /// /// Retrieve the EncryptionMethod object which contain in EncryptionData. /// /// Specified encryption algorithm object. /// Whether the namespace Uniform Resource Identifier (URI) /// for Wrap algorithm or not. This can be used only the keyAlg is a Symmetric Encryption Algorithm. /// If used this, the isOaep will be ignored. /// Whether to use RSA Optimal Asymmetric Encryption Padding (OAEP) /// encryption algorithm. This can be used only the keyAlg is a RSA Asymmetric. /// If used this, the isKeyWrapAlgUri will be ignored. /// Return the EncryptionMethod object. private static EncryptionMethod CreateEncryptionMethod(object keyAlg, bool isKeyWrapAlgUri, bool isOaep) { EncryptionMethod encMethod = new EncryptionMethod(); String URI = String.Empty; if (keyAlg is DES) { encMethod.KeyAlgorithm = EncryptedXml.XmlEncDESUrl; //encMethod.KeySize = 0; [exception, why?] } else if (keyAlg is Rijndael) { switch ((keyAlg as Rijndael).KeySize) { case 128: encMethod.KeyAlgorithm = isKeyWrapAlgUri ? EncryptedXml.XmlEncAES128KeyWrapUrl : EncryptedXml.XmlEncAES128Url; encMethod.KeySize = 128; break; case 192: encMethod.KeyAlgorithm = isKeyWrapAlgUri ? EncryptedXml.XmlEncAES192KeyWrapUrl : EncryptedXml.XmlEncAES192Url; encMethod.KeySize = 192; break; case 256: encMethod.KeyAlgorithm = isKeyWrapAlgUri ? EncryptedXml.XmlEncAES256KeyWrapUrl : EncryptedXml.XmlEncAES256Url; encMethod.KeySize = 256; break; default: break; } } else if (keyAlg is TripleDES) { encMethod.KeyAlgorithm = isKeyWrapAlgUri ? EncryptedXml.XmlEncTripleDESKeyWrapUrl : EncryptedXml.XmlEncTripleDESUrl; //encMethod.KeySize = 0; [exception, why?] } else if (keyAlg is RSA) { encMethod.KeyAlgorithm = isOaep ? EncryptedXml.XmlEncRSAOAEPUrl : EncryptedXml.XmlEncRSA15Url; } else { // Do nothing } return encMethod; } /// /// Decrypt encryption key (in node of ) which used to encrypt xml element. /// /// The encrypted XmlDocument object. /// The key which used to decrypt encryption key. /// The key must be Triple DES Key Wrap algorithm or the Advanced Encryption Standard /// (AES) Key Wrap algorithm (also called Rijndael) or RSA key. /// Return Symmetric Algorithm key object whihc used to decrypt xml element. private static SymmetricAlgorithm DecryptKey(XmlDocument xmlDoc, object decryptKey) { XmlNodeList encKeyNodeList = xmlDoc.GetElementsByTagName("EncryptedKey"); XmlNodeList encDataNodeList = xmlDoc.GetElementsByTagName("EncryptedData"); if (encDataNodeList.Count > 0 && encKeyNodeList.Count > 0) { XmlElement encryptedKey = encKeyNodeList[0] as XmlElement; EncryptedKey ek = new EncryptedKey(); ek.LoadXml(encryptedKey); byte[] decryptedData = null; //if (decryptKey is SymmetricAlgorithm) if (decryptKey is Rijndael || decryptKey is TripleDES) { decryptedData = EncryptedXml.DecryptKey(ek.CipherData.CipherValue, (SymmetricAlgorithm)decryptKey); } else if (decryptKey is RSA) { // kek is an RSA key: get fOAEP from the algorithm, default to false bool fOAEP = (ek.EncryptionMethod != null && ek.EncryptionMethod.KeyAlgorithm == EncryptedXml.XmlEncRSAOAEPUrl); decryptedData = EncryptedXml.DecryptKey(ek.CipherData.CipherValue, (RSA)decryptKey, fOAEP); } else { // The key (decryptKey) used to decrypt encryption data key is not the Triple DES Key Wrap algorithm // or the Advanced Encryption Standard (AES) Key Wrap algorithm (also called Rijndael). return null; } XmlElement encryptDataXml = encDataNodeList[0] as XmlElement; EncryptedData encryptData = new EncryptedData(); encryptData.LoadXml(encryptDataXml); return GenerateSyAlgKey(decryptedData, encryptData.EncryptionMethod); } return null; } /// /// Generate a Symmetric Algorithm key by specified key data and EncryptionMethod object. /// /// Key data. /// The EncryptionMethod object. /// Return Symmetric Algorithm key object whihc used to decrypt xml element. private static SymmetricAlgorithm GenerateSyAlgKey(byte[] decryptedKeyData, EncryptionMethod encMethod) { if (null == encMethod || null == decryptedKeyData || 0 == decryptedKeyData.Length) { return null; } String keyAlg = encMethod.KeyAlgorithm; int keySize = encMethod.KeySize; SymmetricAlgorithm symAlg = null; if (keyAlg.Equals(EncryptedXml.XmlEncDESUrl)) { symAlg = SymmetricAlgorithm.Create("DES"); symAlg.Key = decryptedKeyData; } else if (keyAlg.Equals(EncryptedXml.XmlEncAES128Url) || keyAlg.Equals(EncryptedXml.XmlEncAES128KeyWrapUrl)) { symAlg = SymmetricAlgorithm.Create("Rijndael"); symAlg.KeySize = 128; symAlg.Key = decryptedKeyData; } else if (keyAlg.Equals(EncryptedXml.XmlEncAES192Url) || keyAlg.Equals(EncryptedXml.XmlEncAES192KeyWrapUrl)) { symAlg = SymmetricAlgorithm.Create("Rijndael"); symAlg.KeySize = 192; symAlg.Key = decryptedKeyData; } else if (keyAlg.Equals(EncryptedXml.XmlEncAES256Url) || keyAlg.Equals(EncryptedXml.XmlEncAES256KeyWrapUrl)) { symAlg = SymmetricAlgorithm.Create("Rijndael"); symAlg.KeySize = 256; symAlg.Key = decryptedKeyData; } else if (keyAlg.Equals(EncryptedXml.XmlEncTripleDESUrl) || keyAlg.Equals(EncryptedXml.XmlEncTripleDESKeyWrapUrl)) { symAlg = SymmetricAlgorithm.Create("TripleDES"); symAlg.Key = decryptedKeyData; } else { } return symAlg; } } }
测试代码:
using System; using System.IO; using System.Security.Cryptography; using System.Text; using System.Xml; using GTXUtility.Security.Crypt; namespace TestUtility { public class TestXmlCrypt { enum PrintTypes { PrintOriginal, PrintEncrypt, PrintDecrypt } public static void UseDemo() { Console.WriteLine("Test Xml Crypt begin..."); Console.WriteLine("Input file path: (You can drag a file into console without input file path)"); String filePath = Console.ReadLine(); if (File.Exists(filePath)) { PrintXml(filePath, PrintTypes.PrintOriginal); Console.WriteLine("Select Symmetric/Asymmetric(RSA) Algorithm, 0: Symmetric, 1: Asymmetric (Default is Symmetric)"); String alg = Console.ReadLine(); int nAlg = alg.Equals("1") ? 1 : 0; switch (nAlg) { case 0: TestSymmetricAlgorithm(filePath); break; case 1: TestAsymmetricAlgorithm(filePath); break; default: break; } } Console.WriteLine("Test Xml Crypt end..."); } private static void TestSymmetricAlgorithm(String filePath) { Console.WriteLine("Select the encrypt/decrypt algorithm: (Default is DES)"); GTXXmlCrypt.SymmetricAlgTypes salTypeTemp = GTXXmlCrypt.SymmetricAlgTypes.DES; for (; salTypeTemp <= GTXXmlCrypt.SymmetricAlgTypes.TripleDES; ++salTypeTemp) { Console.WriteLine("{0} is {1}", (int)salTypeTemp, salTypeTemp.ToString()); } // Read the symmetric algorithm command String algString = Console.ReadLine(); GTXXmlCrypt.SymmetricAlgTypes salType = GTXXmlCrypt.SymmetricAlgTypes.DES; try { salType = (GTXXmlCrypt.SymmetricAlgTypes)Convert.ToInt32(algString); if (salType <= GTXXmlCrypt.SymmetricAlgTypes.DES && salType >= GTXXmlCrypt.SymmetricAlgTypes.TripleDES) { salType = GTXXmlCrypt.SymmetricAlgTypes.DES; } } catch (System.Exception ex) { salType = GTXXmlCrypt.SymmetricAlgTypes.DES; } // Read the node which we want to encrypt/decrypt Console.WriteLine("Input the XML element node:"); String elementName = Console.ReadLine(); Console.WriteLine("Encrypt node element or its content, 0: Element, 1: Only content (Default is Element)"); String content = Console.ReadLine(); bool bContent = content.Equals("1") ? true : false; SymmetricAlgorithm sal = GTXXmlCrypt.CreateSymmetricAlgorithm(salType); if (GTXXmlCrypt.EncryptXmlFile(filePath, elementName, bContent, sal, "abc")) { // Print the encrypted XML to console Console.WriteLine("Encrypt XML with {0} algorithm Succeed!", salType); PrintXml(filePath, PrintTypes.PrintEncrypt); if (GTXXmlCrypt.DecryptXmlFile(filePath, sal, /*"abc"*/"")) { // Print the decrypted XML to console Console.WriteLine("Decrypt XML with {0} algorithm Succeed!", salType); PrintXml(filePath, PrintTypes.PrintDecrypt); } else { Console.WriteLine("Decrypt with {0} algorithm Failed!", salType); } } else { Console.WriteLine("Encrypt with {0} algorithm Failed!", salType); } } private static void TestAsymmetricAlgorithm(String filePath) { String keyContainerName = "rsakey container"; RSA rsaKey = GTXXmlCrypt.CreateRSAAlgorithm(keyContainerName); try { // Read the node which we want to encrypt/decrypt Console.WriteLine("Input the XML element node:"); String elementName = Console.ReadLine(); Console.WriteLine("Encrypt node element or its content, 0: Element, 1: Only content (Default is Element)"); String content = Console.ReadLine(); bool bContent = content.Equals("1") ? true : false; if (GTXXmlCrypt.EncryptXmlFile(filePath, elementName, bContent, rsaKey, ""/*"rsakey"*/)) { // Print the encrypted XML to console Console.WriteLine("Encrypt XML with RSA algorithm Succeed!"); PrintXml(filePath, PrintTypes.PrintEncrypt); if (GTXXmlCrypt.DecryptXmlFile(filePath, rsaKey, ""/*"rsakey"*/)) { PrintXml(filePath, PrintTypes.PrintDecrypt); Console.WriteLine("Decrypt XML with RSA algorithm Succeed!"); } else { Console.WriteLine("Decrypt XML with RSA algorithm Failed!"); } } else { Console.WriteLine("Encrypt xml with RSA algorithm Failed!"); } } catch (System.Exception ex) { Console.WriteLine(ex.Message); } finally { rsaKey.Clear(); } } static void PrintXml(String filePath, PrintTypes printType) { String format = String.Empty; switch (printType) { case PrintTypes.PrintOriginal: format = "■ Original file:/n{0}"; break; case PrintTypes.PrintEncrypt: format = "■ Encryptd file:/n{0}"; break; case PrintTypes.PrintDecrypt: format = "■ Decrypted file:/n{0}"; break; } XmlDocument doc = new XmlDocument(); //doc.PreserveWhitespace = true; doc.Load(filePath); XmlWriterSettings xmlSetting = new XmlWriterSettings(); xmlSetting.Indent = true; xmlSetting.IndentChars = "/t"; xmlSetting.Encoding = Encoding.UTF8; MemoryStream stream = new MemoryStream(); XmlWriter writer = XmlWriter.Create(stream, xmlSetting); doc.Save(writer); doc.Save(filePath); // Reload the file and update the doc.InnerXml property. doc.PreserveWhitespace = true; doc.Load(filePath); Console.WriteLine(format, doc.InnerXml); } } }
主函数:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TestUtility { class Program { static void Main(string[] args) { TestXmlCrypt.UseDemo(); } } }
如果该代码存在问题或者您有好的建议,请第一时间通知我,让我再将这份代码完善一下。
哎,CSDN的这个blog编辑器实在是太令人失望了,格式实在是太难调整了。