C# · Cryptography

Implementing encryption the right way with AES (I think)

Security is something nearly every app has to worry about in some form. There are a ton of incorrect or incomplete implementations around the internet. Unfortunately “good enough” isn’t good enough with this stuff. If it’s done wrong you are probably better off not doing it. I did a ton of reading to find the correct implementation, and I’d like to share it here. All examples are going to be in c# and are comparable with .Net Core. This will probably turn into a series depending on the feedback I get.

First in the series is AES (Advanced Encryption Standard). It is a synchronous encryption cypher, meaning you use the same key to encrypt and decrypt. AES has a few settings that you should know about (and get right)

Aes.Mode = CipherMode.CBC;

You have a few modes to choose from but CBC (Cipher Block Chain) is the one that you should go with. AES carves data up into blocks (128 bits) and encrypts each block at a time. This can be problematic because if you have the same data and the same encryption key then your encrypted data would look the same. This could leak information that commonly repeats. But in CBC mode the cipher adds some data from the previous block and then encrypts it. This helps change the encrypted version even if the underlying text is similar.

Aes.Padding = PaddingMode.PKCS7;

The next setting to talk about is padding. As I said above AES encrypts data in 128 bit blocks. if the last block isn’t 128 bits then what do we pad that block with? There are a few options, like zero’s but PKCS7 pads with the number of empty blocks. For example if the last 4 bytes are padding, the last 4 bytes are populated with 04 04 04 04.

Aes.BlockSize = 128;

As I’ve mentioned already AES encrypts data in 128 bit blocks. I believe this is only editable because the underlying cypher (Rijndael) had a variable block size. Just hard code it to 128.

Aes.KeySize = 256;

The key size can be set to either 128, 192, or 256. With the power of modern devices I don’t see any reason to use anything other than 256. Just a note here 128 isn’t half as good as 256. The way bit math works 255 is half as good as 256. AES 256 has 1 x 10^77 (That’s 1 with 77 zero’s after it) possible keys. AES 128 has 3 x 10^38 possible keys. So AES256 is exponentially stronger than AES128.

Aes.GenerateIV();

The Initialization Vector (IV) is where I see the most confusion, and bad practices. As stated earlier, in CBC mode AES takes some data from the previous block and mixes it into the current block creating a chain that is more scrambled than doing each block on its own. However what do you do with the first block of data? There’s no previous block to mix with. That’s what the IV is. Its a random chunk (16 bytes) of data that is mixed with the first bock before it’s encrypted. This data doesn’t need to be secret, but it does need to be unique. Luckily the AES cipher has a GenerateIV() function built in. however you’ll need that exact same IV in order to properly decrypt. This is why most people just hard code the IV with the same 16 bytes for everything they encrypt/decrypt. That works fine (though is less secure) as long as you aren’t sending the encrypted data to someone else. So what you should do is use the GenerateIV() function, and then append the generated IV to the beginning of the encrypted text. That way when you (or anyone else) are ready to decrypt you have it in the file. I’ll post my full AES implementation at the end of the article and you can see an example.

So all of the above settings are default in AesManaged. I detail them here because you may see implementations of AES in applications you work on. If they are not these settings you should ask why they are different. If there isn’t a good reason for it, you should argue for fixing it.

AES.Key = (encryptionKey);

This is the key you are encrypting with. It has to be a byte[] that is the same length as the KeySize above (again I suggest 256bits). It should be a randomized string of bits, or a hashed password. This key is what you need to keep secret. The encrypted data can be public, the settings used to encrypt can be public, the Initialization Vector can be public, but the Key HAS to be private.

That’s it for settings. The steps to encrypt aren’t too difficult. you set up 2 memory streams. One for input (the data) and one for output (the encrypted data). In between you set up a CryptoStream using the AES object you created.

Here’s a link to a demo implementation (on github) AesSimple

2 thoughts on “Implementing encryption the right way with AES (I think)

  1. Good write up Eldie, just one note: IV should not be public, key might be derived from known IV when enough encrypted blocks are collected. This is weakens your security and applies to all encryption types. Also AES128 is not strong enough anymore, AES256 should be a default key size.

    1. Hey Edward,

      Thanks for the comment. Do you have a source for keeping the IV private? Everything I’ve read says it needs to be unique but doesn’t need to be private.

Leave a Reply

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