More than you ever wanted to know about AES Crypto in .NET

First, before I begin anything, I want to point out that cryptography is hard. REALLY hard. There are so many possible points of failure, and those points of failure and methods of attack can change depending on what the purpose of encrypting the data is. If you do it wrong, you get lawsuits, and/or you end up on the front pages of major news organizations for data security breaches. Or a rival nation gets your top-secret plans to rule the world. You get the idea. I’ll touch on some of the technical points of failure briefly; I’m mainly going to be exploring this topic through a fairly simple scenario: A password vault file, encrypted with a password, and stored locally on my computer (potentially synced to other computers through some syncing service).

As with any good crypto, the first line of defense is always preventing an attacker from gaining access to the encrypted data in the first place. If data is going over a network, encryption should be part of that as well, and that brings in a whole new host of issues that are outside of the scope of this article. Since I’m planning on storing the data locally on my hard drive, the first line of defense is me and my computer: Lock my computer when I walk away. Make sure there’s no spyware or malware on my computer, no keyloggers or other devices that could steal my password, etc… Going back to my original point about crypto being hard, it’s hard not only because writing the code is hard, it’s difficult because of all the ancillary ways someone could potentially get access to my data that I have to account for.

If I leave my computer unlocked, someone could steal data off of it. Install a keylogger. If I leave this password app open someone could just go in and look at my passwords after I’ve decrypted them. If they have physical access someone could install a HARDWARE keylogger between my keyboard and computer. Pull out my hard drive and clone it. Hack into the company Wi-Fi network and remote access my computer. Or even STEAL my computer. Or TSA could confiscate it. And/or force / legally compel me to reveal my password. You get the idea. There is no such thing as absolute security or secrecy.

However, let’s assume for a moment that an attacker DOES somehow manage to get access to my vault file. (Maybe by getting access to my computer when the vault program is closed and the data encrypted, or a folder syncing program has a hiccup and spams a public link to it on the Internet) My password file is in the hands of someone actively trying to get at my data, and the only thing protecting the data is my password and good cryptography. So. How do I use good cryptographic practices to secure my data?

Since we want to use the latest and greatest, we pick AES (Advanced Encryption Standard, successor to Date Encryption Standard and Triple Data Encryption Standard or Triple DES), Rijndael 256 to be specific (It’s good enough for government work…). In the System.Security.Cryptography namespace, Microsoft has kindly supplied us with the RijndaelManaged class and a pre-provided implementation of the AES standard. I ALWAYS recommend using a pre provided class instead of attempting to roll your own. The ones from Microsoft have gone through EXTENSIVE testing, code, and security review to verify the correctness of the algorithms and code. Frankly, if you write your own, yours will probably have bugs. Bad ones. Bugs that might get you put on the front page of major news organizations for data security breaches if you do it wrong. Warning given, point made, moving on.

Now, there are a couple of things you’ll need in order to do symmetric encryption when we crack open the RijndaelManaged class and try to create an encryption transform:

  • A Key
  • An Initialization Vector or IV
  • Your Data

The Key

First, the key, which is represented as a byte array of some fixed size. The size of your key is important, the number of bits has to match what the algorithm supports, in the case of Rijndael, it supports key sizes of 128, 192, and 256. In our case, we’ll be using a key size of 256. The number of bytes we need is 256 divided by 8 (remember, the key size for the algorithm is specified in bits, but our code mainly works with bytes). So if I’m declaring a byte array for the key for AES 256, it would be new byte[32].

Derive your Key

