Cryptographic Solutions for .NET Developers: Hashing and Encryption

Cryptography is an important part of many applications. Even if your application doesn’t process credit card data or military information, it is likely that your application will need to store secrets such as usernames or passwords.

Cryptography allows you to store and transmit sensitive data in such a way that outsiders cannot read the data. Assume that you would need to send a password from a client application to a server over the Internet. Without cryptography, you would need to send the password in plaintext; in other words, fully readable. If somebody would listen to the network traffic, he or she could easily steal your password.

Luckily, mathematics and cryptography help you make the job of the malicious listener much more difficult. With the process known as encryption, you could convert the password into non-intelligible data known as ciphertext, and make it very hard to convert it back to the plaintext version.

Another tool is hashing. Hashing is way to calculate a fixed-length “fingerprint,” the hash, of some input data in such a way that no two inputs produce the same output (hash value). This means that each time the input changes, the hash changes as well. Also, the hash is always of fixed length, and is not related to the input length.

There are different ways to encrypt and hash data. Different algorithms are needed because there are diverse needs for encryption and hashing. Sometimes you want to be as secure as possible, but sometimes you want to trade a little security with encryption speed or simplicity, for example. The names of these algorithms are often short acronyms. For example, some of the most common algorithms are named RC2, RSA, AES, and SHA.

.NET Support for Encryption and Hashing

The .NET Framework contains extensive support for cryptographic needs, and in this article you will learn basic encryption and hashing along with C# code examples. Before firing up Visual Studio, you should first understand some of the most common needs for cryptography.

One common need is to store or send information in encrypted format to keep your data out of prying eyes. For example, you might want to transmit accounting information from one machine to another, or store sensitive sales data in a database. Encryption would help here.

Secondly, you might need to store a password in your applications. This need can be twofold: You could either have the need to access other systems, or you might need to verify that a user’s password is correct while he logs in to your application. In these cases, you could use both encryption and hashing.

Most classes in the .NET Framework related to cryptography can be found from the System.Security.Cryptography namespace. From this namespace, you can find classes that implement many common cryptography algorithms, such as AES, RSA, SHA, and MD5. All the implementation classes inherit from certain abstract base classes, such as SymmetricAlgorithm, AsymmetricAlgorithm, and HashAlgorithm.

To use these implementation classes, you first need to be able to select the correct algorithm for your needs. For example, to use password-based encryption, select symmetric encryption algorithms such as AES or RC2. Or, if you prefer asymmetric algorithms needing both public and private keys, select RSA, the only option in the basic class library. For hashing purposes, you could select MD5 or SHA, for instance.

Because many parts of the .NET Framework work with streams, it is not surprising to find that the cryptographic classes also can utilize streams. Speaking of streams, there’s a special class called CryptoStream, which is very central to using cryptography in .NET. You will become familiar with this class when you look at the example application in the next section.

Example of Encryption with RC2

I’ve written two sample applications using .NET cryptography that work as a pair: RC2Encrypt and RC2Decrypt. These applications are simple console applications that take a filename and a password as input, and then encrypt or decrypt the given file with the RC2 algorithm. RC2 is the name of one of those encryption algorithms that .NET supports. You can download both these sample applications.

Console application parameter handling aside, the most interesting method in the encrypting application is the EncryptFile method, shown here:

using System.IO;
using System.Security.Cryptography;

private static void EncryptFile(string filename, string password)
{
   // parameter checks
   bool ok = CheckParameters(filename, ref password);
   if (!ok) return;
   // start encrypting the file
   RC2 rc2 = RC2.Create();
   byte[] initvector = new byte[8] { 83, 123, 28,
                                     95, 70, 231, 117, 156 };
   byte[] key = System.Text.Encoding.ASCII.GetBytes(password);
   string outputFilename = Path.ChangeExtension(filename, "rc2enc");
   ICryptoTransform transform = rc2.CreateEncryptor(key, initvector);
   FileStream input = null;
   FileStream output = null;
   CryptoStream crypto = null;
   try
   {
      input = new FileStream(filename, FileMode.Open);
      output = new FileStream(outputFilename, FileMode.Create);
      crypto = new CryptoStream(output, transform,
                                CryptoStreamMode.Write);
      // encrypt file data
      CopyStream(input, crypto);
   }
   finally
   {
      if (crypto != null) crypto.Close();
      if (output != null) output.Close();
      if (input  != null) input.Close();
   }
   Console.WriteLine("File encrypted.");
}

The EncryptFile method takes a filename (with an optional path) and a password as parameters, creates classes to help in the encryption process, and then copies the input stream (the source file) to the output stream (the output file). Walk through the code.

