How to encrypt multiple copies of an XML element within a single XML document
I have an XML encryption routine based on the MSDN article http://msdn.microsoft.com/en-us/library/sb7w85t6.aspx
My code is here
public static void Encrypt(XmlDocument Doc, string ElementName, SymmetricAlgorithm Key)
{
////////////////////////////////////////////////
// Check the arguments.
////////////////////////////////////////////////
if (Doc == null || (Doc.ToString() == string.Empty))
throw new ArgumentNullException("The XML document was not given or it is empty");
if (ElementName == null || ElementName == string.Empty)
throw new ArgumentNullException("The XML element was not given or it is empty");
if (Key == null || Key.ToString() == string.Empty)
throw new ArgumentNullException("The encryption algorithm was not given or it is empty");
try
{
////////////////////////////////////////////////
// Find the specified element in the XmlDocument
// object and create a new XmlElemnt object.
////////////////////////////////////////////////
foreach (XmlElement elementToEncrypt in Doc.GetElementsByTagName("schema"))
{
//XmlElement elementToEncrypt = Doc.GetElementsByTagName("schema")[0] as XmlElement;
// Throw an XmlException if the element was not found.
if (elementToEncrypt == null || elementToEncrypt.ToString() == string.Empty)
{
throw new XmlException("The specified element was not found or it is empty");
}
//////////////////////////////////////////////////
// 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, Key, false);
////////////////////////////////////////////////
// Construct an EncryptedData object and populate
// it with the desired encryption information.
////////////////////////////////////////////////
EncryptedData edElement = new EncryptedData();
edElemen开发者_StackOverflowt.Type = EncryptedXml.XmlEncElementUrl;
////////////////////////////////////////////////
// Create an EncryptionMethod element so that the
// receiver knows which algorithm to use for decryption.
// Determine what kind of algorithm is being used and
// supply the appropriate URL to the EncryptionMethod element.
////////////////////////////////////////////////
string encryptionMethod = null;
if (Key is TripleDES)
{
encryptionMethod = EncryptedXml.XmlEncTripleDESUrl;
}
else if (Key is DES)
{
encryptionMethod = EncryptedXml.XmlEncDESUrl;
}
if (Key is Rijndael)
{
switch (Key.KeySize)
{
case 128:
encryptionMethod = EncryptedXml.XmlEncAES128Url;
break;
case 192:
encryptionMethod = EncryptedXml.XmlEncAES192Url;
break;
case 256:
encryptionMethod = EncryptedXml.XmlEncAES256Url;
break;
}
}
else
{
// Throw an exception if the transform is not in the previous categories
throw new CryptographicException("The specified algorithm is not supported for XML Encryption.");
}
edElement.EncryptionMethod = new EncryptionMethod(encryptionMethod);
////////////////////////////////////////////////
// Add the encrypted element data to the
// EncryptedData object.
////////////////////////////////////////////////
edElement.CipherData.CipherValue = encryptedElement;
////////////////////////////////////////////////////
// Replace the element from the original XmlDocument
// object with the EncryptedData element.
////////////////////////////////////////////////////
EncryptedXml.ReplaceElement(elementToEncrypt, edElement, false);
}
}
catch (XmlException xexc)
{
throw new Exception(xexc.Message);
}
catch (Exception exc)
{
throw new XmlException("The XML document could not be used to encrypt the elements given: " + exc.Message);
}
finally
{
////////////////////////////////////////////////////
// Replace the XML file with the encrypted version
////////////////////////////////////////////////////
Doc.Save("testMIPS.config");
}
}
This works for the first "schema" element it finds and I can see the Doc.XML string showing the first element having been encrypted.
But when the loop traverses again to supposedly encrypt the next "schema" element, the program throws this exception...
"The element list has changed. The enumeration operation failed to continue."
But I have not actually saved the Doc.XML file as yet.
Why is this happening?
Thank you in advance for your time.
Doc.GetElementsByTagName("schema")
Returns iterator to node which is also a lazy evaluator. When change the XmlElement
the iterate become invalid bcz you have changed the data on which the iterator was working. Try to add resulting element from GetElementsByTagName()
in some List<XmlElement>
and than enumerate that list and encrypt each element.
More info added
Its very simple if you have list
List<int> n =new List<int>(new int{1,2,3});
//now if iterate it
foreach(int i in n){
n.Add(i)
}
the loop is changing the list that its enumerating so it will give a error similar to one given in your case. You can solve the problem by doing
foreach(int i in n.ToArray()){
n.Add(i)
}
since in this case a still image of n is capture before loop and i can freey alter it inside the loop. Your loop iterating over a tree while you change while you enumerating it. That not right and will give you exception. You have to first collect all the element you want to change in a List and then enumerate this list and encrypt them. This will update the original xml document as well.
I had the same problem and came up with the code below. It works for me.
test.xml:
<root>
<creditcard id="1">
<number>19834209</number>
<expiry>02/02/2002</expiry>
</creditcard>
<creditcard id="2">
<number>34834210</number>
<expiry>03/03/2003</expiry>
</creditcard>
<creditcard id="3">
<number>34834211</number>
<expiry>04/04/2003</expiry>
</creditcard>
</root>
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
namespace EncryptXmlTest1
{
class Program
{
static void Main(string[] args)
{
RijndaelManaged key = null;
try
{
// Create a new Rijndael key.
key = new RijndaelManaged();
// Load an XML document.
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.PreserveWhitespace = true;
xmlDoc.Load("test.xml");
// Encrypt the "creditcard" element.
//Encrypt(xmlDoc, "creditcard", key);
EncryptMultiple(xmlDoc, "creditcard", key);
Console.WriteLine("The element was encrypted");
Console.WriteLine(xmlDoc.InnerXml);
//Decrypt(xmlDoc, key);
DecryptMultiple(xmlDoc, key);
Console.WriteLine("The element was decrypted");
Console.WriteLine(xmlDoc.InnerXml);
Console.ReadKey();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
finally
{
// Clear the key.
if (key != null)
{
key.Clear();
}
}
}
public static void Encrypt(XmlDocument Doc, string ElementName, SymmetricAlgorithm Key)
{
// Check the arguments.
if (Doc == null)
throw new ArgumentNullException("Doc");
if (ElementName == null)
throw new ArgumentNullException("ElementToEncrypt");
if (Key == null)
throw new ArgumentNullException("Alg");
////////////////////////////////////////////////
// Find the specified element in the XmlDocument
// object and create a new XmlElemnt object.
////////////////////////////////////////////////
XmlElement elementToEncrypt = Doc.GetElementsByTagName(ElementName)[0] as XmlElement;
XmlNodeList elementsToEncrypt = Doc.GetElementsByTagName(ElementName);
// Throw an XmlException if the element was not found.
if (elementsToEncrypt == null)
{
throw new XmlException("The specified elements were not found");
}
//////////////////////////////////////////////////
// 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, Key, false);
////////////////////////////////////////////////
// Construct an EncryptedData object and populate
// it with the desired encryption information.
////////////////////////////////////////////////
EncryptedData edElement = new EncryptedData();
edElement.Type = EncryptedXml.XmlEncElementUrl;
// Create an EncryptionMethod element so that the
// receiver knows which algorithm to use for decryption.
// Determine what kind of algorithm is being used and
// supply the appropriate URL to the EncryptionMethod element.
string encryptionMethod = null;
if (Key is TripleDES)
{
encryptionMethod = EncryptedXml.XmlEncTripleDESUrl;
}
else if (Key is DES)
{
encryptionMethod = EncryptedXml.XmlEncDESUrl;
}
if (Key is Rijndael)
{
switch (Key.KeySize)
{
case 128:
encryptionMethod = EncryptedXml.XmlEncAES128Url;
break;
case 192:
encryptionMethod = EncryptedXml.XmlEncAES192Url;
break;
case 256:
encryptionMethod = EncryptedXml.XmlEncAES256Url;
break;
}
}
else
{
// Throw an exception if the transform is not in the previous categories
throw new CryptographicException("The specified algorithm is not supported for XML Encryption.");
}
edElement.EncryptionMethod = new EncryptionMethod(encryptionMethod);
// Add the encrypted element data to the
// EncryptedData object.
edElement.CipherData.CipherValue = encryptedElement;
////////////////////////////////////////////////////
// Replace the element from the original XmlDocument
// object with the EncryptedData element.
////////////////////////////////////////////////////
EncryptedXml.ReplaceElement(elementToEncrypt, edElement, false);
}
public static void EncryptMultiple(XmlDocument Doc, string ElementName, SymmetricAlgorithm Key)
{
// Check the arguments.
if (Doc == null)
throw new ArgumentNullException("Doc");
if (ElementName == null)
throw new ArgumentNullException("ElementToEncrypt");
if (Key == null)
throw new ArgumentNullException("Alg");
XmlNodeList elementsToEncrypt = Doc.GetElementsByTagName(ElementName);
if (elementsToEncrypt == null)
{
throw new XmlException("The specified elements were not found");
}
//XmlNodeList elementsToEncryptCloned = Doc.GetElementsByTagName(ElementName);
List<string> ids = new List<string>();
for (int i = 0; i < elementsToEncrypt.Count; i++)
{
string attrVal = elementsToEncrypt[i].Attributes["id"].Value;
ids.Add(attrVal);
}
foreach(string id in ids)
{
string query = string.Format("//*[@id='{0}']", id);
XmlElement elementFetched = (XmlElement)Doc.SelectSingleNode(query);
if (elementFetched == null)
continue;
EncryptedXml eXml = new EncryptedXml();
byte[] encryptedElement = eXml.EncryptData(elementFetched, Key, false);
EncryptedData edElement = new EncryptedData();
edElement.Type = EncryptedXml.XmlEncElementUrl;
edElement.EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncAES256Url);
edElement.CipherData.CipherValue = encryptedElement;
EncryptedXml.ReplaceElement(elementFetched, edElement, false);
}
}
public static void Decrypt(XmlDocument Doc, SymmetricAlgorithm Alg)
{
// Check the arguments.
if (Doc == null)
throw new ArgumentNullException("Doc");
if (Alg == null)
throw new ArgumentNullException("Alg");
// Find the EncryptedData element in the XmlDocument.
XmlElement encryptedElement = Doc.GetElementsByTagName("EncryptedData")[0] as XmlElement;
// If the EncryptedData element was not found, throw an exception.
if (encryptedElement == null)
{
throw new XmlException("The EncryptedData element was not found.");
}
// Create an EncryptedData object and populate it.
EncryptedData edElement = new EncryptedData();
edElement.LoadXml(encryptedElement);
// Create a new EncryptedXml object.
EncryptedXml exml = new EncryptedXml();
// Decrypt the element using the symmetric key.
byte[] rgbOutput = exml.DecryptData(edElement, Alg);
// Replace the encryptedData element with the plaintext XML element.
exml.ReplaceData(encryptedElement, rgbOutput);
}
public static void DecryptMultiple(XmlDocument Doc, SymmetricAlgorithm Alg)
{
// Check the arguments.
if (Doc == null)
throw new ArgumentNullException("Doc");
if (Alg == null)
throw new ArgumentNullException("Alg");
XmlNodeList encryptedElements = Doc.GetElementsByTagName("EncryptedData");
if (encryptedElements == null)
{
throw new XmlException("The EncryptedData elements were not found.");
}
List<XmlNode> nodes = new List<XmlNode>();
foreach (XmlNode n in encryptedElements)
{
if (n == null)
continue;
nodes.Add(n.Clone());
}
foreach (XmlElement ele in nodes)
{
XmlElement elementFetched = encryptedElements[0] as XmlElement;
EncryptedData edElement = new EncryptedData();
edElement.LoadXml(ele);
EncryptedXml exml = new EncryptedXml();
byte[] rgbOutput = exml.DecryptData(edElement, Alg);
exml.ReplaceData(elementFetched, rgbOutput);
}
}
}
}
精彩评论