HomeDigital EditionSearch Dotnet Cd
ASP.NET C# Certification Exams The CLI Data Access Editorials Extending .NET Fundamentals Interoperability Interviews Migrate Mobile .NET Mono .NET Interface Object-Oriented Programming Open Source Optimization Product/Book Reviews Security Source Code UML Visual Studio .NET

Securing XML in the .NET Framework
Using the .NET cryptography classes

Use of XML has become more and more popular over the past few years. Security is a big concern since the content of an XML file is in plain text and the information is in a human-readable form. The World Wide Web Consortium (W3C) has developed standards to meet the security requirements of an XML file conforming to common XML paradigms. In this article I will show you how to implement public-key encryption using the cryptography classes in the .NET Framework to secure an XML file, thereby ensuring its integrity and confidentiality.

XML was conceived as a means of harnessing the power and flexibility of SGML (Standard Generalized Markup Language) without all its complexity. Although a restricted form of SGML, XML nonetheless preserves most of SGML's power and richness, and yet retains all of SGML's commonly used features. XML gives developers the flexibility to develop their own tags and to embed information in them. Since XML is in plain text, it has steadily gained popularity as a medium by which two completely different applications can talk to each other. However, the plain text format that gives XML its inherent advantages is also its Achilles' heel. Listing 1 shows a simple XML document that shows how XML might be used to convey payment information. (All of the code for this article can be downloaded from www.sys-con.com/dotnet/sourcec.cfm).

As you can see, reading XML is pretty simple, and in most cases, no prior knowledge of XML is needed to understand what data is being transmitted in a document. It is very easy for a third party to snoop through the data that is being passed. There needs to be a way to ensure that the information is not disclosed to an unintended recipient. We would also benefit from having some way of making sure that the document has not been tampered with while in transit. The W3C has set standards for implementing encryption of XML documents to prevent the disclosure of information and also to allow for digitally signing a document to ensure its integrity. I will first examine these standards and then discuss ways to implement them by using the cryptography classes in the .NET Framework.

Cryptography
Cryptography is the practice of scrambling plain text into ciphertext through a process called encryption and then converting the ciphertext back into plain text by another process, called decryption. Cryptography has three main objectives.

  • Confidentiality: Keeping the contents of the message private so that only authorized parties can read it. Confidentiality of the information is achieved by encrypting the contents. Cryptography provides an excellent means to ensure confidentiality by applying symmetric or asymmetric encryption mechanisms.
  • Integrity: Ensuring that the contents of the message have not been tampered with while in transit.
  • Authentication: Ensuring that the message claiming to have been sent by a party was actually sent by the party. This is also known as nonrepudiation.
Digitally signing the document helps to ensure the integrity of the document and also helps in verifying its authenticity. The cryptography classes in the .NET Framework provide us with a wide variety of options to implement both asymmetric and symmetric encryption and also to digitally sign documents.

Security and XML
The W3C standards are designed to conform to common XML paradigms. They leverage existing XML standards and also enhance the XML standards. They specifically describe a process for encrypting and signing the XML data and representing the result in XML.

Encrypting XML
The W3C specifies a process for encrypting data and representing the result in XML. The encrypted portion of the XML is replaced by an EncryptedData element, which has the structure shown in Listing 2.

"?" 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 raw encrypted data is enclosed in the CipherValue element. There are different scenarios for implementing XML encryption. Let's look at a few of them. Consider the XML file shown in Listing 3, which includes a person's identification and payment information, which in this case is a credit card.

This XML file is an example of a scenario in which we may want to implement encryption to keep the credit card information from unintended intermediaries. This can be done in three ways.

Encrypting the XML Element
In this scenario, the whole Credit Card element is encrypted since it is sensitive and the user may not want the intermediary to know what is being sent. The encrypted file would look like Listing 4.

Encrypting the XML Element Content
In this scenario, the child elements of the element in question and their contents are encrypted. Taking the same credit card example as before, the number element, the issuer element, and the expiration elements - which are children of the CreditCard element - are encrypted. However, it will be visible to the intermediary that a credit card is being used as the payment medium. Listing 5 shows this scenario.