After initial parameter checks (such as that the file exists), two byte arrays are created: one for the so-called key, and another for the initialization vector (or iv for short). These two-byte sequences are very important in the cryptography process: Unless the combination of both the key and the initialization vector are exactly the same when encrypting and decrypting, the decryption of the previously encrypted data won’t work and you might get a CryptographicException with the message “Bad data.” Thus, it is important to keep both known when you attempt decryption.

Now, take a look at the code inside the try block in the listing. Here, two file streams are initialized, one for the input file (plaintext) and another for the to-be-created ciphertext file; in other words, the encrypted file. Thirdly, an instance of the CryptoStream class is created.

The CryptoStream is a special stream class that needs a stream object, a transformation, and a mode that tells whether the given stream should be read or written. Of these parameters, the transformation requires the most thought. Simply put, it is the encryption algorithm that you want to use to encrypt the data. This parameter is needed because CryptoStream can be used with many different algorithms, not just one.

Creating Transformation Objects

How do you create this transformation object, then? To do this, you need to first create an instance of the algorithm class (in this case, the RC2 class from the System.Security.Cryptography namespace), and then call its CreateEncryptor method. This method is declared as a virtual method in the abstract base class SymmetricAlgorithm, from which the RC2 class is derived. This method returns an ICryptoTransform interface, which you can then pass in to the CryptoStream constructor.

Some further discussion is in order. When the code creates an instance of the RC2 class just after validating the parameters, you might notice that I don’t simply call constructor of the RC2 class, but instead call the static Create method. This is because, in fact, the RC2 class is an abstract class. But by calling the Create method, I’m able to get a concrete implementation of some descending class that inherits from RC2.

All this can sound complex to you, but the idea is that this way, third parties can plug in their own implementations into the .NET cryptography framework. By calling the Create method, you easily can specify at runtime which implementation of the RC2 algorithm you want. If you don’t pass a string (name) to the Create method, .NET’s default implementation will be selected, as I’ve done.

Because you now have an implementation instance of the RC2 algorithm, you can proceed to call the CreateEncryptor method. This method needs the key and initialization vector byte arrays, and returns the ICryptoTransform interface. Note that, for simplicity, the ASCII characters in the password string are simply being converted to bytes to create the key. For this to work, the password must be of certain length, in the case of this RC2 algorithm implementation, 5 to 16 bytes. This corresponds to 40- to 128-bit key lengths (these values can be queried through the LegalKeySizes property).

The initialization vector is created as a hard-coded byte array. When you create an instance of the RC2 implementation class by calling RC2.Create, both the key and initialization vector are initialized with random data. However, as you will recall, both must be exactly the same if you want to later decrypt the encrypted data. Because the simple example application won’t save the initialization vector anywhere and they key is supplied by the user, one option is to have a hard-coded initialization vector (another one would be to derive the vector from the key).

Copying the Stream and Inverting the Process

Up to now, you’ve learned how the cryptography classes in .NET are used to create an encryptor object and the transformation object. Next, you’ll see what this line of code does:

CopyStream(input, crypto);

As the name suggests, this custom method simply copies data from the source stream (input) to the destination stream (your crypto stream in the case). Remember that, because you are working with files (FileStream objects), the CryptoStream is chained to the output FileStream, so that whenever data is written to the crypto stream, it goes through the transformation process (encryption) and is then written to the output file. Here’s the implementation of the CopyStream method:

private static void CopyStream(Stream input, Stream output)
{
   const int blockSize = 64 * 1024;    // 64k
   // copy data block by block
   byte[] buffer = new byte[blockSize];
   int bytesRead = input.Read(buffer, 0, blockSize);
   while (bytesRead > 0)
   {
      output.Write(buffer, 0, bytesRead);
      bytesRead = input.Read(buffer, 0, blockSize);
   }
}

This method is rather simple, and loops through the input stream in 64-kilobyte blocks until all data has been read. Note that it is very important to remember to close all the streams involved in the encryption process; otherwise, you might not flush all encrypted data to the output stream and thus to the file on disk. It is best to do all this inside a try-finally block.

Before going into hashing, you should understand the opposite operation to encryption: decryption. If you take at the look at the code shown previously from the RC2Encrypt sample application and compare it to the RC2Decrypt application, you will notice that there are only minor differences. In fact, only three lines of code need to be changed to decrypt data instead of encrypt it.

In RC2Encrypt, you needed to create the encryptor object by calling the CreateEncryptor method. But for decryption purposes, you would call its counterpart, the CreateDecryptor method. This returns a transformation object “the other way around.” The second change is the creation of the CryptoStream instance. When encrypting, the line is as follows:

crypto = new CryptoStream(output, transform, CryptoStreamMode.Write);

But when decrypting, it needs to be:

crypto = new CryptoStream(input, transform, CryptoStreamMode.Read);

Finally, the CopyStream method needs to be called as follows:

CopyStream(crypto, output);

Other than these differences, the two sample applications are functionally identical.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read