Now wait, how the heck to I remember a 32 number combination? I can’t. You probably can’t, and unless you’re a brainiac with an eidetic memory I wouldn’t suggest trying with any reasonable hope of success. So, since I have now come to this sad conclusion about my memory, I need some way to take a password or key that I can memorize and turn it into a 32 byte key. Fortunately, there’s a way to do just that by way of a password based key derivation function called PBKDF2. It’s also a standard (RFC 2898, which is only important because the code uses the name of the spec for the class name and not PBKDF2 like I would expect… go figure). Essentially, PBKDF2 is a piece of code that hashes an arbitrary length of text into a pseudo random key. Now, we could use something like MD5 to do something similar, but PBKDF2is designed with cryptography in mind, and to be slow on purpose. Why does it need to be slow? Simply this, cryptography is a big huge lever that makes it easy to go one way, and hard to go another. MD5 is FAST to compute, and someone trying to break into your encrypted data wants to be able to test as many passwords as possible as fast as possible. PBKDF2is slow and more difficult to compute, and the harder it is, the longer it takes to generate a hash, which dramatically reduces the number of passwords a hacker trying to attack your vault can try per second. (There’s a really good article by Jeff Atwood here: on hashing algorithms in security, rainbow tables, speed hashing, why it’s important to have a slow hash for security, and so on. If you’re interested on reading more about it)

How do we create this hashed key with PBKDF2? The constructor takes 3 things, a string password, an iteration count, and a byte array for something called a salt (Not in that order). The Password is easy, it’s a string or byte array and it’s whatever you choose to use as your “uB3rAw3$omeP@assw0rd!“. iteration count is pretty self-explanatory too, it defaults to 1000, and the bigger the number, the more calculation rounds it does and the longer it takes to calculate the hash. But what about this salt thing? What is it, and why is it important? Do I even care? So here we go.

Salt your Vault

If you’ve heard about salt or salting passwords before, it was probably in reference to a website or service, usually some piece of code that you controlled that sat between your users (or a supposed attacker) and the hashed passwords in your database so that an attacker couldn’t just *make up* a password like zn3lzl that would hash to the same value as the users password like mycoolpassword2 and allow them to log in. If we assume that the attacker has access to your password vault, we can probably assume he has access to your program as well, and it won’t be very hard to figure out some static salt value you’ve stored in your code. So it’s useless right? In some ways yes, in some ways no. IF it’s JUST you, or the attacker is targeting JUST YOUR vault, then yes, it doesn’t matter. However, if for some reason your password vault app becomes popular, and an attacker gains access to, lets say 5000 different peoples vault files, it’s much much easier to check the same passwords across ALL the vaults at once if the salt values are the same. That is again assuming he is not targeting just one particular vault. If they’re all different, the time required to check a password across all the vaults goes up by a factor of 5000, making it less viable to quickly crack multiple vaults. My thought is to simply store the salt along with the vault, since if an attacker has access to your vault, he probably has access to wherever I’d store the salt anyways, and it at least requires him to compute a separate hash for each vault. So, I’ll generate and store a separate, cryptographically random generated salt with my vault file.

Something like this:

  1. var salt = new byte[256];
  3. using (var rnd = RandomNumberGenerator.Create())
  4. {
  5. rnd.GetBytes(salt);
  6. }

As an aside, realize that no amount of encryption can save your data from a bad password. Good hackers are GOING to use huge word lists and fantastic substitution / transposition / combination rules before they even attempt to use a brute force attack. ‘MyL1ttl3P0ny‘ is not a good password. ‘abc123‘ is arguably much worse; but then you’re probably not reading this if abc123 is something you’d use for a password.

Now that we have our password, the salt (loaded from our vault file) and the number of iterations, we can derive a key with the correct size:

  1. var deriveBytes = new Rfc2898DeriveBytes(myPassword, mySalt, 10000);
  2. var aes256Key = deriveBytes.GetBytes(32);

Tada! magic. We now have a byte key from the password that we supplied earlier. Ok, now that we have our key, we can see that when you try to create our encryption or decryption transform that it is requiring something called an initialization vector (IV). I know when I was going through all this that I was thinking “what the heck is an initialization vector?” Since I had to teach myself what it was and why it was important, I’m going to assume somebody also doesn’t know and brain dump what I’ve learned on you all as well.

Block Ciphers, Chaining, and Initialization Vectors

First, we have to understand a few things, how a block ciphers works, how cipher block chaining works, how the IV plays in, and why it’s all important. To begin with, block ciphers. Most computer ciphers these days are done using cipher blocks of a given bit size, usually in round multiples of 2, for example 128 bit or 16 bytes chunks (this is actually the size AES uses). Our clear text gets chunked up into these small, manageable blocks of data. Then encryption transform is then run multiple times on each block, and the resulting blocks are all concatenated together to form the final encrypted output.

Because encrypted blocks are computed independently, changes somewhere in the original unencrypted data might only change the encrypted data for a few blocks of the resulting encrypted output. Furthermore, if you happened to have the same 16 byte chunk repeated again such that it aligned with a different blocks, you will get the exact same block of encrypted output. On one hand, this could be useful if you want to send delta updates to an encrypted file, it also means that attacks against individual blocks are feasibly be easier or useful information could be obtained by comparing one version of the vault to another or even patterns might still exist even within the encrypted date. A really awesome example of why it’s important to apply some additional steps during the encryption process with block ciphers are these three images below: (Courtesy of Wikipedia):

Original | No Block Chaining | Proper Cipher Block Chaining

This is where CBC or cipher block chaining comes in. CBC fixes this problem by taking the previously encrypted block and performing a bitwise XOR between it and the raw bytes of the plain text of the next block to be encrypted. Effectively, this means that a one byte change in the first block will change EVERY OTHER block in the resulting ciphertext. However, there’s one last piece. If the first couple of blocks are NOT changed in any way, you can still leek some information about changes that were made by way of the first changed block. So if I have blocks A B C D E and block C gets changed, CBC will cause the resulting ciphertext for blocks D and E to change. It will NOT affect blocks A and B. So, at last, we finally get to the purpose of the initialization vector. The IV is essentially a completely random block to start off the cipher chain. Makes sense now doesn’t it? If I have a completely random block that I create and pass along, it guarantees that even if the plain text doesn’t change at all, the ciphertext will be completely different each time, assuming I generate a new IV each time I change the cipher text. In practice, encrypted data should be statistically indistinguishable from random noise.

Illustration of the process of a block cipher with CBC for Encryption:

Illustration of the process of a block cipher with CBC for Decryption:

Now that we know why we need an initialization vector, we also know, or can guess, what size it should be without having to look at the documentation (16 bytes, because that’s the block size for AES, and our IV is essentially a known random starting block). Since we should generate a new block and also store it with our vault, I’m going to declare our IV and initialize it the same way we initialized our salt:

  1. var iv = new byte[16];
  3. using (var rnd = RandomNumberGenerator.Create())
  4. {
  5. rnd.GetBytes(mySalt);
  6. rnd.GetBytes(iv);
  7. }

The CryptoStream

Now that we have all the pieces we can put it all together and encrypt our data to a MemoryStream (the memory stream could be anything, including a FileStream, but this is easier for demo and console output):

  1. using(var ms = new MemoryStream())
  2. {
  3. using (var cryptoStream = new CryptoStream(ms, transform.CreateEncryptor(aes256Key, iv), CryptoStreamMode.Write))
  4. {
  5. cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Count());
  6. // cryptoStream.FlushFinalBlock();
  7. }
  9. cipherTextBytes = ms.ToArray();
  10. }

EDIT: Thanks to Nick (see comments), he pointed out that cryptoStream.FlushFinalBlock() is unnecessary if you dispose the cryptoStream before you read the data out from the underlying stream. (This happens automatically with a using statement)

And now the code snippet for decrypting the cypherTextBytes:

  1. using (var ms = new MemoryStream(cipherTextBytes))
  2. {
  3. using (var cryptoStream = new CryptoStream(ms, transform.CreateDecryptor(aes256Key, iv), CryptoStreamMode.Read))
  4. {
  5. var decryptedBytes = new byte[cipherTextBytes.Length];
  6. var length = cryptoStream.Read(decryptedBytes, 0, decryptedBytes.Length);
  8. var decryptedText = Encoding.UTF8.GetString(decryptedBytes.Take(length).ToArray());
  9. }
  10. }

Padding under the covers

Some final notes block cipher padding: You’ll notice that when I’m reading the stream I have the following line of code: decryptedBytes.Take(length).ToArray() When you’re using a block cipher like AES, just like the key has to be exactly a certain size, each block of initial data ALSO has to be a certain size. This means you ARE going to get some extra data tacked on the end of your stream that you weren’t anticipating. There’s a couple of ways this can be handled, RijndaelManaged has a couple of padding modes that it can use ranging from filling all the additional slots of data with zeros, or filling them with completely random data, but by default it uses PaddingMode.PKCS7 which fills each extra bytes needed to make the length of the data an even multiple of 16 bytes with the number that represents the number of padded bytes added to fill the empty space. If you have the exact amount of data to exactly to fill the number of blocks, the padding algorithm will add an extra block to ensure that the last byte in the last block that is read represents the amount of padding. Otherwise, depending on whatever data you’re storing, you could accidentally lose some of your data if it was misinterpreted as padding. The crypto stream is aware of the padding and will return the correct length of the original data on the last read call. I simplified the stream reading process for simplicity of demonstrating the use of the crypto stream and how it handles padding, it just reads everything and trims the result with decryptedBytes.Take(length).ToArray(). In real life, you should use or make a ‘real’ stream reader that reads data out of the stream in chunks and aggregates them together, or just serialize / deserialize your objects directly to and from the crypto stream.


The Full Demo

  1. using System;
  2. using System.IO;
  3. using System.Linq;
  4. using System.Security.Cryptography;
  5. using System.Text;
  7. public class CryptoDemo
  8. {
  9. public static void Main(string[] args)
  10. {
  11. TestEncryptionAndDecryption();
  13. Console.ReadLine();
  14. }
  16. public static void TestEncryptionAndDecryption()
  17. {
  18. const string myPassword = "uB3rAw3$omeP@assw0rd!";
  19. const string myData = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
  20. "Morbi rutrum pulvinar purus, nec ornare neque cursus id. " +
  21. "Nunc non tortor est. Morbi laoreet commodo tellus, et suscipit neque elementum eu. " +
  22. "Sed velit lorem, ultricies id varius vitae, eleifend eget massa. " +
  23. "Curabitur dignissim eleifend quam, sit amet interdum velit rutrum vel. " +
  24. "Nulla nec enim tortor.";
  25. var plainTextBytes = Encoding.UTF8.GetBytes(myData);
  27. Console.WriteLine("Password ({0} bytes): ", Encoding.UTF8.GetBytes(myPassword).Length);
  28. Console.WriteLine(myPassword);
  29. Console.WriteLine();
  31. Console.WriteLine("Plain Text ({0} bytes): ", plainTextBytes.Length);
  32. Console.WriteLine(myData);
  33. Console.WriteLine();
  35. var mySalt = new byte[256];
  36. var iv = new byte[16];
  38. using (var rnd = RandomNumberGenerator.Create())
  39. {
  40. rnd.GetBytes(mySalt);
  41. rnd.GetBytes(iv);
  42. }
  44. var transform = new RijndaelManaged();
  46. Console.WriteLine("Salt ({0} bytes): ", mySalt.Length);
  47. Console.WriteLine(Convert.ToBase64String(mySalt));
  48. Console.WriteLine();
  49. Console.WriteLine("Initilization Vector ({0} bytes): ", iv.Length);
  50. Console.WriteLine(Convert.ToBase64String(iv));
  51. Console.WriteLine();
  53. // Derive the passkey from a hash of the password plus salt with the number of hashing rounds.
  54. var deriveBytes = new Rfc2898DeriveBytes(myPassword, mySalt, 10000);
  56. // This gives us a derived byte key from our password.
  57. var aes256Key = deriveBytes.GetBytes(32);
  59. Console.WriteLine("Derived Key ({0} bytes): ", aes256Key.Length);
  60. Console.WriteLine(Convert.ToBase64String(aes256Key));
  61. Console.WriteLine();
  63. byte[] cipherTextBytes;
  65. using (var ms = new MemoryStream())
  66. {
  67. using (var cryptoStream = new CryptoStream(ms, transform.CreateEncryptor(aes256Key, iv), CryptoStreamMode.Write))
  68. {
  69. cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Count());
  70. // cryptoStream.FlushFinalBlock();
  71. }
  73. cipherTextBytes = ms.ToArray();
  75. Console.WriteLine("Encrypted Text ({0} bytes): ", cipherTextBytes.Length);
  76. Console.WriteLine(Convert.ToBase64String(cipherTextBytes));
  77. Console.WriteLine();
  78. }
  80. // this resets the algorithm. Normally, if we have a seperate encrypt / decrypt method
  81. // we would create a new instance of RijndaelManaged
  82. transform.Clear();
  84. using (var ms = new MemoryStream(cipherTextBytes))
  85. {
  86. using (var cryptoStream = new CryptoStream(ms, transform.CreateDecryptor(aes256Key, iv), CryptoStreamMode.Read))
  87. {
  88. var decryptedBytes = new byte[cipherTextBytes.Length];
  89. var length = cryptoStream.Read(decryptedBytes, 0, decryptedBytes.Length);
  91. var decryptedText = Encoding.UTF8.GetString(decryptedBytes.Take(length).ToArray());
  93. Console.WriteLine("Decrypted Text ({0} bytes): ", decryptedText.Length);
  94. Console.Write(decryptedText);
  95. Console.WriteLine();
  96. }
  97. }
  98. }
  99. }