Encrypting the XML Element Character Content
In this scenario we would encrypt only the character content of a particular element. An example would be if we encrypt only the credit card number, as shown in Listing 6.

Signing XML
The W3C has specific XML syntax and processing rules for creating and representing digital signatures. XML digital signatures are represented by the Signature element, which has the structure shown in Listing 7 (where "?" denotes zero or one occurrence; "+" denotes one or more occurrences; and "*" denotes zero or more occurrences). Listing 8 shows a sample XML signature.

There are three basic ways in which an XML signature may be associated with signed content:

  • Wrapped signature
  • Detached signature
  • Enveloped signature
Wrapped Signature
In this case, the digital signature is wrapped around the signed content. A wrapped digital signature carries the signed content embedded within it. Wrapped signatures have the advantage that they put the signature and the signed content together in a single package that is easily recognizable. However, they obscure the original format of the signed data.

Detached Signature
Detached signatures are physically separate from the signed content and may or may not contain a reference to the signed content.

Enveloped Signature
In this representation, the signature object is embedded within the signed content. An enveloped signature can be used only when there exists a way to add the signature to the signed content without changing the hash of the signed content. Figure 1 shows a comparison between the different types of XML digital signatures.

Encrypting and Decrypting XML Using the .NET Framework
In this section, we will look at using the .NET Framework to encrypt and decrypt an XML document according to the standards set by the W3C. The System.Security. Cryptography namespace of the .NET Framework provides a variety of options for implementing either symmetric or asymmetric encryption and decryption. The steps for encryption are almost the same irrespective of whether we are encrypting the XML element, its content, or even its character data. The encryption method can be designed in such a way as to accept an XPath expression and return the encrypted value. Listing 9 shows the custom class XMLEncryptor used to encrypt and decrypt an XML document according to the standards set by the W3C.

XMLEncryptor accepts as parameters in its constructor the XML document to be encrypted, the path to the public key, and the path to the private key. This class uses a custom class called CryptoProvider, shown in Listing 10, which provides the implementation of public-key encryption. It has two public methods, namely Encrypt and Decrypt, which take the XPath expression and the Type of Encryption as arguments (XMLEncryptType). XMLEncryptType is an enumerator shown in Listing 11.

The encrypt method creates the new nodes, namely EncryptedData, CipherData, and CipherValue. Depending upon the value of XMLEncryptType, either the XML element or the content in the XML document is replaced with the encrypted data. The Decrypt method again takes the XPath expression and makes a call to the Decrypt method of CryptoProvider to decrypt the data. It then replaces the nodes in the XML document with the decrypted data. Both methods utilize XPathNodeIterator and XPathNavigator classes to iterate and navigate through the XML document.

Listing 10 shows a custom class called CryptoProvider that can be reused to implement public-key encryption. This class utilizes the RSACryptoServiceProvider class to provide asymmetric encryption and decryption using the RSA algorithm. This class accepts the path to the private key and public key, which are saved as XML files, and uses them to encrypt and decrypt the input string. The Encrypt method encrypts the input string. It takes the input string as the argument, and then creates a new instance of the RSACryptoServiceProvider from the public key. It utilizes a helper method, ReadXMLKeyintoString, to read the key from the XML file. RSACryptoServiceProvider provides an Encrypt method that encrypts a byte array. In order to convert the string into a byte array, the method utilizes another helper method, called ChangeStringToByte, which outputs a byte array. This byte array is then converted into a string by utilizing the Convert.ToBase64String method. The resulting string is then passed to the caller.

The Decrypt method is similar to the Encrypt method. The difference is that it creates a new instance of the RSACryptoServiceProvider from the private key. The encrypted string is first converted to a byte array. The byte array is then decrypted and the decrypted byte array is converted back into a string and returned to the caller. Listing 12 shows a custom class that generates a public key and private key pair that can be used for encryption and decryption. The class utilizes the RSA class to create the key pair and then writes the public and private keys as XML files.

Signing and Verifying XML Using the .NET Framework
The .NET Framework provides a set of classes in the system.Security. Cryptography.Xml namespace that provide high-level signature generation and verification functions. Regardless of the type of signature being created, the steps are almost the same. They involve creating a new SignedXml object to hold the signature information.

