Send a suggestion!

We're building a brand new version of the site, and we'd love to hear your ideas

Members

Technology Zones

IBM Learning Center

Articles

Hosted By

MaximumASP

Info

Rated
Read 55,062 times

Contents

Related Categories

Using Encryption in .NET - Getting Started

sjjohnson

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.

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 Windows on version 3.1 in 1995 using only C, the SDK, and Petzold's classic "Programming Windows 3.1". He was a hobbyist programmer until February 1999, when he turned professional. Steve particularly enjoys learning internals and behind-the-scenes details and developing low-level infrastructure code. Steve currently resides in Highlands Ranch, CO with his wife Kathleen and two cats.

Comments

  • RE: varbinary

    Posted by bmills on 21 May 2005

    I totally agree that varbinary could have been used. My only problem with that solution is that it seems difficult to work with binary data in ADO.NET and SQL Server. I may be wrong. I haven't trie...

  • Varbinary

    Posted by nuikeoni on 19 May 2005

    Couldn't you also store this in a Varbinary field instead of trying to convert it to a string for a varchar field?

  • File Encrypting

    Posted by nuikeoni on 18 May 2005

    Dear Steve,
    I was wondering if you had some insight as to how to best to file encrypting with the rijndael encryption. Most examples I've found only give info. about how to encrypt messages. I know...

  • Posted by sjjohnson on 24 Apr 2005

    Exactly. If you don't use the same IV for encryption and decryption, the message can't be properly decoded.

  • Fixed

    Posted by wheeldo on 23 Apr 2005

    Problem seems to be fixed now - I'd uncommented the initialisation vector code and it looks like it's mandetory.

    Also - replaced the hex encoding with base64 strings (many thanks to Ben Mills on th...