Understanding .NET Encryption

These days, all you ever seem to see in the news are stories about websites getting hacked, and how many developers don't do enough to protect the applications they write from the bad guys. Although that may be true in some places, there's certainly no shortage of functionality built into the .NET framework to help you encrypt and secure your data. The 'System.Security.Cryptography' namespace has a treasure trove of functionality that allows you to perform hashes, two-way data encryption, and a good few utility scenarios related to both of those subjects.

Technically, when it comes to encryption, there are actually two very different things you can do. The first scenario (usually known as hashing) is the process of taking some piece of input data and providing a one-way, cryptographic signature on it. MD5 is a very good example of this, whereby you can take any bit of input data and turn it into a 16-character signature that can never ever be used to obtain the original data. For a long time, standard hashes such as MD5 were the primary means by which passwords, user names, and other sensitive information were stored in many databases.

These days however (especially with the rise in cheap, high-speed processing power), many ciphers like MD5 need extra data appending to them, such as a 'salt' value to protect them against massive checking databases known as 'Rainbow Tables'. This is not to say that things such as MD5 don't still have a use, however, as we'll see in just a moment.

The second scenario typically addressed is the process of protecting data while it's in transit. For web server and http communication, this is typically handled automatically using the secure HTTPS protocol. There are, however, many other instances where you might want to encrypt a payload; one that springs to mind is Email. In this situation, the sender needs to be able to encrypt the contents of a message, then send that message over a public network, where the receiver can then un-encrypt that message, revealing the original contents for them to read.

Among the functionality available in the .NET security namespace, this second case is handled by 'asymmetric public key encryption' amongst others.

Some Practical Uses

We'll take a look at a two-way example soon, but first let's take a quick look at one way MD5 can still help with a quite surprising task. As I mentioned in the previous section, MD5 is now relatively easy for those who have the means to reverse back to an original phrase.

This is possible, because MD5 will always produce the same signature for the same input no matter how many times you ask it. The only way you can prevent this is by picking a random key, phrase, or some other extra bit of information known ONLY to the system this encryption is being performed on. This extra bit of information, known as a salt, then makes the data being encrypted different enough that the signature does not match what would be produced had the data just been encrypted on its own. However, forgetting about using this to encrypt something like passwords for a moment, there is actually one thing that MD5 is exceptionally good for, and that's detecting changes.

Let's imagine, for a moment, that you have a library of corporate documents, and in a secure database somewhere, you've taken those documents, performed an MD5 hash of the contents of said documents, then saved that hash in a secure location. If one of those documents were then changed by an unauthorised person, it would be quite a simple process to loop through them all, reading out the contents, recalculating the hash and comparing it to the hash you have stored.

Hashes are so good at detecting changes like this that many file protection programs and antivirus tools employ them in some way to help protect files on your system. A good hash can detect something as small as an individual bit being changed in a file, which also means there also very good at confirming a file you've downloaded has arrived without any encryption.

The following code will take a simple string and produce an MD5 hash of that string for you:

byte[] theHash;
string simpleString = "Some Text The Hash";
using(MD5 md5 = MD5.Create())
{
   theHash = md5.ComputeHash
      (Encoding.ASCII.GetBytes(simpleString));
}

This will produce a hash for the text in 'simpleString' as a byte array, which you then can use something like the following to turn into a hex number that you can easily use.

StringBuilder strBuilder = new StringBuilder();
for (int index = 0; index < theHash.Length; index++)
{
   strBuilder.Append(data[index].ToString("x2"));
}
string hexHash = strBuilder.ToString();

Putting this code into a console app and running it should give you something like the following

Encrypt1
Figure 1: Running a console app

This was just using a simple constant string; however, using a file stream, or just grabbing all the bytes of a file into a byte array is just as simple. As way of a bit of food for thought, I actually have a utility I've written that uses this very method to look around on my hard drive for duplicate images and MP3 files.

Moving on, however, let's get a bit more creative, and have a brief look at how to do the two-way encryption.

Public key encryption works by having two security keys: one private, and one public. Each key can work only one way at a time; that is, either key can be used to Encrypt or Decrypt some data, but NEVER both. What this means in practice is that if you encrypt the data with one key, you need the other key to decrypt it.

If you're sending data, then usually the intended recipient will make his/her public key available. You would then use this public key to encrypt the data, knowing that the recipient can then use their private key to decrypt the data and reveal the original payload.

To use the public/private key encryption functions, you first need to generate a public & private key pair. Generating a suitable pair of files to use with RSA based encryption is relatively easy and can be achieved with the following code:

private static CspParameters cspParameters;
private static RSACryptoServiceProvider rsaProvider;
private static string publicKey;
private static string privateKey;

