Using Encryption in .NET

Cipher Modes & Initialization Vectors

Cipher Modes

The process by which individual blocks of a message are put together to form the complete encrypted message is called a cipher mode. You can specify a cipher mode by assigning a value from the CipherMode enumeration to the Mode property of an instance of the cipher. Choosing a secure cipher mode is one of the most important things to get right when you implement encryption in your applications.

Usually, a message is more than one block in length. So how are the blocks of the message then put together to form the complete encrypted message? The obvious answer to this question would be simply to encrypt each block individually and then place the encrypted blocks end-to-end to form the encrypted message. In fact, there is a cipher mode available in the FCL that does exactly this, Electronic Code Book (ECB). Sounds simple, doesn't it? It turns out that this mode is very insecure for most applications and should never be used unless you really know what you're doing and have a good reason. Let's look at an example that uses ECB so that we can see it's weakness. We'll use the following code snippet to demonstrate:

string testMessage = "This is a block.This is a block.This is a block.";

byte[] plainText = Encoding.ASCII.GetBytes(testMessage);

RijndaelManaged cipher = new RijndaelManaged();

cipher.Mode = CipherMode.ECB;

ICryptoTransform transform = cipher.CreateEncryptor();

byte[] cipherText = transform.TransformFinalBlock(plainText, 0, plainText.Length);

Here's the hex dump for the data contained in cipherText with the padding block removed from the end:

C6 D7 EA 66 D0 A9 D5 6B 42 5F 45 95 BF 9B 5E FA
C6 D7 EA 66 D0 A9 D5 6B 42 5F 45 95 BF 9B 5E FA
C6 D7 EA 66 D0 A9 D5 6B 42 5F 45 95 BF 9B 5E FA

