.NET Security and Cryptography
Just one of the many nice features of XML is that it tends to be rather human readable when compared to most traditional proprietary or legacy binary data formats. Just open an Excel spreadsheet in Notepad and try to figure out what is meant by all the proprietary formatting codes. In XML, all those human-meaningful tags sprinkled throughout the document tend to visually jump out and help you to interpret the underlying meaning. This can go a long way in helping you sort your way through the complex morass that is so typical of most structured data that is processed by complex applications. The readability of XML can therefore often be a great benefit to programmers, who must frequently read XML input and output to help guide them during software development and testing. Of course, human readability is an advantage only if you in fact do intend for people to make visual sense of your data. Frequently, that is not the case, especially after the application is deployed and used for processing real-world sensitive data. Fortunately, XML encryption can be selectively applied to the sensitive elements contained within your XML documents. In this way, you can ensure that those parts of your data that need to be kept private are made unintelligible to unauthorized individuals. XML Encryption Versus SSL/TLS
There already exist Internet encryption and security protocols, such as Secure Sockets Layer (SSL), Transport Layer Security (TLS), [2] and IPSec, [3] which can be used to protect privacy and ensure data integrity between two communicating applications. There are two aspects of XML encryption that contrast with these encryption protocols. The first difference is that with XML encryption, you can selectively encrypt those XML elements that actually require it, and other nonsensitive elements may be intentionally left unencrypted. The second difference is that XML encryption can be used for encrypting data that is either transmitted directly to another application or accessed by many applications via stored media, such as a disk file or database record. In contrast, protocols such as SSL, TLS, and IPSec encrypt the entire connection as a whole, allowing it to be used between two communicating applications, but it is not as useful for encrypted data storage purposes. XML encryption does not replace these existing security protocols, but instead solves an entirely different type of security problem. The two major issues that are specifically addressed by XML encryption are the following: [2] The SSL protocol was developed by Netscape to provide privacy and data integrity between two communicating applications. SSL formed the basis for the TLS protocol, which is defined in RFC2246. SSL and TLS are basically the same protocol, referred to collectively as SSL/TLS. [3] The IPsec protocol provides packet-level security, which essentially implements a VPN protocol.
The XML Encryption Specification
For the full details on the XML encryption specification, you can review the official XML Encryption Syntax and Processing specification. [4] This specification provides an overview with examples, as well as a complete XML schema [5] definition of XML encryption syntax. At the time of writing, the status of this specification was at the Proposed Recommendations [6] stage, meaning that it could potentially undergo changes. In fact, the .NET Framework does not yet provide any specialized support for this specification. We therefore, for the time being, need to write our own code to support this specification where necessary. [4] This W3C specification can be found at http://www.w3.org/TR/xmlenc- core /. [5] More information on the XML schema specification can be found in two parts located at http://www.w3.org/TR/2001/REC-xmlschema-1-20010502/ and http://www.w3.org/TR/2001/REC-xmlschema-2-20010502/. [6] A W3C Proposed Recommendation is a draft that meets all requirements established by the associated Working Group, but it is not yet appropriate for widespread deployment. It is considered to be a work-in-progress and may be updated, replaced , or made obsolete at any time. It would be an undertaking of major proportions to design and code an entire .NET class library to fully support this specification, which we will not attempt here. Instead, we implement modest and makeshift functionality inline where necessary to demonstrate in a simple example program how to encrypt a portion of a simple XML document. We do this using the currently available XML and cryptography classes provided by the .NET Framework. Fortunately, as we will see later in this chapter, the XML signature specification has completed its standardization process, and .NET therefore provides much more complete support for that standard. What XML Encryption Provides
XML encryption provides a standardized means for encrypting structured data and representing the result in a standard XML format. This allows you to encrypt any data, whether it is an entire XML document, just specific elements within an XML document, or arbitrary, externally referenced data that is not even necessarily in XML format. The result is then represented as an XML encryption element that can either directly contain the encrypted data or indirectly reference the externally encrypted data. In either case the resulting representation allows the encrypted data to be conveniently accessed for processing by applications that use powerful, familiar, and platform-independent XML programming techniques. The general cryptographic concepts that we have already seen in previous chapters regarding symmetric and asymmetric algorithms remain the same when dealing with XML encryption. The only difference here is that we make use of a standardized XML tag syntax for representing relevant information such as key information, algorithm, and, of course, the actual encrypted data that is contained or referenced within the resulting XML document. XML Encryption Syntax
XML encryption syntax is defined clearly and completely by the W3C specification, so we will not provide a lengthy duplication of that information here. Rather than exhaustively discussing each tag and processing rule, we instead provide a brief overview of some of the key aspects of XML encryption syntax along with a simple example to help you get started in understanding the specification. THE XML ENCRYPTION NAMESPACE
Namespaces [7] are used in XML for much the same reason that they are used in many programming languages, such as C#, Java, and C++. Namespaces solve the problem of name collisions by providing universal names that extend beyond the scope of individual XML applications. [7] More information on XML namespaces can be found at http://www.w3.org/TR/1999/REC-xml-names-19990114/. Each XML syntax specification defines a set of tags (i.e., elements and attributes), which are referred to as the syntax's markup vocabulary. But applications often must work with many XML documents that originate from multiple syntax specifications, each with its own distinct markup vocabularies. The problem can therefore arise where name collisions occur between conflicting markup vocabularies. By defining each markup vocabulary within its own distinct namespace, such collisions are avoided. For this reason, the XML encryption specification defines the following namespace: xmlns:xenc='http://www.w3c.org/2001/04/xmlenc#' In addition to providing syntactic scope, the namespace is used as a prefix for algorithm identifiers referred to in the specification. Here are some examples of algorithm identifiers that are defined in the specification. You should recognize each of the algorithms being identified, such as RSA, 3DES, SHA-1, and so on.
THE XML ENCRYPTION ELEMENTS
The EncryptedData element is the main syntactic component used in XML encryption. All other XML encryption elements are children of the EncryptedData element. This section provides a brief overview of the XML encryption elements, including the EncryptedData element and each of its children.
Let's now look at how all these elements are used together in an encrypted XML document. EncryptedData is the outermost element, which can contain any of its optional child elements: EncryptionMethod, ds:KeyInfo, and EncryptionProperties. It must also contain its one mandatory CipherData element. The ds:KeyInfo element can contain any of its optional elements that make session key information available. The CipherData element may contain either one of its optional elements, CipherValue or CipherReference. The EncryptedData element syntax has the following structure, where ? denotes zero or one occurrence and * denotes zero or more occurrences. [8] [8] Although it is not needed in this particular syntactic description, the specification uses + in other syntactic descriptions to denote one or more occurrences. <EncryptedData Id? Type? MimeType? Encoding?> <EncryptionMethod/>? <ds:KeyInfo> <EncryptedKey>? <AgreementMethod>? <ds:KeyName>? <ds:RetrievalMethod>? <ds:*>? </ds:KeyInfo>? <CipherData> <CipherValue>? <CipherReference URI?>? </CipherData> <EncryptionProperties>? </EncryptedData> To help make this more concrete, let's take a look at how the EncryptedData element is used in an actual example. This is the same simple XML document that we will see in the upcoming XML encryption example program. Let's now look at this XML document before and after encryption. Here is the content of the original document, before encryption is performed. <invoice> <items> <item> <desc>Deluxe corncob pipe</desc> <unitprice>14.95</unitprice> <quantity>1</quantity> </item> </items> < creditinfo > <cardnumber>0123456789</cardnumber> <expiration>01/06/2005</expiration> <lastname>Finn</lastname> <firstname>Huckleberry</firstname> </creditinfo> </invoice> As you can see, we have a simple invoice containing the information on the list of items purchased, which is not considered sensitive data in this example. But we also have credit information that is clearly in need of encryption. The following document shows the new contents after the encryption of the credit information. <invoice> <items> <item> <desc>Deluxe corncob pipe</desc> <unitprice>14.95</unitprice> <quantity>1</quantity> </item> </items> < EncryptedData Type="http://www.w3.org/2001/04/ xmlenc #Element "> <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/ xmldsig#"> <KeyName>My 3DES Session Key</KeyName> </ds:KeyInfo> <CipherData> <CipherValue>tu7ev6nuRCYUgZpN0ZABz+VJvL... </CipherValue> </CipherData> </EncryptedData> </invoice> This document is the same as the original except that the creditinfo element has been entirely replaced with the EncryptedData element. The CipherValue element contains the encrypted data representing the entire creditinfo element of the original document. The ds:KeyInfo element contains the name of the encrypted session key that must be used to decrypt the contents of the CipherValue element to recover the original credit information. If you look at the tail end of the Type attribute in the EncryptedData element, you will see that we are specifying #Element rather than #Content. This means that we want the entire creditinfo element, not just the content of that element, to be encrypted. You must decide for yourself which of these effects is desirable in your own applications. You must decide if you want only the content of the element to be hidden or if you want the entire element to be hidden, obscuring the fact that the information even exists in the document. It is of course a more secure approach to encrypt the entire element, since that helps to prevent an attacker from even noticing or taking an interest in the first place. However, it may be necessary for the type of element to be in the clear to make processing by your applications more convenient , in which case you should encrypt only the element contents. It is important to recognize that the credit information is not the only data that must be encrypted in the above example. The credit information is encrypted with a secret symmetric key, but that key must be asymmetrically encrypted with a public key so that only the intended recipient who has the matching private key can decrypt that session key and then use it to decrypt the credit information. We could have incorporated the encrypted session key data directly into the invoice document. To do that, we would have placed a CipherValue element containing the encrypted session key into the EncryptedData element. Instead, we included only information on the name of the key within the invoice document. In this example we provide the encrypted session key data its own dedicated key exchange document, as shown in the following listing. Notice that this key exchange document uses EncryptedKey rather than EncryptedData as the top-level element. < EncryptedKey CarriedKeyName="My 3DES Session Key"> <EncryptionMethod Algorithm="...xmlenc#rsa-1_5" /> < ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/ xmldsig#"> <KeyName>My Private Key</KeyName> </ds:KeyInfo> <CipherData> < CipherValue >bYgmKUZIXzwt2te9dmONF7Mj... </CipherValue> </CipherData> </EncryptedKey> How XML Encryption Works
As is the case with most general-purpose encryption schemes, XML encryption makes use of a combination of symmetric and asymmetric algorithms. The symmetric algorithm is used for bulk encryption of XML data elements, and the asymmetric algorithm is used to securely exchange the symmetric key. Here is the typical scheme used by the sender and receiver of encrypted XML data messages.
Classes Used in XML Encryption
Although the .NET Framework does provide high-level support for the XML signatures specification, which is covered later in this chapter, there is currently no high-level support for the XML encryption specification. Instead, we make do with our own approach to implementing the necessary functionality using the available classes found in the System.Xml and System.Security.Cryptography namespaces. These namespaces provide general-purpose XML parsing capabilities and cryptographic functionality, respectively. We will not exhaustively study these two namespaces here. We have already seen the main cryptography namespace classes in action in previous chapters, and we will also not get sidetracked by all the powerful parsing capabilities in the XML namespace, which is beyond the scope of this book. Instead, we look at just a few methods in some of these classes that will be useful in the upcoming XML encryption program example. THE XMLDOCUMENT CLASS
The XmlDocument class enables the navigation and editing of an XML document as defined by the W3C Document Object Model [9] (DOM). The XmlDocument class, defined in the System.Xml namespace, supports a large number of methods and properties. The XmlDocument class inherits from XmlNode , from which it derives many of its members . The fact that the document is an element that can contain subelements makes sense, since an XML document is inherently hierarchical. [9] The DOM specification is available at http://www.w3.org/TR/2002/WD-DOM-Level-3-Core-20021022/. The following paragraphs describe the methods that are particularly useful in understanding the upcoming XML encryption program example. There are four overloaded versions of the Load method, each of which loads an XML document from a specified source. One of these Load methods takes a string parameter, representing the URL for the file containing the XML document to load. In the upcoming example program, this parameter is simply the name of a disk file. public virtual void Load( string filename ); The LoadXml method is similar to the Load method, except that it loads the XML document directly from the contents of the specified string. We will pass a literal string containing the desired XML data. public virtual void LoadXml( string xml ); There are three overloaded versions of the CreateElement method. In each case these methods create and add a new XML element with the specified name to the XML document. We will use the following two overloadings. public XmlElement CreateElement( string name ); public virtual XmlElement CreateElement( string prefix, string localName, string namespaceURI ); The AppendChild method, inherited from XmlNode , adds the specified node to the end of the child list of the current node. We will use this to add child nodes to the XML document. public virtual XmlNode AppendChild( XmlNode newChild ); The CreateAttribute method has three overloadings that are used to create an XML attribute with a specified name. We will use only the one that takes a single string parameter. public XmlAttribute CreateAttribute( string name ); The SelectSingleNode method, inherited from XmlNode , has two overloadings. We will use the overloading that takes a single string parameter. public XmlNode SelectSingleNode( string xpath ); The Save method saves the XML document to the specified location. There are four overloadings available for this method. We will use the one that takes a string parameter, which represents the name of the file to which the XML document is saved. public virtual void Save( string filename ); THE XMLELEMENT CLASS
The XmlElement class, defined in the System.Xml namespace, represents an XML element within a document. The XmlElement class is derived from XmlLinkedNode , which in turn is derived from XmlNode. XmlElement supports many methods and properties, but we will use only the AppendChild method and InnerText property. The AppendChild method, inherited from XmlNode , adds the specified node to the end of the child list of the current node. We will use this to add child nodes to an XML element. This method is identical to the method of the same name in the XmlDocument class described earlier. public virtual XmlNode AppendChild( XmlNode newChild ); The InnerText property is used to get or set the concatenated string representing the node and all its children. public override string InnerText {get; set;} THE XMLATTRIBUTE CLASS
The XmlAttribute class, defined in the System.Xml namespace, supports many useful properties and methods. We will use only the Value property, which is used to get or set the value of the node. public override string Value {get; set;} THE RSACRYPTOSERVICEPROVIDER CLASS
We have already seen how to use the RSACryptoServiceProvider class in Chapters 4 and 5. You may recall from those previous discussions that this class is defined in the System.Security.Cryptography namespace. We will use the familiar Encrypt and Decrypt methods. We will also use the two methods FromXmlString and ToXmlString . The Decrypt method decrypts data using the RSA algorithm. public byte[] Decrypt( byte[] rgb, bool fOAEP ); The Encrypt method encrypts data using the RSA algorithm. public byte[] Encrypt( byte[] rgb, bool fOAEP ); The ToXmlString method creates and returns an XML string object that represents the RSA object. The includePrivateParameters parameter is used to control whether or not private key information is stored. If this parameter is true, both public and private information is stored in the XML output. public override string ToXmlString( bool includePrivateParameters ); The FromXmlString method recreates the RSA object from an XML string representation. public override void FromXmlString( string xmlString ); THE CONVERT CLASS
When dealing with encryption, we generally must work a great deal with data that is in the form of an array of bytes. However, when dealing with XML data processing, we invariably work with data that is in the form of text strings. To bridge this gap, we often must convert between these two datatypes. To do this, we make use of the ToBase64String and FromBase64String static methods provided by the Convert class. ToBase64String converts an array of 8-bit unsigned integers to its equivalent string representation consisting of base64 digits. public static string ToBase64String( byte[] inArray ); The FromBase64String method converts a string representing a sequence of base64 digits to the corresponding array of 8-bit unsigned integers. public static byte[] FromBase64String( string s ); Communicating Asymmetric Key Information
You do not need to do anything special to generate an asymmetric key pair, since each asymmetric algorithm class (i.e., RSA and DSA) automatically generates a random key pair each time that it is instantiated . Since the sender and receiver will each need to create a distinct instance of their own RSA object, the resulting public and private key information will obviously not automatically match between the two parties. This problem can be solved simply by communicating the public key information between the two parties. In the case of RSA the public key information is limited to the modulus and exponent. This can be accomplished using the method pairs ToXmlString and FromXmlString or ExportParameters and ImportParameters. ToXmlString and FromXmlString allow you to store and retrieve an asymmetric algorithm object in XML format in a way that is reminiscent of traditional object serialization. ExportParameters and ImportParameters allow you to get much the same result, but rather than storing the asymmetric algorithm object in an XML format, an RSAParameters object is used for storage instead. Working with XML has an appealing simplicity about it, but since the RSAParameters class is serializable, it is also a workable solution for communicating key information. For example, if the receiver creates an RSA instance, then he or she can save the public key information and send it to the sender, unencrypted. The sender obtains the public key information and uses it to encrypt the sensitive data, which is then sent back to the receiver. The receiver obtains the encrypted data, and uses the associated private key information to decrypt the sensitive data. We will see the ToXmlString and FromXmlString methods of the RSA class in the upcoming program example. But first, let's look at the somewhat similar ExportParameters and ImportParameters methods. The ExportParameters method has the same parameter as ToXmlString , named includePrivateParameters , which is used to control whether or not private key information is stored. If this parameter is true, both public and private information is stored. If it is false, only public key information is stored. This parameter is handy, since the key information you will want to share with other parties should always contain only the public key information. Private key information may sometimes be stored for local usage, but then you must be sure that it is stored in a secure manner, such as in an Encrypted File System (EFS) file. [10] As an alternative, the private key information can be stored in the CryptoAPI (CAPI) keystore. One of the RSACryptoServiceProvider constructors takes a CspParameters object as a parameter. That CspParameters class contains a field named KeyContainerName , which can be used to store and retrieve your key pair. [10] This may seem to be a never-ending story. A symmetric session key used for secure transmission is encrypted using an asymmetric key that the application created, and the asymmetric key itself may be stored in an operating system-encrypted (i.e., EFS) file. This file is encrypted by yet another symmetric session key maintained by the operating system, which is in turn encrypted using an asymmetric key that is associated with an encrypted version of your logon password. Nobody ever said that cryptography and security was simple! The following shows the syntax for the ExportParameters and ImportParameters methods of the RSA and DSA algorithm classes. Instead of storing the key information in XML format, it stores the information in an algorithm-specific object, RSAParameters for RSA and DSAParameters for DSA . The includePrivateParameters parameter is provided in each of these ExportParameters methods and serves exactly the same purpose as that previously described for the ToXmlString method. public abstract RSAParameters ExportParameters( bool includePrivateParameters ); public abstract void ImportParameters( RSAParameters parameters ); public abstract DSAParameters ExportParameters( bool includePrivateParameters ); public abstract void ImportParameters( DSAParameters parameters ); The XmlEncryption Example
The XmlEncryption example program demonstrates how to encrypt and decrypt XML data. The source code for this entire example program is shown in the following code listing. As you can see, the program starts out in the Main method by creating a sender and receiver object. This models the typical scenario where one sending program encrypts data and transmits the result to a receiving program. To keep things simple and self-contained in this example, we represent these two communicating entities as two distinct classes; however, in a real-world scenario, these would almost certainly be distinct program instances. The program then has the receiver establish the RSA parameters that will be used to secure the symmetric algorithm session key by calling the EstablishXmlRsaParameters method. This is typically what is done in a real-life scenario, since it is the receiver who must establish the asymmetric key pair before any sender can use the public key to encrypt the session key that will be sent to the receiver. Then, by calling the CreateOriginalXmlDocument method, the sender creates the original XML document that will be encrypted. //XMLEncryption.cs //NOTE: must add a project reference to System.Security using System; using System.IO; using System.Text; using System.Xml; using System.Security.Cryptography; using System.Security.Cryptography.Xml; class XMLEncryption { static void Main(string[] args) { //create participants Sender sender = new Sender(); Receiver receiver = new Receiver(); //establish public and private RSA key information receiver.EstablishXmlRsaParameters( "RsaIncludePrivateParams.xml", "RsaExcludePrivateParams.xml"); //create original XML document to be encrypted sender.CreateOriginalXmlDocument( "OriginalInvoice.xml"); //create session key and encrypt via RSA public key byte [] IV = sender.CreateAndEncryptXmlSessionKey( "RsaExcludePrivateParams.xml", "SessionKeyExchange.xml"); //encrypt original XML document with session key sender.EncryptOriginalXmlDocument( "OriginalInvoice.xml", "RsaExcludePrivateParams.xml", "SessionKeyExchange.xml", "EncryptedInvoice.xml"); //encrypt XML document with session key receiver.DecryptXmlDocument( "EncryptedInvoice.xml", "RsaIncludePrivateParams.xml", "SessionKeyExchange.xml", "DecryptedCreditInfo.xml", IV); } } class Sender { public void CreateOriginalXmlDocument( String originalFilename) { //establish the original XML document XmlDocument xmlDoc = new XmlDocument(); xmlDoc.PreserveWhitespace = true; xmlDoc.LoadXml( "<invoice>\n" + " <items>\n" + " <item>\n" + " <desc>Deluxe corncob pipe</desc>\n" + " <unitprice>14.95</unitprice>\n" + " <quantity>1</quantity>\n" + " </item>\n" + " </items>\n" + " <creditinfo>\n" + " <cardnumber>0123456789</cardnumber>\n" + " <expiration>01/06/2005</expiration>\n" + " <lastname>Finn</lastname>\n" + " <firstname>Huckleberry</firstname>\n" + " </creditinfo>\n" + "</invoice>\n"); //write original XML document to file StreamWriter file = new StreamWriter(originalFilename); file.Write(xmlDoc.OuterXml); file.Close(); //let the user know what happened Console.WriteLine( "Original XML document written to:\n\t" + originalFilename); } public byte [] CreateAndEncryptXmlSessionKey( String rsaExcludePrivateParamsFilename, String keyFilename) { //obtain session key for 3DES bulk encryption TripleDESCryptoServiceProvider tripleDES = new TripleDESCryptoServiceProvider(); //store IV and Key for sender encryption IV = tripleDES.IV; Key = tripleDES.Key; //fetch public only RSA parameters from XML StreamReader fileRsaParams = new StreamReader( rsaExcludePrivateParamsFilename); String rsaExcludePrivateParamsXML = fileRsaParams.ReadToEnd(); fileRsaParams.Close(); //RSA encrypt session key RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(rsaExcludePrivateParamsXML); byte[] keyEncryptedBytes = rsa.Encrypt(tripleDES.Key, false); //store encrypted 3DES session key in Base64 string String keyEncryptedString = Convert.ToBase64String( keyEncryptedBytes); //create XML document for 3DES session key exchange XmlDocument xmlKeyDoc = new XmlDocument(); xmlKeyDoc.PreserveWhitespace = true; //add EncryptedKey element to key XML XmlElement xmlEncryptedKey = xmlKeyDoc.CreateElement("EncryptedKey"); xmlKeyDoc.AppendChild(xmlEncryptedKey); XmlAttribute xmlCarriedKeyName = xmlKeyDoc.CreateAttribute("CarriedKeyName"); xmlCarriedKeyName.Value = "My 3DES Session Key"; xmlEncryptedKey.Attributes.Append( xmlCarriedKeyName); //add the EncryptionMethod element to key XML XmlElement xmlEncryptionMethod = xmlKeyDoc.CreateElement("EncryptionMethod"); xmlEncryptedKey.AppendChild(xmlEncryptionMethod); XmlAttribute xmlAlgorithm = xmlKeyDoc.CreateAttribute("Algorithm"); xmlAlgorithm.Value = "http://www.w3.org/2001/04/xmlenc#rsa-1_5"; xmlEncryptionMethod.Attributes.Append( xmlAlgorithm); //add KeyInfo element to key XML XmlElement xmlKeyInfo = xmlKeyDoc.CreateElement( "ds", "KeyInfo", "http://www.w3.org/2000/09/xmldsig#"); xmlEncryptedKey.AppendChild(xmlKeyInfo); //add KeyName element to key XML XmlElement xmlKeyName = xmlKeyDoc.CreateElement("ds", "KeyName", null); xmlKeyName.InnerText = "My Private Key"; xmlKeyInfo.AppendChild(xmlKeyName); //add CipherData element to key XML XmlElement xmlCipherData = xmlKeyDoc.CreateElement("CipherData"); xmlEncryptedKey.AppendChild(xmlCipherData); //add CipherValue element to key XML XmlElement xmlCipherValue = xmlKeyDoc.CreateElement("CipherValue"); xmlCipherValue.InnerText = keyEncryptedString; xmlCipherData.AppendChild(xmlCipherValue); //save key XML information xmlKeyDoc.Save(keyFilename); //let the user know what happened Console.WriteLine( "Encrypted Session Key XML written to:\n\t" + keyFilename); return IV; //needed by receiver too } public void EncryptOriginalXmlDocument( String originalFilename, String rsaExcludePrivateParamsFilename, String keyFilename, String encryptedFilename) { //load XML document to be encrypted XmlDocument xmlDoc = new XmlDocument(); xmlDoc.PreserveWhitespace = true; xmlDoc.Load(originalFilename); //get creditinfo node plaintext bytes to encrypt XmlElement xmlCreditinfo = (XmlElement)xmlDoc.SelectSingleNode( "invoice/creditinfo"); byte[] creditinfoPlainbytes = Encoding.UTF8.GetBytes(xmlCreditinfo.OuterXml); //load XML key document XmlDocument xmlKeyDoc = new XmlDocument(); xmlKeyDoc.PreserveWhitespace = true; xmlKeyDoc.Load(keyFilename); //get encrypted session key bytes XmlElement xmlKeyCipherValue = (XmlElement)xmlKeyDoc.SelectSingleNode( "EncryptedKey/CipherData/CipherValue"); byte[] xmlKeyCipherbytes = Convert.FromBase64String( xmlKeyCipherValue.InnerText); //create 3DES algorithm object for bulk encryption TripleDESCryptoServiceProvider tripleDES = new TripleDESCryptoServiceProvider(); //establish crypto stream using 3DES algorithm MemoryStream ms = new MemoryStream(); CryptoStream cs = new CryptoStream( ms, tripleDES.CreateEncryptor(Key, IV), CryptoStreamMode.Write); //write creditinfo plaintext to crypto stream cs.Write( creditinfoPlainbytes, 0, creditinfoPlainbytes.Length); cs.Close(); //get creditinfo ciphertext from crypto stream byte[] creditinfoCipherbytes = ms.ToArray(); ms.Close(); String creditinfoCiphertext = Convert.ToBase64String( creditinfoCipherbytes); //create EncryptedData in XML file XmlElement xmlEncryptedData = xmlDoc.CreateElement("EncryptedData"); XmlAttribute xmlType = xmlDoc.CreateAttribute("Type"); xmlType.Value = "http://www.w3.org/2001/04/xmlenc#Element"; xmlEncryptedData.Attributes.Append(xmlType); //add KeyInfo element XmlElement xmlKeyInfo = xmlDoc.CreateElement( "ds", "KeyInfo", "http://www.w3.org/2000/09/xmldsig#"); xmlEncryptedData.AppendChild(xmlKeyInfo); //add KeyName element XmlElement xmlKeyName = xmlDoc.CreateElement("ds", "KeyName",null); xmlKeyName.InnerText = "My 3DES Session Key"; xmlKeyInfo.AppendChild(xmlKeyName); //add CipherData element XmlElement xmlCipherData = xmlDoc.CreateElement("CipherData"); xmlEncryptedData.AppendChild(xmlCipherData); //add CipherValue element with encrypted creditinfo XmlElement xmlCipherValue = xmlDoc.CreateElement("CipherValue"); xmlCipherValue.InnerText = creditinfoCiphertext; xmlCipherData.AppendChild(xmlCipherValue); //replace original node with the encrypted node xmlCreditinfo.ParentNode.ReplaceChild( xmlEncryptedData, xmlCreditinfo); //save XML to encrypted file xmlDoc.Save(encryptedFilename); //let the user know what happened Console.WriteLine( "Encrypted XML document written to:\n\t" + encryptedFilename); } //information sender needs across method calls static byte [] IV; static byte [] Key; } class Receiver { public void EstablishXmlRsaParameters( String rsaIncludePrivateParamsFilename, String rsaExcludePrivateParamsFilename) { //create RSA object with new key pair RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); //store public and private RSA key params in XML StreamWriter fileRsaIncludePrivateParams = new StreamWriter( rsaIncludePrivateParamsFilename); fileRsaIncludePrivateParams.Write( rsa.ToXmlString(true)); fileRsaIncludePrivateParams.Close(); //store public only RSA key params in XML StreamWriter fileRsaExcludePrivateParams = new StreamWriter( rsaExcludePrivateParamsFilename); fileRsaExcludePrivateParams.Write( rsa.ToXmlString(false)); fileRsaExcludePrivateParams.Close(); //let the user know what happened Console.WriteLine( "RSA parameters written to:\n\t" + rsaIncludePrivateParamsFilename + "\n\t" + rsaExcludePrivateParamsFilename); } public void DecryptXmlDocument( String encryptedFilename, String rsaIncludePrivateParamsFilename, String keyFilename, String decryptedFilename, byte [] IV) { //load encrypted XML document XmlDocument xmlDoc = new XmlDocument(); xmlDoc.PreserveWhitespace = true; xmlDoc.Load(encryptedFilename); //get creditinfo node ciphertext bytes to decrypt XmlElement xmlEncryptedData = (XmlElement)xmlDoc.SelectSingleNode( "invoice/EncryptedData"); XmlElement xmlCipherValue = (XmlElement)xmlEncryptedData.SelectSingleNode( "CipherData/CipherValue"); byte[] creditinfoCipherbytes = Convert.FromBase64String( xmlCipherValue.InnerText); //load XML key document XmlDocument xmlKeyDoc = new XmlDocument(); xmlKeyDoc.PreserveWhitespace = true; xmlKeyDoc.Load(keyFilename); //get encrypted session key bytes XmlElement xmlKeyCipherValue = (XmlElement)xmlKeyDoc.SelectSingleNode( "EncryptedKey/CipherData/CipherValue"); byte[] xmlKeyCipherbytes = Convert.FromBase64String( xmlKeyCipherValue.InnerText); //fetch public only RSA parameters from XML StreamReader fileRsaParams = new StreamReader( rsaIncludePrivateParamsFilename); String rsaIncludePrivateParamsXML = fileRsaParams.ReadToEnd(); fileRsaParams.Close(); //RSA decrypt 3DES session key RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(rsaIncludePrivateParamsXML); byte[] keyPlainBytes = rsa.Decrypt(xmlKeyCipherbytes, false); //create 3DES algorithm object for bulk encryption TripleDESCryptoServiceProvider tripleDES = new TripleDESCryptoServiceProvider(); //establish crypto stream using 3DES algorithm MemoryStream ms = new MemoryStream( creditinfoCipherbytes); CryptoStream cs = new CryptoStream( ms, tripleDES.CreateDecryptor(keyPlainBytes, IV), CryptoStreamMode.Read); //read creditinfo plaintext from crypto stream byte[] creditinfoPlainbytes = new Byte[creditinfoCipherbytes.Length]; cs.Read( creditinfoPlainbytes, 0, creditinfoPlainbytes.Length); cs.Close(); ms.Close(); String creditinfoPlaintext = Encoding.UTF8.GetString(creditinfoPlainbytes); //write decrypted XML node to file StreamWriter fileplaintext = new StreamWriter(decryptedFilename); fileplaintext.Write(creditinfoPlaintext); fileplaintext.Close(); //let the user know what happened Console.WriteLine( "Decrypted XML credit info written to:\n\t" + decryptedFilename); } } When you run the XmlEncryption program, you will see the following output displayed in the console window. From this output, you can see the various XML files that are produced. RSA parameters written to: RsaIncludePrivateParams.xml RsaExcludePrivateParams.xml Original XML document written to: OriginalInvoice.xml Encrypted Session Key XML written to: SessionKeyExchange.xml Encrypted XML document written to: EncryptedInvoice.xml Decrypted XML credit info written to: DecryptedCreditInfo.xml Let's now take a look at the contents of each of these XML files. Here is the listing of the OriginalInvoice.xml file. As you can see, it is quite simple. It represents an invoice, which contains a list of items purchased and some credit card information. In this example we assume that the item list is not sensitive information, but the credit information must be hidden from view by unauthorized parties. <invoice> <items> <item> <desc>Deluxe corncob pipe</desc> <unitprice>14.95</unitprice> <quantity>1</quantity> </item> </items> < creditinfo > <cardnumber>0123456789</cardnumber> <expiration>01/06/2005</expiration> <lastname>Finn</lastname> <firstname>Huckleberry</firstname> </creditinfo> </invoice> Here is the RsaExcludePrivateParams.xml file, slightly reformatted to allow for the width of the printed page. As you can see, the only parts of the RSA key information that are made publicly available are the RSA modulus and exponent parameters. <RSAKeyValue> < Modulus >1x6LG6Hv3cf87U0n+3E2OZtxJAEZI...</Modulus> < Exponent >AQAB</Exponent> </RSAKeyValue> Here is the RsaIncludePrivateParams.xml file, reformatted to allow for the width of the printed page. You can see that several other private pieces of information, including the very sensitive parameters P, Q, and D, are contained in this XML file, so it is critical that this XML file never be exposed in the clear. This file must never be made available to others, so it should probably be stored in a password-protected, encrypted directory. <RSAKeyValue> < Modulus >1x6LG6Hv3cf87U0n+3E2OZtxJAEZIjzKkk9hDmg... </Modulus> < Exponent >AQAB</Exponent> < P >7vMj9Ji4CR+ObULD8q1sFgJwHiVLVJK4LKO9zA5KvFTtV...</P> < Q >5ngYdhg+0fxhv4Pu/Wl9eh/BvRzavGFRsPYl9AROD8UuA...</Q> < DP >5mGwkfzIu6scNEYCDLGeG55gIQCOH82SGyAIN3y0G96...</DP> < DQ >pmVtG86ThJ6YoGKMKXCBhKvrADQWBU6qYX7GljCJf79...</DQ> < InverseQ >1KGPyuuUOa3A7iNA00Ocsg4zwSZS3sb...</InverseQ> < D >kRNyMVKG2AVVmBweyL5TGYqxRNzQvHxPCVk1tJPOdYAdo...</D> </RSAKeyValue> Here is the SessionKeyExchange.xml file, reformatted to allow for the width of the printed page. You can see the encryption method is based on RSA, and the key name is provided as well. The most important piece of information is contained in the CipherValue tag, which provides the ciphertext representing the encrypted symmetric session key that is being exchanged. <EncryptedKey CarriedKeyName="My 3DES Session Key"> < EncryptionMethod Algorithm="http://... / xmlenc#rsa-1_5 " /> < ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/ xmldsig#"> <KeyName>My Private Key</KeyName> </ds:KeyInfo> <CipherData> < CipherValue >Uqth0M4Cu6vcRSGIiI9rzg/Hk... </CipherValue> </CipherData> </EncryptedKey> Here is the EncryptedInvoice.xml file, slightly reformatted to allow for the width of the printed page. You can see here that the original creditinfo tag has been replaced with the EncryptedData tag. The actual encrypted credit-card information is contained in the CipherValue tag. <invoice> <items> <item> <desc>Deluxe corncob pipe</desc> <unitprice>14.95</unitprice> <quantity>1</quantity> </item> </items> < EncryptedData Type="http://www.w3.org/2001/04/ xmlenc#Element "> <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/ xmldsig#"> <KeyName>My 3DES Session Key</KeyName> </ds:KeyInfo> <CipherData> < CipherValue >IrdwslX+xx3Ej2BYvDd3gFfuKw... </CipherValue> </CipherData> </EncryptedData> </invoice> Now, here is the final result. The decrypted data for the credit information is shown in the file named DecryptedCreditInfo.xml . As can be seen in the following listing, its contents match with the original data representing the credit-card information in the original file named OriginalInvoice.xml . < creditinfo > <cardnumber>0123456789</cardnumber> <expiration>01/06/2005</expiration> <lastname>Finn</lastname> <firstname>Huckleberry</firstname> </creditinfo> |