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.

Cryptographic Solutions for .NET Developers: Hashing and Encryption

Hashing as a Way to Store Secrets

Now that you know the basics of encrypting and decrypting data, let me fulfill my promise of talking about hashing. So, where would these hash functions be useful?

Hash functions have two important applications: First, they can be used to store secrets (for example, passwords); and secondly, they can be used to check whether a message has been modified during transmission or storage.

Assume you have a web application that allows users to log in with an e-mail address and password. You could store these credentials in an SQL database, but storing the password directly isn't a very good practice. Instead of the user's password, you could store the hash of that password in the database. Then, when the user logs in, you would compute a hash of the provided password, and compare it to the one stored in the database. If they match, the password is valid. Remember, this works because only one input can become the hashed value. This very feature of hashes can also be used to detect transmission errors, for example.

Storing the hash instead of the password thus gives you a large advantage. Even if somebody would get their hands on the database data, the attacker wouldn't be able to know the original password. Although storing the hashed password in the database is a good way to improve security, it is also a double-edged sword: If you wanted to provide your user an "I Forgot My Password" type of function, you too cannot recover the original password back from the hash only.

Two of the most important hash algorithms are called MD5 and SHA. The .NET Framework supports both of these algorithms, and I'm providing a sample application called MD5Hash to show how the MD5 algorithm can be used. The example application is a command-line application, similar to the previously discussed RC2Encrypt and RC2Decrypt applications.

Hashing with the System.Security.Cryptography.MD5 Class

Just as with the encryption algorithms, the .NET hash algorithm implementations also have their home in the System.Security.Cryptography namespace. Using hash algorithms is simple. You would first create an instance of a hash class, and then call its ComputeHash method. This method is overloaded, and by default takes either a byte array or a stream object as input.

Here are few lines of code from the sample application:

private static void CalculateMD5Hash(string literal)
{
   // step 1, calculate MD5 hash from literal given as input
   MD5 md5 = MD5.Create();
   byte[] byteBuffer = System.Text.Encoding.ASCII.GetBytes(literal);
   byte[] hash = md5.ComputeHash(byteBuffer);

   // step 2, convert byte array to hexadecimal string
   string hashHex = ByteArrayToHexString(hash);
   Console.WriteLine("The MD5 has for the literal \"" +
                     literal + "\" is:");
   Console.WriteLine(hashHex);
}

Given an input string (literal) such as "abc", this method would write the following hash to the screen:

900150983CD24FB0D6963F7D28E17F72

Calculating a hash from a stream, such as a FileStream, is equally simple. The above code can stay basically the same, except for the ComputeHash call:

FileStream input = new FileStream(filename, FileMode.Open);
MD5 md5 = MD5.Create();
byte[] hash = md5.ComputeHash(input);

In the case of the MD5 algorithm, the returned byte array is always 16 bytes (128 bits) in length. To convert this array to a hexadecimal string, you can use the following method:

private static string ByteArrayToHexString(byte[] hash)
{
   StringBuilder sb = new StringBuilder();
   for (int i = 0; i < hash.Length; i++)
   {
      sb.Append(hash[i].ToString("X2"));
   }
   return sb.ToString();
}

Notice how the byte type's ToString method is used with the "X2" format string. If you want lower-case hexadecimal numbers (A to F), simply specify "x2" instead.

Conclusion

In this article, you've seen how two basic cryptograph functions, encryption and hashing, can be used to improve the security of your applications. Although cryptography is a complex field of computer science, using the implementation classes in .NET Framework is quite straightforward.

I've tried to provide simple and easy-to-follow examples in C#, and you can use the code as basic building blocks in your own applications. And, even if you would use Visual Basic .NET or even Delphi.NET from Borland/CodeGear, the same classes are at your disposal.

Good luck with improving the security of your applications!

About the Author:

Jani Järvinen is a software development trainer and consultant in Finland. He is a Microsoft C# MVP and has written dozens of magazine articles and published two books about software development. He is a group leader of a Finnish software development expert group at ITpro.fi. His frequently updated blog can be found at http://www.saunalahti.fi/janij/. You can send him mail by clicking on his name at the top of the article.



About the Author

Jani Jarvinen

Jani Jarvinen is a software development trainer and consultant in Finland. He is a Microsoft C# MVP, a frequent author and has published three books about software development. He is the group leader of a Finnish software development expert group at ITpro.fi and a board member of the Finnish Visual Studio Team System User Group. His blog can be found at http://www.saunalahti.fi/janij/. You can send him mail by clicking on his name at the top of the article.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds