Using Encryption in .NET

Getting Started

Getting Started

For the sample code and instructions in the remainder of this article, we will be using the Rijndael cipher as implemented in the RijndaelManaged FCL class. OK, let's get started with the code. Our first steps will be to create an instance of the cipher, set the key and block sizes and generate a random key. The following code snippet demonstrates this procedure:

RijndaelManaged InitCipher()
{
  // Create an instance of the cipher
  RijndaelManaged cipher = new RijndaelManaged();
  // Set the key and block size.
  // Although the key size defaults to 256, it's better to be explicit.
  cipher.KeySize = 256;
 
  // BlockSize defaults to 128 bits, so let's set this
  // to 256 for better security
  cipher.BlockSize = 256;
 
  // GenerateKey method utilizes the RNGCryptoServiceProvider
  // class to generate random bytes of necessary length.
  cipher.GenerateKey();
 
  return cipher;
}

The code above initializes a new instance of RijndaelManaged, sets the appropriate key and block sizes and creates a new random key. The value of the key can be retrieved by accessing the Key property of the cipher.

Assuming that the messages encrypted by our cipher are to be transmitted across the network, the generated key must somehow be securely transmitted to the other party. Just how to accomplish this safely is a problem beyond the scope of this article, but this is generally accomplished by using a longer term key, which is used to encrypt "session" keys for transmission between parties. This session key is then used for a relatively short duration communications session. If you are simply encrypting data to be stored on a medium such as a hard drive, you simply need to devise a means of securely storing the key so it can later be used to decrypt the data. I say simply, but this is yet another tricky problem beyond the scope of this article. In fact there is really no way to completely securely store a key online. There are ways to make it difficult for an attacker to access the key, but the fact is that the key is still present on a computer somewhere. If an attacker is able to gain privileged access to the computer, he can read the key. A more secure way to store keys is offline, such as on a disk locked in a safe.

We have a problem now. What if we are the "other party" described above? That is, what if the key has already been decided on by another who is initiating communication with us and we need to make use of the same key? In this case, you can initialize the key directly by assigning the key byte array to the Key property of the cipher. Let's refactor our initialization so that we can add an overload that allows us to play the role of either initiator or responsor. Here's the new code:

RijndaelManaged CreateCipher()
{
  RijndaelManaged cipher = new RijndaelManaged();
  cipher.KeySize  = 256;
  cipher.BlockSize = 256;
  return cipher;
}
RijndaelManaged InitCipher()
{
  RijndaelManaged cipher = CreateCipher();
 
  cipher.GenerateKey();
 
  return cipher;
}
RijndaelManaged InitCipher(byte[] key)
{
  RijndaelManaged cipher = CreateCipher();
  cipher.Key = key;
 
  return cipher;
}

This modified code will now allow us either to initiate a session or participate in an already established session. However, we obviously can't encrypt or decrypt messages yet. Let's create a class, CipherWrapper, to which we will later add encryption and decryption methods.

class CipherWrapper
{
  RijndaelManaged _cipher = null;
  public CipherWrapper()
  {
    _cipher = InitCipher();
  }
  public CipherWrapper(byte[] key)
  {
    _cipher = InitCipher(key);
  }
  public byte[] Key
  {
    get { return _cipher.Key;  }
    set { _cipher.Key = value; }
  }
}

Now we can simply copy/paste our initialization functions into the CipherWrapper class. Note that the key initialization is taken care of by the constructor and a property has been added that allows us to change the key, if need be.

Blocks and Padding

One peculiarity of block ciphers that you should be aware of is that they must operate on entire blocks of data. Data less than one block in length can't be encrypted. So what happens if (as is generally the case) the length of our plain text is not an even multiple of the block size? Enter padding. Padding is extra data added to the end of the message to force the length to be an even multiple of the block size. There are several methods for accomplishing this. These methods are called padding modes, of which there are three supported by version 1.1 of the FCL (the current version as of this writing): None, PKCS7 and Zeros. The padding mode is set by assigning a value from the PaddingMode enumeration to the Padding property of an instance of the cipher.

