Library tutorials & articles

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;
}

Comments

  1. 21 May 2005 at 19:53

    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 tried it, but I seem to remember seeing some nasty code dealing with storing images and other binary data in SQL Server.  The conversion to and from Base64 strings is 1 line of code in each case and then I can deal with regular char and varchar fields.

  2. 19 May 2005 at 17:26

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

  3. 18 May 2005 at 17:12

    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 you said that the CryptoStream object was beyond your scope, but that's where I'm needing to go with this and I like to use a FileStream instead of a MemoryStream.  I would also like to save the encrypted file in place of the original (which I may just have to rename them differently and then delete the original).  I also know you said that you don't have to write the encryption byte for byte, but how do you do this with a FileStream?  And if you do have to do it byte for byte with a FileStream, then how many bytes do I/should I set it to?  Also, all examples I've seen only demonstrate how to Encrypt a file and not Decrypt it again.  On the decryption, though, I only need to display it and not keep or save it anywhere.  Maybe loading it into a MemoryStream might be better?  Keep in mind that this files could be big (most are images).  Is there any advice you could give or maybe another discussion you have?


    Thanks!

  4. 24 Apr 2005 at 00:32

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

  5. 23 Apr 2005 at 10:29

    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 the other thread about storing values in SQL server for pointing me in this direction).

  6. 23 Apr 2005 at 10:26

    Many thanks Ben - I've got a working solution now!


    I think the problem was the fact I'd commented out the initialisation vector code.. I didn't think this was essential.


    Anyhow - I've also removed my conversion routines to and from hex as the conversion to base64 strings is a lot more elegant (and the strings generated shorter).


    Thanks again.

  7. 22 Apr 2005 at 19:32

    I actually ended up cracking the problem in a different way.  Here's what I do to encode a .NET string:


    1.  Turn the string into a byte array using the ASCII encoder.
    2.  Run the byte array through the Rijndael encryptor (some bytes cannot now be represented as ASCII chars).
    3.  Convert the resulting array of bytes to a Base64 string using Convert.ToBase64String().
    4.  The result is an ASCII string which can be strored in regular (non-unicode) database columns.


    To decrypt the string I do the reverse:


    1.  Convert the string from the database into a byte array using Convert.FromBase64String().
    2.  Run the byte array through the Rijndael decryptor.
    3.  Convert the resulting array back into a string using the ASCII encoder.


    The trick with all of this is that the an 8 bit unicode string can be stored as a 7 bit ASCII string by converting it into a Base64 string.  Base 64 strings use 4 bytes to represent every block of 3 byte unicode characters.  See http://email.about.com/cs/standards/a/base64_encoding.htm for a good intro on base64 strings.  The one thing you have to think about is how long to make your database columns.  Just remember that every block of 3 unencrypted characters results in 4 encrypted characters in the database.


    I hope this helps,
    Ben Mills


  8. 22 Apr 2005 at 18:49

    Hi all,


    Excellent article - made a lot of sense.


    I've created a little test app which converts the byte arrays to hex strings (for storing in DB) - and then does the reverse. This all seems to work, but for some strange reason the first 32 characters of the decrypted string are garbled (everything after this point is fine!!).


    I'm guessing it's something to do with padding, but not sure - maybe I'm missing something really obvious!


    Any help would be hugely appreciated. I've posted the code from my test form below (hope you're okay about this and hope it benefits others)..


    cheers.



    using System;
    using System.Drawing;
    using System.Collections;
    using System.ComponentModel;
    using System.Windows.Forms;
    using System.Data;


    using System.Security.Cryptography;
    using System.Text;
    using System.IO;
    using System.Globalization;        // for NumberStyles


    namespace EncryptionTool
    {
       /// <summary>
       /// Summary description for Form1.
       /// </summary>
       public class Form1 : System.Windows.Forms.Form
       {
           private System.Windows.Forms.TextBox txtToEncrypt;
           private System.Windows.Forms.Label label1;
           private System.Windows.Forms.Button btnDecrypt;
           private System.Windows.Forms.Label label2;
           private System.Windows.Forms.TextBox txtKey;
           private System.Windows.Forms.Label label3;
           private System.Windows.Forms.Button btnEncrypt;
           private System.Windows.Forms.TextBox txtEncrypted;
           private System.Windows.Forms.TextBox txtDecrypted;
           private System.Windows.Forms.Label label4;
           /// <summary>
           /// Required designer variable.
           /// </summary>
           private System.ComponentModel.Container components = null;


           public Form1()
           {
               //
               // Required for Windows Form Designer support
               //
               InitializeComponent();


               //
               // TODO: Add any constructor code after InitializeComponent call
               //
           }


           /// <summary>
           /// Clean up any resources being used.
           /// </summary>
           protected override void Dispose( bool disposing )
           {
               if( disposing )
               {
                   if (components != null)
                   {
                       components.Dispose();
                   }
               }
               base.Dispose( disposing );
           }


           #region Windows Form Designer generated code
           /// <summary>
           /// Required method for Designer support - do not modify
           /// the contents of this method with the code editor.
           /// </summary>
           private void InitializeComponent()
           {
               this.txtToEncrypt = new System.Windows.Forms.TextBox();
               this.label1 = new System.Windows.Forms.Label();
               this.txtEncrypted = new System.Windows.Forms.TextBox();
               this.btnDecrypt = new System.Windows.Forms.Button();
               this.label2 = new System.Windows.Forms.Label();
               this.txtKey = new System.Windows.Forms.TextBox();
               this.label3 = new System.Windows.Forms.Label();
               this.btnEncrypt = new System.Windows.Forms.Button();
               this.txtDecrypted = new System.Windows.Forms.TextBox();
               this.label4 = new System.Windows.Forms.Label();
               this.SuspendLayout();
               //
               // txtToEncrypt
               //
               this.txtToEncrypt.Location = new System.Drawing.Point(8, 72);
               this.txtToEncrypt.Multiline = true;
               this.txtToEncrypt.Name = "txtToEncrypt";
               this.txtToEncrypt.Size = new System.Drawing.Size(448, 80);
               this.txtToEncrypt.TabIndex = 1;
               this.txtToEncrypt.Text = "";
               //
               // label1
               //
               this.label1.Location = new System.Drawing.Point(8, 56);
               this.label1.Name = "label1";
               this.label1.TabIndex = 2;
               this.label1.Text = "Text to encrypt";
               //
               // txtEncrypted
               //
               this.txtEncrypted.Location = new System.Drawing.Point(8, 176);
               this.txtEncrypted.Multiline = true;
               this.txtEncrypted.Name = "txtEncrypted";
               this.txtEncrypted.Size = new System.Drawing.Size(448, 80);
               this.txtEncrypted.TabIndex = 3;
               this.txtEncrypted.Text = "";
               //
               // btnDecrypt
               //
               this.btnDecrypt.Location = new System.Drawing.Point(464, 184);
               this.btnDecrypt.Name = "btnDecrypt";
               this.btnDecrypt.TabIndex = 4;
               this.btnDecrypt.Text = "&Decrypt";
               this.btnDecrypt.Click += new System.EventHandler(this.btnDecrypt_Click);
               // <

  9. 22 Apr 2005 at 18:42

    Hi,


    I'm attempting to do the same thing. I've gone down the route of converting the cipher's byte array to a Hex string using the following: -


       BitConverter.ToString(bytEncryptedMessage);


    This creates a hyphon delimitted string (e.g. 0A-10-FA etc...) which can be stored in the database.



    When I want to decrypt I'm first calling the following function: -


    // Assumes the hex string values are "-" delimitted. e.g. 0A-FA-BD
           public byte[] HexStringToBytes(string sHexString)
           {
               string[]        sStringArray;
               byte[]            bytByteArray;
               int                iCount = 0;


               sStringArray = sHexString.Split('-');
               bytByteArray = new byte[sStringArray.Length];
       
               foreach (string sHexValue in sStringArray)
               {
                   bytByteArray[iCount] = byte.Parse(sHexValue, NumberStyles.HexNumber);
                   iCount ++;
               }


               return bytByteArray;
           }


    which re-creates the byte array.




    I thought I'd cracked it, but something really weird is happening!! When decrypted - the first 32 characters are garbage - everything after is fine!!!


    I'm guessing this is something to do with the padding, but not sure.


    Hope this points you in the right direction and would also really appreciate any assistance with the problem I'm having!


  10. 30 Mar 2005 at 14:50

    First of all, I'd like to say that this was a fantastic article.  I was struggling through the MSDN info and other online articles and your article finally made everything clear.


    I want to use this technique to encrypt credit card data before I store it in SQL Server.  I take the ASCII credit card number, convert it to an array of bytes using the ASCII encoder and then encrypt the bytes.  The trouble is that the bytes are now out of the ASCII range (0 to 127), so I can't convert the bytes back to an ASCII string to store in a VarChar column of a SQL Server table.


    It seems like a solution is to convert the encrypted bytes to a unicode string using the unicode encoder and store that value in a NVarChar column.  The only problem with this is that I usually avoid unicode SQL Server columns.  Is this the only solution or do you have any other ideas?

  11. 07 Jan 2005 at 08:39

    Regarding my comment that "there is "no way" to reverse this process without the key."


    I want to be clear that I mean that there is no trivial way to do this.  What I'm trying to say is that the process is as secure as the key and the underlying cipher.


    There is, of course, no such thing as "no way" in cryptography.  We just try to use the best technology we can to make it as difficult and expensive as possible for an attacker to break the system.



    Steve Johnson

  12. 07 Jan 2005 at 08:23

    Using CBC as an example, encryption proceeds as follows:
    1 - XOR Plain Text Block #1 with IV
    2 - Encrypt the block resulting from step 1 with the key
    3 - The Cipher Text block resulting from step 3 is used as Cipher Text block #1
    4 - XOR Plain Text block #2 with Cipher Text block #1
    5 - Encrypt the block resulting from step 4 with the key
    6 - The Cipher Text block resulting from step 5 is used as Cipher Text block #2
    7 - etc, etc...


    There's no way to reverse this process without the key.  The IV is not being used to seed a PRNG; it's simply used as random data, with which to mix the first block of plain text on encryption and to retrieve the initial plain text block on decryption.  As you can see, there is no intermediate "non-randomized" state that can be determined using the cipher text and the IV.



    Steve Johnson

  13. 07 Jan 2005 at 08:05

    Wouldn't this mean that for eavesdroppers using the same random generator (which can not be that big a problem) and the transmitted seed it is possible to retrieve the 'un-randomized' (if thats a word) still encrypted content?


    Wilfred Kuijpers
    dbHost


  14. 07 Jan 2005 at 07:47

    You transmit the IV in the clear (unencrypted).  Just add the IV to your transmitted message so the receiving computer can decrypt the message.  There's no need to encrypt the IV because it is used simply to randomize the cipher text to prevent repetition.



    Steve Johnson

  15. 05 Jan 2005 at 17:08

    Hi,


    Great article,


    Just one question though, how do you transmit your IV's? I mean, decryction usually happens at another computer and for encryption and decryption you suggest to use the iv?


    regards,


    Wilfred Kuijpers
    dbhost

  16. 01 Jan 1999 at 00:00

    This thread is for discussions of Using Encryption in .NET.

Leave a comment

Sign in or Join us (it's free).

AddThis

Related podcasts

  • CodeCast Episode 4: State of .NET, IE8, ASP.NET MVC, and O'Reilly Media

    CodeCast Episode 4: State of .NET, IE8, ASP.NET MVC, and O'Reilly MediaHosts Ken Levy and Markus Egger discuss the new State of .NET events, IE8, ASP.NET MVC, followed by an interview from PDC with two editors from O'Reilly Media. More on ASP.NET MVC can be found at http://asp.net/mvc. Interview...

Related jobs

Events coming up

  • Dec 6

    Developing AJAX Web Applications with Castle Monorail

    London, United Kingdom

    Monorail is the model-view-controller engine of the Castle Project, bringing many of the best ideas of Ruby on Rails to the .NET world. In this talk, David De Florinier and Gojko Adzic show how Monorail makes it easy to develop .NET based AJAX applications, and how to use the Castle Project to build Web 2.0 applications effectively. Come to this session if you are a .NET web developer. Everyone is welcome!