// See http://msdn.microsoft.com/en-us/library/
// system.security.cryptography.cspparameters.
// providertype(v=vs.110).aspx for other values
private const int PROV_RSA_FULL = 1;

static void Main()
{
   cspParameters = new CspParameters();
   cspParameters.ProviderType = PROV_RSA_FULL;
   cspParameters.Flags = CspProviderFlags.NoFlags;
   cspParameters.KeyNumber = (int)KeyNumber.Exchange;
   rsaProvider =
      new RSACryptoServiceProvider(cspParameters);

   publicKey = rsaProvider.ToXmlString(false);
   privateKey = rsaProvider.ToXmlString(true);

   File.WriteAllText(@"d:\publickey.xml", 
      publicKey, Encoding.ASCII);
   File.WriteAllText(@"d:\privatekey.xml", 
      privateKey, Encoding.ASCII);

}

This will save the public and private keys into the XML files specified, and then you can use those XML files when encrypting and decrypting text. Needless to say, the private key file needs to be stored away, far from prying eyes. Because the crypto provider produces standard strings, you can easily store the strings anywhere you please. I just popped them into XML files for convenience; it's up to you where you hide them.

Once you've generated some keys, it's very easy to use them to encrypt some text, as the following code shows:

private static CspParameters cspParameters;
private static RSACryptoServiceProvider rsaProvider;
private static string publicKey;
private static string privateKey;
private static byte[] unencryptedFile;
private static byte[] encryptedFile;

private const int PROV_RSA_FULL = 1;

static void Main()
{
   cspParameters = new CspParameters();
   cspParameters.ProviderType = PROV_RSA_FULL;
   cspParameters.Flags = CspProviderFlags.NoFlags;
   cspParameters.KeyNumber = (int)KeyNumber.Exchange;
   rsaProvider =
      new RSACryptoServiceProvider(cspParameters);

   publicKey = File.ReadAllText(@"d:\publickey.xml");
   rsaProvider.FromXmlString(publicKey);

   unencryptedFile = Encoding.ASCII.GetBytes("This is some
      text for me to encrypt, it can't be too long.");

   encryptedFile = rsaProvider.Encrypt(unencryptedFile, true);

   File.WriteAllBytes(@"d:\encryptedtest.bin",
      encryptedFile);

}

One thing you have to be careful of, though, is that you can see the text I'm encrypting is not very long. RSA, because of the way it works, has a length restriction. This length restriction varies depending on how many bits you specify for the key, something I'm not doing here. I'm just going with the defaults.

You might be thinking to yourself, what good is the API then, if I can't encrypt large payloads. Well, you can, and there are some (such as the Diffie Hellman algorithm) that allow you to do so.

The main purpose with the two-way public/private key encryption routines is to allow secure transport of a smaller piece of information, such as a single symmetric password or key phrase, one that's to be kept secure only until the other party receives it. At that point, both parties would then keep this one key a secret between them both to work on larger volumes of data.

For now, however, let's finish this post off by looking at the code that would be used to decrypt the string we previously encrypted.

private static CspParameters cspParameters;
private static RSACryptoServiceProvider
   rsaProvider;
private static string publicKey;
private static string privateKey;
private static byte[] unencryptedFile;
private static byte[] encryptedFile;

private const int PROV_RSA_FULL = 1;

static void Main()
{
   cspParameters = new CspParameters();
   cspParameters.ProviderType = PROV_RSA_FULL;
   cspParameters.Flags =
      CspProviderFlags.NoFlags;
   cspParameters.KeyNumber =
      (int)KeyNumber.Exchange;
   rsaProvider = new
      RSACryptoServiceProvider(cspParameters);
   privateKey =
      File.ReadAllText(@"d:\privatekey.xml");

   rsaProvider.FromXmlString(privateKey);

   encryptedFile =
      File.ReadAllBytes(@"d:\encryptedtest.bin");

   string unencryptedFileString =
      Encoding.ASCII.GetString(rsaProvider.Decrypt
      (encryptedFile, true));

}

Dealing with full file streams and symmetric keys is a little too much for this article at the moment; we'll cover that in the future. For now, though, this will encrypt/decrypt small amounts of data. There's actually nothing stopping you from reading a file in small, known-size chunks and using this to encrypt an entire file. I definitely wouldn't recommend it, though.

If there's a topic that interests you in .NET, or you've found a strange API you never knew was there and want to know how it works, please leave a comment below, I'll happily do what I can to write a post on it in this column. You also can generally find me hanging around in the Linked .NET users group (otherwise known as Lidnug) on the Linked-in platform, or you can find me on Twitter as @shawty_ds.



Related Articles

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

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date