The PaddingMode.None mode is really not a padding mode at all. If you specify PaddingMode.None, you're telling the cipher that you don't want any padding performed at all. In this case, you must ensure that you only attempt to encrypt data whose length is an even multiple of the block size. If the data doesn't meet this requirement, you will get an exception stating that this is the case. The PaddingMode.Zeros mode simply pads the data with enough zeros to cause the length of the message to be an even multiple of the block size. The problem with PaddingMode.Zeros is that it is not reversible on decryption. Suppose the cipher encounters data that decrypts with five zero bytes at the end of the message. Are there five bytes of padding or are there 4 bytes of padding and an actual zero byte in the original plain text? How would the cipher be able to know? It really can't tell. If you use the PaddingMode.Zeros mode, you must transmit the actual length of the data as part of the message so that when the data is decrypted, the decrypting party will know how many bytes of plain text there are so they can strip the padding from the decrypted message.

The PaddingMode.PKCS7 mode is more interesting. This padding mode fills out the last block of the message with a sequence of bytes, the value of each of which is equal to the total number of padding bytes. For instance, given a block size of 128 bits and a final block plain text value of [AA BB CC DD EE FF], the padding string would be "0A 0A 0A 0A 0A 0A 0A 0A 0A 0A". The 0A value indicates that there are 10 total padding bytes. The padded block would then look like so: [AA BB CC DD EE FF 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A]. In order to reverse this, the cipher simply examines the value of the last byte in the block, verifies that the appropriate number of bytes containing this value exist at the end of the message, and then removes the padding from the decrypted data. You may be wondering what happens if our data length is a perfect multiple of the block size. In this scenario, PaddingMode.None and PaddingMode.Zeros add no padding. However, in the case of PaddingMode.PKCS7, padding must be added because the cipher must be able to reverse even a no-padding situation. In this case, an additional block must be added to the plain text and the value of each byte set to the block size in bytes. In the case of a 128-bit block, a block containing [10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10] would be added to the end of the message.

A couple of new padding modes are added to the .NET 2.0 FCL, ANSIX923 and ISO10126. These modes operate in a manner very similar to PKCS7. Each of the two new modes sets the value of the final byte equal to the total number of padding bytes. ANSIX923 then sets the remaining bytes to zero, while ISO10126 places random data in the remaining padding bytes. If you are using the 1.1 version of the FCL, I would recommend using the PKCS7 mode because it requires the least amount of effort and care on your part. Although, fortunately, this is the default, I would still recommend explicitly setting the Padding property of your cipher to PaddingMode.PKCS7 in your code. Let's modify our CreateCipher method to do this:

RijndaelManaged CreateCipher()
{
  RijndaelManaged cipher = new RijndaelManaged();
  cipher.KeySize  = 256;
  cipher.BlockSize = 256;
  // Here's the new line:
  cipher.Padding = PaddingMode.PKCS7;
  return cipher;
}

If you happen to be using the 2.0 version of the FCL, I would recommend using the ISO10126 mode as it adds random padding bytes, which reduces the predictability of the plain text. In 2.0, PKCS7 is still the default, so in this case, explicitly setting the Padding property to PaddingMode.ISO10126 will be mandatory. In recommending these modes, I realize that you won't always have a choice. You're not operating in a vacuum and you need to play nicely with the other parties with whom you're communicating. However, given the choice, go with PKCS7 in 1.1 and ISO10126 in 2.0.

You might also like...

Comments

About the author

Steve Johnson United States

Steve is a senior developer and consultant for 3t Systems, Inc. in Denver, CO, where he spends his days doing architecture, training, and "in-the-trenches" development. Steve began programming W...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“C++: an octopus made by nailing extra legs onto a dog.” - Steve Taylor