(if you try this, you'll end up with different data because your key will be different)

Do you see the problem? That's right; the problem is that each block contains precisely the same cipher text. Actually, we should expect this, given that we stated that ECB mode simply encrypts each block individually and places them end-to-end. I've just tried to highlight the problem by laying the data out in blocks like this. The cipher text in the hex dump reveals a great deal to a potential attacker. The structure of our message is obvious to anyone who wishes to look. Remember from our discussion of key strength that our data is not as random as we might think. There is probably a high recurrence of certain headers and many documents contain a lot of NULL data. We don't want this information revealed in the cipher text.

In order to correct this problem, we need to use a better mode. Cipher Block Chaining (CBC) mode is a much more secure mode. CBC creates each cipher text block by first XORing the plain text block with the previous cipher text block and then encrypting this combined block. Obviously, we have the problem of what to use as the previous cipher text block when we encrypt the first plain text block. In this case, an Initialization Vector (IV), which we'll discuss shortly, is used as the first cipher text block. This process prevents the structure of the message from being revealed to the degree it was with ECB. In order to decrypt the message, this process is simply unwound from the end of the message forward. Let's make a slight change to the code and then examine the hex output.

The line that reads...

cipher.Mode = CipherMode.ECB;

becomes...

cipher.Mode = CipherMode.CBC;

...and here's the hex dump, also with the padding block removed:

E7 E7 57 00 A3 FB 39 0D 0F 9C FC 2B E5 8F B6 80
8C 41 25 C7 D1 81 F4 9B 4F F6 9F 23 C5 80 F3 65
A9 63 8A 86 24 D0 53 39 BE 64 B0 63 A2 00 1B 21

As you can see, the message structure is no longer revealed. Let's change our CipherWrapper class to reflect this new knowledge. As I said above, CBC mode is the default, but it's better to always be explicit in the code with such important details. Let's modify our CreateCipher method to explicitly state our intention:

RijndaelManaged CreateCipher()
{
  RijndaelManaged cipher = new RijndaelManaged();
  cipher.KeySize  = 256;
  cipher.BlockSize = 256;
  cipher.Padding  = PaddingMode.PKCS7;
  cipher.Mode      = CipherMode.CBC; // The new modification
  return cipher;
}

Although there are other modes provided in the CipherMode enumeration, I won't cover them here for two reasons. First, I just wanted to illustrate the importance and principles of cipher modes. Secondly, there isn't consistent support for other modes in the FCL.

Initialization Vectors

I mentioned IVs above, but I wanted to return to it and cover it in some detail, now that you have a better understanding of modes. IVs are very important to getting encryption right. An IV is a block of random data that is used as the initial block for modes such as CBC. The IV can be safely transmitted in the clear with each message. Let's look at an example to illustrate the importance of IVs. For this example, I will use our CipherWrapper to encrypt two messages, as follows:

CipherWrapper cipher = new CipherWrapper();

string testMessage = "This is the test message";

byte[] plainText = Encoding.Unicode.GetBytes(testMessage);

byte[] cipherText = cipher.EncryptMessage(plainText);

byte[] cipherText2 = cipher.EncryptMessage(plainText);

Here's a hex dump of these two messages:

3B 10 7C D3 B8 AC BE 6D 7C 2F A6 24 8C 2C B3 05
08 6F 46 06 98 B7 12 E7 64 D0 44 3A CA 55 2D 27
A1 1E 45 1B 4B A3 7B AF 5E 23 44 07 9B BC AF B2
EE CE 3C 38 58 32 90 8D 6E FF FE 09 69 28 A7 56
3B 10 7C D3 B8 AC BE 6D 7C 2F A6 24 8C 2C B3 05
08 6F 46 06 98 B7 12 E7 64 D0 44 3A CA 55 2D 27
A1 1E 45 1B 4B A3 7B AF 5E 23 44 07 9B BC AF B2
EE CE 3C 38 58 32 90 8D 6E FF FE 09 69 28 A7 56

Do you see the problem? The problem is the same as before, only now the duplication is at a message level. This leaks information to an attacker in the same way as the ECB mode problem did, only now at a message rather than a block level. Our implementation of EncryptMessage is broken. Let's fix it by making a simple modification. Here's the corrected code:

public byte[] EncryptMessage(byte[] plaintext, out byte[] iv /*added*/)
{
  _cipher.GenerateIV(); //added
  iv = _cipher.IV; //added
  ICryptoTransform transform = _cipher.CreateEncryptor();
  byte[] cipherText = transform.TransformFinalBlock(plainText, 0, plainText.Length);
  return cipherText;
}

Here's the hex dump of the data produced by the modified version of EncryptData:

C7 62 8D E5 87 F2 1A 00 6E 1C 09 EE 73 CF E1 A2
61 E7 FF 05 C2 FF 2E 9C 71 EC FF D5 07 91 47 41
39 D3 30 29 49 05 DA 56 8E 87 6A AB AB 01 7C DC
36 B0 0F 3D EC 01 36 AF 34 8F C0 78 89 AC E6 F3
C6 8F 9F 83 43 A4 4C 16 73 A8 0D 69 4F D4 B2 FB
4F F6 C4 91 CA 8A BA 57 EA 2A 28 D9 88 8A 24 E2
D0 00 CD E2 35 9D 7D 29 38 E3 84 71 79 A5 8B 5D
C2 73 09 4B 50 4B 18 B9 53 37 66 B9 68 4E B4 F5

As you can see, the data is no longer the same in the two messages.

The corrected code now calls the Generate IV method of the cipher each time a message is encrypted and returns this value in a new out parameter. The GenerateIV method makes use of the RNGCryptoServiceProvider class to generate random bytes 1 block in length and assigns the value to the IV field of the cipher. You could do this manually by using the RNGCryptoServiceProvider class to generate random data and then assign the value to the IV property of the cipher yourself. It is very important that you use a unique IV for each message and that you never reuse it. Reusing an IV will cause your cipher text to leak information. Now we need to modify the DecryptMessage method to allow the IV to be passed in and used.

public byte[] DecryptMessage(byte[] cipherText, byte[] iv)
{
  ICryptoTransform transform = _cipher.CreateDecryptor();
  byte[] plainText = transform.TransformFinalBlock(cipherText, 0, cipherText.Length);
  return plainText;
}

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.

“Debugging is anticipated with distaste, performed with reluctance, and bragged about forever.” - Dan Kaminsky