Sample Output

Password (21 bytes):

Plain Text (355 bytes):
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi rutrum pulvinar purus, nec ornare neque cursus id. Nunc non tortor est. Morbi laoreet commodo tellus, et suscipit neque elementum eu. Sed velit lorem, ultricies id varius vitae, eleifend eget massa. Curabitur dignissim eleifend quam, sit amet interdum velit rutrum vel. Nulla nec enim tortor.

Salt (256 bytes):

Initilization Vector (16 bytes):

Derived Key (32 bytes):

Encrypted Text (368 bytes):

Decrypted Text (355 bytes):
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi rutrum pulvinar purus, nec ornare neque cursus id. Nunc non tortor est. Morbi laoreet commodo tellus, et suscipit neque elementum eu. Sed velit lorem, ultricies id varius vitae, eleifend eget massa. Curabitur dignissim eleifend quam, sit amet interdum velit rutrum vel. Nulla nec enim tortor.

That concludes my epic tour de AES in .NET. It’s like writing papers in college all over again. Hope somebody finds it useful.

5 thoughts on “More than you ever wanted to know about AES Crypto in .NET

  1. I don’t think calling cryptoStream.FlushFinalBlock() is actually necessary in this code.
    This method is called when the stream is closed, which will happen with the stream is disposed upon exiting the using block.

    A good blog post though. I’ve seen people (naively) try to work with ICryptoTransform directly rather than using CryptoStream – I think at least 50% of the time people will (incorrectly and unknowingly) discard the padded bytes from the final block of the ciphertext.

    • Hey Nick! Thanks for the comment, you’re right of course, the cryptoStream.FlushFinalBlock() is unnecessary, assuming (like you stated) that the cryptoStream.Dispose() is called before you attempt to read out the encrypted bytes from under the underlying stream. I updated the post and noted you contribution, thanks for catching that!

  2. Pingback: Providing integrity for Encrypted data with HMACs in .NET | Paul Rohde

  3. Pingback: Providing integrity for Encrypted data with HMACs in .NET | InterKnowlogy Blogs

  4. Spot on with this write-up, I really believe that this amazing
    site needs a great deal more attention. I’ll probably be back again to see more, thanks for the advice!

Leave a Reply

Your email address will not be published. Required fields are marked *