I thought I would start a little series on using some of the cryptography primitives in .NET. Cryptography and Encryption is something that most developers working on enterprise applications will come across, especially if you work in the financial services industry.
Whilst cryptography is a fascinating subject and the design of these algorithms is very interesting, I do not recommend using an algorithm that you have designed yourself. The standard algorithms in practice today have been through lots of analysis by experts both in private industry and governments all around the world trying to find faults and weaknesses, so you are much better off using these recommended systems.
The main algorithms fall into 2 categories, Symmetric encryption and Asymmetric encryption. Symmetric encryption contains algorithms that are based solely on an encryption key. For example, if you encrypt some plaintext with Key1 you get a cipher text out the other end. If you then decrypt the cipher text with the same key (Key1) you will get back to the original plaintext.
Asymmetric encryption works by having 2 keys, a public and private key. These keys are mathematically derived from each other. The public key can be used by anyone and the private key has to be kept secret. I will talk about asymmetric encryption and more specifically RSA in another post.
For this first article I am going to look at the AES symmetric algorithm. AES stands for the Advanced Encryption Standard. This was a competition winner when the National Institute of Standards and Technology ran a contest to replace the already broken DES algorithm.
What I will show in this article is a good practical implementation of AES in .NET. We will start with the following interface. The interface contains 2 methods, Encrypt and Decrypt. They methods take cipher text/plaintext and an encryption key.
using System; namespace CryptoLibrary { public interface IAES { string Decrypt(string ciphertext, string key); string Encrypt(string plainText, string key); } }
The following method shows the encryption process. The key is a string. This could be a password or a string of pseudo random characters. The first thing we do is run a key derivation function to generate the actual AES encryption key based on the password and a salt (which is generated in the key derivation function).
public string Encrypt(string plainText, string key) { if (string.IsNullOrEmpty(plainText)) { throw new ArgumentNullException("plainText"); } if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException("key"); } using (var keyDerivationFunction = new Rfc2898DeriveBytes(key, _saltSize)) { byte[] saltBytes = keyDerivationFunction.Salt; byte[] keyBytes = keyDerivationFunction.GetBytes(32); byte[] ivBytes = keyDerivationFunction.GetBytes(16); using (var aesManaged = new AesManaged()) { aesManaged.KeySize = 256; using (var encryptor = aesManaged.CreateEncryptor(keyBytes, ivBytes)) { MemoryStream memoryStream = null; CryptoStream cryptoStream = null; return WriteMemoryStream(plainText, ref saltBytes, encryptor, ref memoryStream, ref cryptoStream); } } } }
What the key derivation function (Rfc2898DeriveBytes) does is repeatedly hash the user password along with a salt. This has multiple benefits:
Firstly, you can use arbitrarily sized passwords – AES only supports specific key sizes.
Secondly, the addition of the salt means that you can use the same passphrase to generate multiple different. This is important for key separation; reusing keys in different contexts is one of the most common ways cryptographic systems are broken.
The beauty of this is that every time you encrypt plain text P1 with the derived key, you will get a different cipher text out the other side. But those different cipher texts can all be decrypted with the same key.
The code below shows the full use of this class based on the interface above.
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; namespace CryptoLibrary { public class AES : IAES { private readonly int _saltSize = 32; public string Encrypt(string plainText, string key) { if (string.IsNullOrEmpty(plainText)) { throw new ArgumentNullException("plainText"); } if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException("key"); } using (var keyDerivationFunction = new Rfc2898DeriveBytes(key, _saltSize)) { byte[] saltBytes = keyDerivationFunction.Salt; byte[] keyBytes = keyDerivationFunction.GetBytes(32); byte[] ivBytes = keyDerivationFunction.GetBytes(16); using (var aesManaged = new AesManaged()) { aesManaged.KeySize = 256; using (var encryptor = aesManaged.CreateEncryptor(keyBytes, ivBytes)) { MemoryStream memoryStream = null; CryptoStream cryptoStream = null; return WriteMemoryStream(plainText, ref saltBytes, encryptor, ref memoryStream, ref cryptoStream); } } } } public string Decrypt(string ciphertext, string key) { if (string.IsNullOrEmpty(ciphertext)) { throw new ArgumentNullException("ciphertext"); } if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException("key"); } var allTheBytes = Convert.FromBase64String(ciphertext); var saltBytes = allTheBytes.Take(_saltSize).ToArray(); var ciphertextBytes = allTheBytes.Skip(_saltSize).Take(allTheBytes.Length - _saltSize).ToArray(); using (var keyDerivationFunction = new Rfc2898DeriveBytes(key, saltBytes)) { var keyBytes = keyDerivationFunction.GetBytes(32); var ivBytes = keyDerivationFunction.GetBytes(16); return DecryptWithAES(ciphertextBytes, keyBytes, ivBytes); } } private string WriteMemoryStream(string plainText, ref byte[] saltBytes, ICryptoTransform encryptor, ref MemoryStream memoryStream, ref CryptoStream cryptoStream) { try { memoryStream = new MemoryStream(); try { cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write); using (var streamWriter = new StreamWriter(cryptoStream)) { streamWriter.Write(plainText); } } finally { if (cryptoStream != null) { cryptoStream.Dispose(); } } var cipherTextBytes = memoryStream.ToArray(); Array.Resize(ref saltBytes, saltBytes.Length + cipherTextBytes.Length); Array.Copy(cipherTextBytes, 0, saltBytes, _saltSize, cipherTextBytes.Length); return Convert.ToBase64String(saltBytes); } finally { if (memoryStream != null) { memoryStream.Dispose(); } } } private static string DecryptWithAES(byte[] ciphertextBytes, byte[] keyBytes, byte[] ivBytes) { using (var aesManaged = new AesManaged()) { using (var decryptor = aesManaged.CreateDecryptor(keyBytes, ivBytes)) { MemoryStream memoryStream = null; CryptoStream cryptoStream = null; StreamReader streamReader = null; try { memoryStream = new MemoryStream(ciphertextBytes); cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read); streamReader = new StreamReader(cryptoStream); return streamReader.ReadToEnd(); } finally { if (memoryStream != null) { memoryStream.Dispose(); memoryStream = null; } } } } } } }
