Secure .NET Apps With Cryptography
Use the .NET Framework's System.Security.Cryptography namespace to protect your sensitive application settings and communications.
by Travis Vandersypen

March 2003 Issue

Technology Toolbox: VB.NET, C#, ASP.NET, XML

Businesses' concerns about computer-system security are increasing in today's interconnected world. Management wants to ensure that employees can perform only the tasks for which they have rights, and to prevent third parties from reading and modifying sensitive information. Cryptography—the science of encryption—offers a solution. Microsoft created a simple set of classes within the .NET Framework to handle all the cryptographic services you need to have within an application. The System.Security.Cryptography namespace provides classes built around two main branches of encryption algorithms: SymmetricAlgorithm and AsymmetricAlgorithm. (The same namespace also provides a set of hashing algorithms stemming from HashAlgorithm; these are beyond this article's scope.) I'll explain the difference between symmetric and asymmetric algorithms and show you how to encrypt and decrypt data with either one in your applications (download the sample code here).

The classes that stem from SymmetricAlgorithm are private-key encryption algorithms; the ones that stem from AsymmetricAlgorithm are public-key encryption routines (see the sidebar, "All Encryption Algorithms Aren't Created Equal"). Symmetric encryption algorithms use a single, private key to encrypt and decrypt information. Encryption and decryption happen faster with only one key than they do for asymmetric algorithms, which use a combination of a public key and a private key. The .NET Framework's symmetric encryption algorithms include the Data Encryption Standard (DES), Ron's Code 2 (RC2), Rijndael, and Triple DES (which goes through three iterations of the DES algorithm) algorithms. Each of them serves as the super class for a .NET managed class (see Table 1).

Figure 1. The Initialization Vector Starts the Encryption Chain.

Each class implements the same interface (not a language interface, but an interface from a conceptual standpoint), which is based on an encryption key and an initialization vector (IV). You must define both properties for the encryption routines to work. The encryption algorithms employ a cipher block chaining method, which uses the previously encrypted cipher block to encrypt the current one; an IV serves as a "seed" that's used to encrypt and decrypt the first block of bytes (see Figure 1). This approach ensures that no two blocks of information, whether or not they're identical, produce the same block of encrypted text. This increases the encryption and decryption routines' complexity. A malicious hacker (or a computer) must guess the value of the IV as well as of the encryption key, which increases dramatically the amount of time it takes to break the encrypted information.

Use the CryptoStream Class
You use the same general process to perform encryption and decryption with any of the private-key algorithm classes: Instantiate the managed class, set the IV, set the encryption key, then use the CryptoStream class to stream the information through its encryption or decryption routine. Take an example that involves this XML document, which represents data you might want to protect with encryption:

<Configuration>
   <Data>
      <Provider>SQLOLEDB</Provider>
      <Server>(local)</Server>
      <User>sa</User>
      <Password></Password>
      <Database>Northwind</Database>
   </Data>
</Configuration>

The document contains simple configuration information for connecting to a Microsoft SQL Server database. It sets up the minimum amount of information necessary to construct the connection string for an OleDbConnection object to the Northwind database of SQL Server's local instance. Changes to this information can break the data access for your application easily, so you decide to encrypt the information to prevent unauthorized individuals from tinkering with it.

Begin by choosing an encryption algorithm—the Rijndael algorithm, for this article's purposes. Then, instantiate this cryptographic service provider in VB.NET:

Dim CryptoProvider As RijndaelManaged _
   = New RijndaelManaged()

Next, decide on the encryption key and the IV. Keep in mind that each is an array of bytes that determines the encryption algorithm's bit level directly. The RijndaelManaged class supports byte arrays of 16, 24, or 32 in length. A 16-byte array creates a 128-bit encryption level, and a 32-byte array creates a 256-bit encryption level. For demonstration purposes, choose arrays of 16 bytes long:

Dim key As Byte() = {11, 2, 7, 24, 16, _
   22, 4, 38, 27, 3, 11, 10, 17, 15, 6, 23}

Dim iv As Byte() = {1, 2, 3, 4, 5, 6, _
   17, 8, 9, 10, 11, 12, 13, 14, 15, 16}

You need the CryptoStream class in addition to the algorithm class. The CryptoStream object uses an input/output stream you feed into it to encrypt and decrypt the information the stream contains. Assume that the example XML document resides in a file called CrytpographyExamples.xml in the C: drive's root directory. This means you use a FileStream object to feed data into the CryptoStream object. You must create the "Encryptor" or "Decryptor" based on the encryption key and IV by using the CreateEncryptor or CreateDecryptor methods on the RijndaelManaged object. The CryptoStream object is a stream, so you must also indicate whether you're reading from or writing to it. Fortunately, the CryptoStream class's constructor takes care of most of this. This code creates a stream for encryption:

Dim filename As String = _
   "c:\SymmetricExample.xml"
   
Dim FileWriter As FileStream = New _
   FileStream(filename,FileMode.Create)

Dim CryptoWriter As CryptoStream = _
   New CryptoStream(FileWriter, _
   cryptoprovider.CreateEncryptor( _
   key,iv), CryptoStreamMode.Write)

This code creates a stream for decrypting a file:

Dim filename As String = _
   "c:\SymmetricExample.xml"

Dim FileReader As FileStream = New _
   FileStream(filename, FileMode.Open)

Dim CryptoReader As CryptoStream = _
   New CryptoStream(FileReader, _
   cryptoprovider.CreateDecryptor( _
   key,iv), CryptoStreamMode.Read)

Now that you have a stream for encrypting and decrypting data, you can feed it into an XmlTextReader object for decrypting or an XmlTextWriter object for encrypting:

Dim XmlDoc As XmlDocument

Dim XmlReader As XmlTextReader = New _
   XmlTextReader(CryptoReader)

XmlDoc = New XmlDocument()

XmlDoc.Load(XmlReader)

Protect Communications With Asymmetric Algorithms
One of the big problems you face in using symmetric algorithms is sending the encryption key and IV securely. If you can accomplish this, then you have a secure channel of communication, theoretically. This is where asymmetric algorithms enter the picture. Asymmetric, or public-key, encryption algorithms use a combination of a public key available to everyone and a private key available only to authorized individuals. Information encrypted with the public key can be decrypted only with the private key, and vice versa. However, the private key is seldom used to encrypt the information, because the public key is available to everyone. Typically, information is encrypted with the public key and decrypted with the private key. This ensures that only the intended recipients can decrypt and see the information.

Unlike symmetric algorithms, asymmetric algorithms use more of a manual process that converts arrays of bytes rather than streams of information. Although this process removes the complexity of chaining streams together, it replaces it with the complexity of the various type conversions you must perform between character arrays and byte arrays, and vice versa. Interestingly, it's much easier to convert from a byte array to a character array than the other way around. I suggest converting from a character array to a byte array the old-fashioned way: Use a for loop and the string object that contains a character array for each character in the string:

Dim Xml(Contents.Length - 1) As Byte

Dim n As Integer = 0

For n = 0 To Contents.Length - 1
   Xml(n) = Convert.ToByte(Contents.Chars(n))
Next

Going the other direction (where Xml is the byte array) is a much easier task:

Dim Text(Xml.Length - 1) As Char

Xml.CopyTo(Text, 0)

Unfortunately, all the facets of type conversions in .NET are beyond this article's scope.

Asymmetric algorithms' encryption keys are another drawback: In contrast to symmetric algorithms and their associated classes, no single property gives you a public key and/or a private key. The parameter values you use to construct the keys can differ depending on which asymmetric algorithm you use. Keep in mind that the key's size determines how long the byte array to be encrypted can be. The maximum length of the byte array you can encrypt with the RSACryptoServiceProvider is (key size/8) – 11.

Suppose you want to encrypt and decrypt the example XML document with the RSA algorithm. You need to instantiate an instance of the RSACryptoServiceProvider class. It's created with a default key size of 1,024 bits unless you specify otherwise; this means the maximum size of the byte array that can be encrypted is 117. Unfortunately, you're 20 bytes short, because the document's minimum size is 137 bytes even if all the elements are empty. You can pass the key size into the constructor for the RSACryptoServiceProvider class to solve this problem:

Dim CryptoProvider As RSACryptoProvider = New _
   RSACryptoProvider(2048)

Extend the Byte Array Length
The algorithm must create a public and private key pair that consists of 2,048 bits, so the previous line of code takes a little longer to execute than it would with the default key size of 1,024. However, using a key size of 2,048 means you can accommodate a byte array with a maximum length of 245. This is still a rather small number, but you can pass in an integer as large as 16,384 to the constructor, which gives you a maximum byte array size of 2,037. However, even at its largest key size, asymmetric encryption can accommodate only small amounts of data.

Once you've generated the public and private key pairs, you should store them somewhere for reuse. The RSACryptoServiceProvider class provides the ExportParameters and ToXmlString methods for this purpose. ExportParameters returns an RSAParameters structure you can then use to store information. In contrast, ToXmlString returns an XML representation of the RSAParameters structure, which you can then easily pass around, write to disk, and so on—taking special care to ensure that this information doesn't fall into the wrong hands. For instance, you could use one of the symmetric encryption algorithms to store the XML string that the RSACryptoServiceProvider class's ToXmlString method returns, before you write it to disk. Then, you can use the FromXmlString method when you need to reinstantiate an instance of it with the same key information.

You can encrypt and decrypt information easily between application sessions once you persist the key information. However, remember that the key size determines directly the maximum size of the byte array you can encrypt. You pass the byte array to the Encrypt method to encrypt information using the RSACryptoServiceProvider:

Dim Contents As Byte[] = _ 
   {101,60,102,104,107}
Dim Encrypt As Byte[] = _
   CryptoProvider.Encrypt(Contents, _
   False)

You make a call to the Decrypt method to decrypt the information:

Dim Decrypt As Byte[] = _
   CryptoProvider.Decrypt(Encrypt, _
   False)

Now, take a look at two complete classes that employ symmetric and asymmetric algorithms, respectively, for encrypting and decrypting the sample XML document (see Listings 1 and 2). Each class decrypts the document and stores the contents of the <Server>, <Database>, <User>, <Password>, and <Provider> elements into like-named fields on the class in the Decrypt method. In the Encrypt method, the class creates an encrypted XML document with the specified structure using the values stored in the fields on the class.

Keep in mind that each type of encryption algorithm is suited to specific functions. A symmetric encryption algorithm is probably the best choice if your application needs to encrypt sensitive information. An asymmetric encryption algorithm is more suited for the job if you wish to send sensitive information over public communication paths. Although asymmetric algorithms limit the amount of information you can encrypt at one time, you can create a series of byte arrays, encrypt each, and add them together at the end to create a complete encryption picture.

About the Author
Travis Vandersypen is the chief software architect for DilMad Enterprises, where he uses Visual Studio and VS.NET to write custom applications for clients. He's the coauthor of XML and Web Services Unleashed (Sams), the lead author of ADO.NET Unleashed (Sams, forthcoming), and a frequent speaker at developer conferences and user groups. Reach him at .