The next step is to associate the signing key with the SignedXml object by setting its SigningKey property. Once this is done, a reference object is created for the piece of content to be signed, and the reference object is added to the SignedXml object. Optionally, a KeyInfo object can be added to the SignedXml object to communicate key information.The XML signature is computed by calling the ComputeSignature() method of the SignedXml object. The XML signature can now be obtained by calling the SignedXml.GetXml method(). Listing 13 summarizes these steps and creates an enveloped signature. These steps can easily be modified for creating other types of signatures.

The next challenge is to verify the signature that has been created. We will be using the enveloped signature created in the above example and verifying its signature. The first step in the process is to create a SignedXml object and to pass the Xmldocument being verified as a parameter to the constructor.The next step is to load the digital signature object by using the Loadxml method of the SignedXml object. Since we have created an enveloped signature, we extract the Signature node from the document and then load the signature into the SignedXml object. To verify the signature, utilize the CheckSignature method of the SignedXml object. If the signature is valid, the CheckSignature method (see Listing 14) returns true and if it's invalid, it returns false.

Summary
I have discussed the standards set by the W3C for securing XML by encrypting the content and also by digitally signing it. I have also shown how these standards can be implemented using the .NET Framework.

Resources

  • XML Encryption Syntax and Processing: www.w3.org/TR/xmlenc-core
  • XML-Signature Syntax and Processing: www.w3.org/TR/xmldsig-core
  • .NET Framework Class Library - System.Security.Cryptography Namespace: Click Here !

    About The Author
    Jeevan C. Murkoth is a senior programmer analyst with Marlabs Inc. Jeevan holds an MS in Management Information Systems from Texas Tech University and is a Microsoft Certified Solutions Developer (MCSD) in .NET (Early Achiever) and a Microsoft Certified Application Developer (MCAD) in .NET. jeevan@marlabs.com

    
    
    Listing 1 : XML file
    
    <?xml version='1.0'?>
      <PaymentInfo xmlns='http://example.org/paymentv2'>
        <Name>John Doe</Name>
        <CreditCard Limit='5,000' Currency='USD'>
          <Number>4019 2445 0277 5567</Number>
          <Issuer>Example Bank</Issuer>
          <Expiration>04/04</Expiration>
        </CreditCard>
     </PaymentInfo>
    
    
    Listing 2: Structure of encrypted data
    
    <EncryptedData Id? Type? MimeType? Encoding?>
        <EncryptionMethod/>?
        <ds:KeyInfo>
          <EncryptedKey>?
          <AgreementMethod>?
          <ds:KeyName>?
          <ds:RetrievalMethod>?
          <ds:*>?
        </ds:KeyInfo>?
        <CipherData>
          <CipherValue>?
          <CipherReference URI?>?
        </CipherData>
        <EncryptionProperties>?
      </EncryptedData>
    
    
    Listing 3: XML file to be encrypted
    
      <?xml version='1.0'?>
      <PaymentInfo xmlns='http://example.org/paymentv2'>
        <Name>John Doe</Name>
        <CreditCard Limit='5,000' Currency='USD'>
          <Number>4019 2445 0277 5567</Number>
          <Issuer>Example Bank</Issuer>
          <Expiration>04/04</Expiration>
        </CreditCard>
      </PaymentInfo>
    
    
    Listing 4 : Encrypted XML element
    
    <?xml version='1.0'?>
      <PaymentInfo xmlns='http://example.org/paymentv2'>
        <Name>John Smith</Name>
        <EncryptedData Type='http://www.w3.org/2001/04/xmlenc#Element'
         xmlns='http://www.w3.org/2001/04/xmlenc#'>
          <CipherData>
            <CipherValue>A23B45C56</CipherValue>
          </CipherData>
        </EncryptedData>
    </PaymentInfo>
    
    
    Listing 5 : Encrypted XML content
    
      <?xml version='1.0'?> 
      <PaymentInfo xmlns='http://example.org/paymentv2'>
        <Name>John Smith</Name>
        <CreditCard Limit='5,000' Currency='USD'>
          <EncryptedData xmlns='http://www.w3.org/2001/04/xmlenc#'
           Type='http://www.w3.org/2001/04/xmlenc#Content'>
            <CipherData>
              <CipherValue>A23B45C56</CipherValue>
            </CipherData>
          </EncryptedData>
        </CreditCard>
      </PaymentInfo>
    
    Listing 6 : Encrypted XML character content
    
    <?xml version='1.0'?> 
      <PaymentInfo xmlns='http://example.org/paymentv2'>
        <Name>John Smith</Name>
        <CreditCard Limit='5,000' Currency='USD'>
          <Number>
            <EncryptedData xmlns='http://www.w3.org/2001/04/xmlenc#'
             Type='http://www.w3.org/2001/04/xmlenc#Content'>
              <CipherData>
                <CipherValue>A23B45C56</CipherValue>
              </CipherData>
            </EncryptedData>
          </Number>
          <Issuer>Example Bank</Issuer>
          <Expiration>04/04</Expiration>
        </CreditCard>
       	</PaymentInfo> 
    
    
    Listing 7 : XML Signature structure
    
    	<Signature> 
         		<SignedInfo>
           		(CanonicalizationMethod)
           		(SignatureMethod)
           		(<Reference (URI=)? >
            		(Transforms)?
             		(DigestMethod)
             		(DigestValue)
           		</Reference>)+
         		</SignedInfo>
         		(SignatureValue) 
        		(KeyInfo)?
        		(Object)*
      	 </Signature>
    
    
    Listing 8: Signed XML
    
    <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
    <CanonicalizationMethod
     Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
    <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
    <Reference URI="">
    <Transforms>
    <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
    	</Transforms>
    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
    <DigestValue>DZyioHJGMPli2oapvqukERomI9w=</DigestValue>
    </Reference>
    </SignedInfo>
    <SignatureValue>hv3OGlBnIcC8mDL2yZrOtqk8/l7aQjNAOY=</SignatureValue>
    <KeyInfo>
    <KeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
    <RSAKeyValue>
    <Modulus>wItpMqxUfOw46R6lrAO6aPqVLZp/1Cbbc=</Modulus>
    <Exponent>AQAB</Exponent>
    </RSAKeyValue>
    </KeyValue>
    </KeyInfo>
    </Signature>
    
    
    Listing 9: Custom class for XML encryption
    
    using System;
    using System.Xml;
    using System.Xml.XPath;
    using System.Security.Cryptography;
    using CryptoProviderNamespace; //Namespace containing CryptoProvider class
    public class XMLEncryptor 
    	{
    		//The XML document to be encrypted
    private XmlDocument XMLdocTobeEncrypted;
    //Instance of the custom class CryptoProvider
    		private CryptoProvider myCryptoProvider;
    
    public XMLEncryptor(XmlDocument doc,string sPublicKeyPath, string sPrivateKeyPath) 
    		{			
    			XMLdocTobeEncrypted = doc;
    //Instantiating the custom class and assigning values		
    myCryptoProvider= new CryptoProvider();
    myCryptoProvider.RecipientPublicKeyPath=sPrivateKeyPath;
    myCryptoProvider.SenderPrivateKeyPath =sPublicKeyPath;			
    		}
    
    		public XmlDocument GetXMLDocument()
    		{
    			return XMLdocTobeEncrypted;
    		}
    
    		private string  EncryptXML(string sInputString)
    		{
    			return myCryptoProvider.Encrypt(sInputString);
    		}
    		private string DecryptXML(string sInputString)
    		{
    			return myCryptoProvider.Decrypt(sInputString);
    		}
    
    		public void Encrypt(String xpath, XMLEncryptType Type)
    		{
    	//selecting the node depending on the xpath expression
    	XPathNavigator mynavigator = XMLdocTobeEncrypted.CreateNavigator();
    	XPathNodeIterator myiter = mynavigator.Select(xpath);
    	myiter.MoveNext(); 
    	// Creating the New Element and nodes for the encrypted data
    	XmlElement myElement =
    	XMLdocTobeEncrypted.CreateElement("EncryptedData","");
    	XmlNode myElementChild =
    	 XMLdocTobeEncrypted.CreateElement("CipherData","");
    	 XmlNode myElementChild2=
    	 XMLdocTobeEncrypted.CreateElement("CipherValue");
    	 XmlNode myElementChild3=XMLdocTobeEncrypted.CreateTextNode("");
    	 myElementChild2.AppendChild(myElementChild3);
    	 myElementChild.AppendChild(myElementChild2);
    	 myElement.AppendChild(myElementChild);
    	XmlNode node = ((IHasXmlNode)myiter.Current).GetNode();
    	//Choosing the course of action depending on what is to be
    	//encrypted
    	switch(Type)
    	{
    	case XMLEncryptType.EncryptContent:
    	//Setting the value of the Cipher value node by encrypting the
    	content of the node
    	myElementChild3.Value=EncryptXML(node.InnerXml);
    	//Replacing the text of the XML document node by the encrypted data
    	nodes
    	node.InnerXml=myElement.OuterXml;
    	break;
    	case XMLEncryptType.EncryptElement:
    	myElementChild3.Value=EncryptXML(node.OuterXml);
    	XmlNode myn1= node.ParentNode;
    	//Replacing the node with the encrypted Data nodes
    	myn1.ReplaceChild(myElement,node);
    	break;						
    	}
    			
    
    	}
    
    		public void Decrypt(String xpath)
    		{
    XPathNavigator mynavigator = XMLdocTobeEncrypted.CreateNavigator();
    XPathNodeIterator myiterator = mynavigator.Select(xpath);
    myiterator.MoveNext();
    XmlNode node = ((IHasXmlNode)myiterator.Current).GetNode();
    //calling the helper method and decrypting the node
    string sDecryptedValue= DecryptXML(node.InnerText);
    //Since there are three levels of nodes in the structure of
    //encrypted data, we navigate 3 levels up and insert the //decrypted value
    node.ParentNode.ParentNode.ParentNode.InnerXml = sDecryptedValue;
    		}
    
    		
    	}
    
    
    Listing 10: A Reusable Class providing public-key encryption
    
    using System;
    using System.Security.Cryptography;
    using System.IO;
    public class CryptoProvider
    	{
    		private RSACryptoServiceProvider _myCryptoProvider; 
    		private string _sRecipientPublicKeyPath;
    		private string _sSenderPrivateKeyPath;
    		public CryptoProvider()
    		{
    	_myCryptoProvider = new RSACryptoServiceProvider();
    		}
    		public string SenderPrivateKeyPath
    		{
    			set
    			{
    			_sSenderPrivateKeyPath=value;
    			}
    			get
    			{
    				return _sSenderPrivateKeyPath;
    
    			}
    
    		}
    		public string RecipientPublicKeyPath
    		{
    			set
    			{
    				_sRecipientPublicKeyPath=value;
    			}
    			
    			get
    			{
    				return _sRecipientPublicKeyPath;
    
    			}
    		}
    		public string Encrypt( string sInputString)
    		{
    			string sEncryptedString=string.Empty;
    			try
    {	//Reconstructing the RSA provider from the key saved as //XML file
    _myCryptoProvider.FromXmlString(ReadXMLKeyintoString(_sRecipientPublicKeyPath));
    //Encrypting input string after changing to byte array
    byte[] mybtArray=
    _myCryptoProvider.Encrypt(ChangeStringToByte(sInputString),false);
    	sEncryptedString=Convert.ToBase64String(mybtArray);	}
    		catch(Exception ex)
    			{
    throw new Exception("ENCRYPTION FAILED:"+ex.Message);
    			}
    			return sEncryptedString;
    		}
    		public string Decrypt(string sEncryptedString )
    		{
    			string sDecryptedString = string.Empty;
    			try
    {
    //Reconstructing the RSA provider from the key saved as XML file
    
    _myCryptoProvider.FromXmlString(ReadXMLKeyintoString(_sSenderPrivateKeyPath));
    byte[] mybtArray
     =_myCryptoProvider.Decrypt(Convert.FromBase64String(sEncryptedString),false);
     	sDecryptedString=ChangeByteToString(mybtArray);	
    			}
    			catch(Exception ex)
    			{
    throw new Exception("DECRYPTION FAILED:"+ex.Message); 
    			}
    
    			return sDecryptedString;
    		}
    		private string ReadXMLKeyintoString(string sKeyFilePath)
    		{
    		string sKeyContent;
    		//The Key is saved in an XML document , so reading the file
    	FileStream myFileStream=new FileStream(sKeyFilePath,FileMode.Open);
    	StreamReader myReader = new StreamReader(myFileStream);
    	sKeyContent=	myReader.ReadToEnd();
    	myReader.Close();
    	myFileStream.Close();
    	return sKeyContent;
    		}
    		private  byte[] ChangeStringToByte(string sInputString)
    		{
    return System.Text.UnicodeEncoding.Default.GetBytes(sInputString);
    		}
    
    		private  string ChangeByteToString(byte[] btArrayInput)
    		{
    return System.Text.UnicodeEncoding.Default.GetString(btArrayInput);
    		}
    
    	}
    
    
    Listing 11: XMLEncryption Type Enumerator
    
    public enum XMLEncryptType
    	{
    		EncryptElement,
    		EncryptContent
    
    	}
    
    
    Listing 12: A Reusable Class for Key generation
    
    using System;
    using System.Security.Cryptography;
    using System.IO;
    public class KeyPairGenerator
    	{
    		public KeyPairGenerator()
    		{
    			//Default constructor
    		}
    		public void GenKeyAndSave()
    		{
    			//Creating an Instance of RSA implementation
    			RSA myRSA = RSA.Create();
    //Creating and saving an XML representation of public key to //file
    WriteKeyToFile(myRSA.ToXmlString(false),@"Publickey.xml");
    //Creating and saving an XML representation of private key to //file
    WriteKeyToFile(myRSA.ToXmlString(true),@"Privatekey.xml");
    		}
    		private void WriteKeyToFile(string sKey, string fileName)
    		{
    			//Helper method which writes to the physical file
    StreamWriter mywriter = new StreamWriter(fileName,false);
    			mywriter.Write(sKey);
    			mywriter.Close();
    		}
    	}
    
    
    
    Listing 13: Method for Signing an XML document
    
    public void SignXMLDocument(string sFilePath ,string sKeyFilePath)
    		{
    			string sKeyContent;
    			//The Key is saved in an XML document 
      FileStream myFileStream=new FileStream(sKeyFilePath,FileMode.Open);
      StreamReader myReader = new StreamReader(myFileStream);
      sKeyContent=	myReader.ReadToEnd();
      myReader.Close();
      myFileStream.Close();
      XmlDocument mydoc = new XmlDocument();
      mydoc.Load(sFilePath);
      SignedXml mysignedxml =new SignedXml(mydoc);
      RSA key =RSA.Create();
      key.FromXmlString(sKeyContent);
      mysignedxml.SigningKey =key;
      Reference reference = new Reference();
      reference.Uri = "";
      reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
      KeyInfo keyInfo = new KeyInfo();
      keyInfo.AddClause(new RSAKeyValue(key));
      mysignedxml.KeyInfo = keyInfo;
      mysignedxml.AddReference(reference);
      mysignedxml.ComputeSignature();
      XmlElement xmlSignature = mysignedxml.GetXml();
      XmlNode n = mydoc.ImportNode(xmlSignature, true);
      XmlNode root = mydoc.DocumentElement;
      root.InsertAfter(n, root.FirstChild);
      mydoc.PreserveWhitespace =true;
      mydoc.Save(sFilePath);
      
    		}
    
    
    
    Listing 14: Method for checking the signature of an XML document
    
    public bool CheckSignature(string sFilePath)
    			{
    	XmlDocument xmlDocument = new XmlDocument();
    	xmlDocument.PreserveWhitespace = true;
    	xmlDocument.Load(sFilePath);
    	SignedXml signedXml = new SignedXml(xmlDocument);
    XmlNodeList nodeList = xmlDocument.GetElementsByTagName("Signature");
    	signedXml.LoadXml((XmlElement)nodeList[0]);
    	return signedXml.CheckSignature();
    			}
    

    All Rights Reserved
    Copyright ©  2004 SYS-CON Media, Inc.

      E-mail: info@sys-con.com