Building a Simple AES Encryption Class
Welcome friends. This is the first article in a series that will eventually culminate in creating a network messaging class which can be used by a host to send and receive messages across a network. If you bookmark my profile and check back, you will be able to follow along and build the class yourself. As part of this larger class, we'll also build several supporting classes to perform lower level functionality. This article will focus on creating a simple encryption class which our messenger will use to encrypt/decrypt data as it's sent across the wire. Like future articles, this one will build on some of my previous articles and will reference the underlying concepts that we've previously discussed within them. If you haven't already, I recommend that you read the following articles before continuing, to better understand some of the code that we'll be writing as part of this exercise...
Before We Begin:
Before we get started on our shiny new encryption class, let's pause for a second to briefly go over the full messenger, so you'll have a better understanding of how this encryption class fits into the bigger design. Here is a mission statement and high level feature set for our messenger class.
Mission Statement: Build a class that allows the host application to send and receive messages in the form of objects between the client and the server and vice versa. The messages should be dynamic such that the client and server are not required to know of each message type. The messages should be encrypted while being transmitted. The messages should be fire-and-forget by using asynchronous methods that send and receive on a background thread. Re-usability and separation of duties should be paramount.
- The network messaging class will be a single library that provides both client and server functionality and will use TCP to send/receive data.
- The network messaging class will convert the objects to Json using the NewtonSoft.Json library and encrypt the data before transmittal.
- The network messaging class will handle sending/receiving, encrypting/decrypting and exposing the message to the host application so that the host is not required to have knowledge of the underlying system.
That's it. This may seem simple on the surface, but there will be quite a bit of functionality in the underlying classes and it will build on everything that we've learned in the previous articles linked above. This is your last chance to go back and read up on those concepts before we get started. Did you do that? Good, let's go.
The Encryption Class:
So, as you can see from the requirements above, this encryption class will fit between the messaging class and the TCP class and will be used by the messaging class to convert string data to/from encrypted bytes before they're sent across the network. As you may be able to guess, this encryption class will need to encrypt as well as decrypt the data on each end of the network connection. Given this requirement , the encryption class will need to provide an Encryption Key that is known by both the client and the server. There are plenty of other ways to provide this functionality such as with an encryption certificate. However, to keep things simple, we'll allow the host application to provide the key (as well as a salt key, but we'll get into that later). Due to this simplicity, I do not recommend using this encryption class for anything other than encrypting the simplest and non-trivial data. It should be used only as a way to keep prying eyes from sniffing the network and reading the data. If more security is required, you should extend this class to allow for certificates or some other form of encryption. If you've been following along with my other articles, you may be able to guess that we'll be using dependency injection to build the encryption class as a controller provided to a larger class and thus, you can extend it later to fit your needs if desired.
One more thing: this encryption class is not designed to be feature rich, but rather simple and to abstract away as much of the complexity of the encryption process as possible. As the title of the article indicates, the class will be using AES encryption. AES provides quite a bit of functionality that will not be used in this class. We're really only focused on encrypting a string to be transmitted across the network that's decrypted on the other side.
With all the formalities out of the way, let's get started.
First Things First:
Step one of building our encryption class is to build the underlying foundational functionality, not part of this class, that this class will use. First, let's define what we'll need.
The first few things we'll need to build or acquire are the following extensions:
- IsNullEmptyOrWhiteSpace: this extension is one of my Extension for Everyday Use and can be found in the Struct.Core.Extensions library: here as part of the String class. You can either download the entire Struct.Core.Extensions library or add only the extensions you need by following the directions below. If you're not familiar with the extension from some of my other articles, here's what it looks like...
/// <summary>
/// This extension uses the System.IsNullOrWhiteSpace
/// to return true if the string is Null, string.Empty,
/// or contains only white space. Otherwise, this
/// extension returns false.
/// </summary>
/// <param name="Target">Target String</param>
/// <returns>Is Null, Empty, or White Space</returns>
public static bool IsNullEmptyOrWhiteSpace(this string Target)
{
return string.IsNullOrWhiteSpace(Target);
}
- ToArrayOrEmpty: this extension is another of my Extensions for Everyday Use and can be found in the Generic class within the Struct.Core.Extensions library. The extension guarantees that an array is not null by either returning the array or returning a zero length instance of the array thus insuring that the array is never null. You can learn more about Generic T, which this extension uses, in my Using Generic T with Extensions article. Here's what the code looks like...
/// <summary>
/// This extension checks to make sure that the target
/// being returned is always an array. If the target
/// is null then a zero length array is returned. Use
/// this extension when you need to insure that your
/// data type array has been initialized.
/// </summary>
/// <param name="Target">Target Array</param>
/// <returns>Populated Array or Empty Array</returns>
public static T[] ToArrayOrEmpty<T>(this T[] Target)
{
return Target == null ? new T[0] : Target;
}
Create a new project and add a new class named Extensions to it. Make sure the class is static and then add the two extensions above, if you didn't download and compile the Struct.Core.Extensions library. Once you have the Extensions class created with our without the two extensions above, add one more extension to your Extensions class which will only be used by this project and is not part of Struct.Core.Extensions...
/// <summary>
/// This extension is used to insure that
/// the key being used is at least one
/// character and converts it by duplicating
/// the characters until it's the required
/// length. This allows the developer to provide
/// a string of any length and still use it for
/// encrypting/decrypting.
/// </summary>
/// <param name="Value">The Key Value</param>
/// <param name="KeyLength">The Length Needed</param>
/// <returns>Converted Key of Required Length</returns>
public static byte[] ConvertToCryptoKey(this string Target, int KeyLength)
{
if (Target.IsNullEmptyOrWhiteSpace())
throw new ArgumentNullException("Target");
else if (KeyLength < 1)
throw new Exception("KeyLength parameter must be greater than zero.");
// just duplicate the Value until
// we get to the required length
while (Target.Length < KeyLength)
Target += Target;
// return the required characters
return Encoding.UTF8.GetBytes(Target.Substring(0, KeyLength));
}
This extension will be used to convert the keys which we'll provide later and insure that they keys are of the correct length. We'll talk more about the requirement later when we get into the encryption class. For now, we only need to know that this extension is attached to the String data type and when it's called it will insure that the Target string contains at least one character. It then creates a while loop and continues to duplicate and concatenate the value back onto the target until the target is as long or longer than the KeyLength value. So for example, if we call ConvertToCryptoKey on the word Struct and pass a key length of 16, the while loop will continue to concatenate the word Struct onto the word Struct until it reaches a length of at least 16. In this example, it will break when we reach StructStructStruct which is 18 characters long. The last step in the extension is to return the first part of the string up to the length provided in the KeyLength value. The reason for this will become apparent later but suffice it to say this extension will be used, for simplicity sake, to insure that our encryption key is the correct length regardless of the length passed by the host application.
Building for the Future:
The next step in our encryption class, is to create the interface that our class will use. As I mentioned previously, our messaging class will use this encryption class via dependency injection to encrypt and decrypt the data. To achieve the goal of re-usability and scale-ability, our class needs to provide an interface of what it means to encrypt and decrypt data. Let's take a look at the interface our encryption class will be using...
public interface ICryptor
{
byte[] Encrypt(string StringToEncrypt);
string Decrypt(byte[] BytesToDecrypt);
}
It doesn't get much simpler than that. We have one interface named ICryptor and it contains two methods, Encrypt and Decrypt. As I mentioned previously, one of our main goals is to keep things simple and this interface is no exception to that rule. As you can see the interface provides two simple methods that our concrete class must implement. First is the Encrypt method, which will require the host to pass in a string to be encrypted. If you've worked with encryption before, you may have been expecting this method to require a byte array. However, again, for simplicity sake, we know that our messenger class will have previously converted our object to a Json string and therefore, we're going to allow the messaging class to pass that string directly into our encryption class. We'll convert the string to a byte array within the encryption method, perform the encryption, and then pass out the byte array as a result. The returned byte array can then be passed directly to our network TCP class and sent across the wire.
The second method provided by our interface is the Decrypt method. Since this method will be called from the messenger class directly after receiving encrypted data from the network, we know that it will have received a byte array and therefore we allow the messenger class to pass the byte array directly into the Decrypt method. After the decryption, we'll convert the byte array into a string--in the form of a Json string, if everything goes to plan--and pass it back out to the messenger class.
Now that we have our underlying code written, let's get started on our actual encryption class.
Building the Cryptor Class:
The first thing well do is stub out our class and discuss the parameters, properties and methods, then we'll fill in those methods and discuss each in more detail. Here is what a stub of the encryption class looks like...
public class AesCryptor : ICryptor
{
/// <summary>
/// Aes Encryptor
/// </summary>
private readonly Aes Aes;
/// <summary>
/// This is the constructor for the AES
/// cryptor. EncryptionKey and SaltKey
/// must be at least 32 characters.
/// </summary>
/// <param name="Password"></param>
/// <param name="Salt"></param>
public AesCryptor(string EncryptionKey, string SaltKey)
{
}
/// <summary>
/// This method is used to encrypt a string
/// using AES encryption.
/// </summary>
/// <param name="StringToEncrypt">String</param>
/// <returns>Encrypted Bytes</returns>
public byte[] Encrypt(string StringToEncrypt)
{
}
/// <summary>
/// This method is used to decrypt previously
/// encrypted bytes back into a string.
/// </summary>
/// <param name="BytesToDecrypt">Encrypted Bytes</param>
/// <returns>Decrypted String</returns>
public string Decrypt(byte[] BytesToDecrypt)
{
}
/// <summary>
/// Dispose the Aes when the class
/// is destroyed
/// </summary>
~ AesCryptor()
{
}
}
As you can see, we've created a concrete class named AesCryptor. This simple name is designed to indicate to future developers that this class will be encrypting and decrypting data and that it will use AES as the cryptography type. The class includes one private property named Aes. This property contains the System.Security.Cryptography.Aes class which provides the underlying functionality for encrypting and decrypting. Additionally, you can see that this class inherits from our ICryptor interface and therefore will implement our two methods, Encrypt and Decrypt, which we previously discussed. Finally, our class will have a constructor that expects an EncryptionKey and a SaltKey (also known as the Initialization Vector but that's not nearly as fun to say as salt) as well as a Deconstructor where we can dispose of our Aes class when our class is destroyed. Let's pause for a second and take a look at what the encryption key and salt key are used for. Please consider this definition from Microsoft...
For a given secret key k, a simple block cipher that does not use an initialization vector will encrypt the same input block of plain text into the same output block of cipher text. If you have duplicate blocks within your plain text stream, you will have duplicate blocks within your cipher text stream. If unauthorized users know anything about the structure of a block of your plain text, they can use that information to decipher the known cipher text block and possibly recover your key. To combat this problem, information from the previous block is mixed into the process of encrypting the next block. Thus, the output of two identical plain text blocks is different. Because this technique uses the previous block to encrypt the next block, an initialization vector is needed to encrypt the first block of data.
The definition above might be confusing, but let me put it in layman's terms: The encryption key is used to randomize the data being encrypted. However, because of the way encryption(Cipher Block Chaining) works, using just the key allows someone to figure out the key by knowing some of the text. Once they know the key, they can decrypt all of your encrypted data at will and that's not good. To avoid this issue, the salt key (or Initialization Vector) is used. This allows the encryption process to randomize the beginning of the encrypted string, which causes all encrypted data downstream to be randomized.
Okay, that's a brief bit of info on encryption. Let's move on to the actual code of our encryption class, starting with the constructor. Please take a look at this constructor code...
/// <summary>
/// This is the constructor for the AES
/// cryptor. EncryptionKey and SaltKey
/// are expected and must be at least
/// one character each.
/// </summary>
/// <param name="Password"></param>
/// <param name="Salt"></param>
public AesCryptor(string EncryptionKey, string SaltKey)
{
// check required password
if (EncryptionKey.IsNullEmptyOrWhiteSpace())
throw new ArgumentNullException("EncryptionKey");
// check required salt key
else if (SaltKey.IsNullEmptyOrWhiteSpace())
throw new ArgumentNullException("SaltKey");
// create the encryptor & decryptor
this.Aes = Aes.Create();
// set the iv and the key
this.Aes.Key = EncryptionKey.ConvertToCryptoKey(32);
this.Aes.IV = SaltKey.ConvertToCryptoKey(16);
// set the padding size (might not need)
this.Aes.Padding = PaddingMode.PKCS7;
// ciphermode is cbc which sets initial values
// based on xor of salt key.
this.Aes.Mode = CipherMode.CBC;
}
Here are the guts of our AesCryptor constructor. Since our class is not static, this code will be run when our host creates an instance of the AesCryptor class. You can see that...
- First we're checking to make sure the EncryptionKey and the SaltKey are not empty. We must at least have one character in each of these variables, else we will have nothing to duplicate further down in our ConvertToCryptoKey extension which we discussed earlier.
- The next step is to create an instance of the Aes private property that we discussed earlier. Recall that this class is the underlying System.Security.Cryptography.Aes class which provides the functionality for encrypting and decrypting via the .Net framework.
- Next our constructor will execute the ConvertToCryptoKey extension on our EncryptionKey and SaltKey values. You'll notice that the EncryptionKey is set to a length of 32 and the SaltKey is set to a length of 16. This is required by the type of encryption that we're employing and the size of the data block being used by the underlying .Net Aes class. If you would like to understand why this is, you can read more about AES encryption here at Microsoft's site. These values are passed directly into the Aes.Key and Aes.IV properties of the underlying Aes class.
- Next we'll need to set the padding type for the underlying .Net Aes class. Again, you can learn everything you need to know about padding here, but it's enough to say that the padding determines what type of characters will be used to pad our string so that it can be encrypted successfully based on the Block Size. I like PKCS7 because it seems the least likely to interfere with the data that we're passing in to be encrypted or decrypted, but that's just my opinion.
- The final step in our constructor is to set the Cipher Mode. This determines how we'll be encrypting our data and as I said earlier, we're using CBC (Cipher Block Chaining). One more time, you can learn all about CBC here. It's enough to know that CBC uses the previous blocks data to randomize the current block of data being encrypted, and will use the current blocks data to randomize the next block, etc, etc. Recall that this is why we needed the salt key; to provide the 'randomize' data for the first block, thus kicking off the entire chain.
/// <summary>
/// Dispose the Aes when the class
/// is destroyed
/// </summary>
~ AesCryptor()
{
if (this.Aes != null)
this.Aes.Dispose();
}
The next step in our AesCryptor is to include a de-constructor. This is the code that will run when our class is destroyed. Here we'll dispose of our Aes class which we created during construction. This will clean up any processes, including any un-manged code that the Aes class may have created internally. This will normally be handled by Garbage Collection within the .Net Framework, but if the Aes class does have hooks to un-managed code and doesn't clean up during it's own deconstruction, it could cause memory leaks.
That's it for our constructor and de-constructor, we're now ready to start filling in our Encrypt and Decrypt methods. Let's take a look at the Encrypt method...
Encrypting Data:
/// <summary>
/// This method is used to encrypt a string
/// using AES encryption.
/// </summary>
/// <param name="StringToEncrypt">String</param>
/// <returns>Encrypted Bytes</returns>
public byte[] Encrypt(string StringToEncrypt)
{
// if there's nothing to encrypt
// just return an empty array
if (StringToEncrypt.IsNullEmptyOrWhiteSpace())
return new byte[0];
// we should create the new memory stream
// and crypto stream first. If this fails
// we should bubble up the error.
MemoryStream MS = new MemoryStream();
CryptoStream CS = CS = new CryptoStream(
MS,
this.Aes.CreateEncryptor(),
CryptoStreamMode.Write
);
try
{
// encrypt the string into the memory stream
CS.Write(Encoding.UTF8.GetBytes(StringToEncrypt), 0, StringToEncrypt.Length);
CS.FlushFinalBlock();
return MS.ToArray();
}
finally
{
// close the streams
if (CS != null) CS.Close();
if (MS != null) MS.Close();
}
}
It may seem complex, but it's really quite simple. Let's step through the Encrypt method and "layout this bag of snakes" as they say.
- First we take the normal step of making sure that the data being passed in isn't an empty string. If so, we'll simply pass out an empty byte array.
- The second step is to create a new MemoryStream which will hold our encrypted data.
- Our third step is to create the CryptoStream which is the class that will manage the work getting data into and out of the encryption class as well as kicking off the encryption process. As you can see, the constructor of this class requires us to pass in the memory stream we created in the previous step, a new Encryptor which we create from the Aes property that we created during construction of our own class, and define the stream mode which we set to Write to indicate that we'll be writing to the memory stream as apposed to reading from it.
Once we've got our encryption classes set up and ready to encrypt, we want to setup a Try...Finally block. We're doing this because regardless of what happens, we want to close out our MemoryStream and CryptoStream...even if there's a failure during the encryption process.
- The fourth step in our encryption process is to do the actual work of encrypting. We do this by calling the Write method on the CryptoStream class and passing in the bytes to be encrypted. Of course, since we allowed the host to pass in the data to be encrypted as a string, we'll need to convert it to a byte array as it's passed into the Write method. This also requires us to pass in the starting point in the byte array, as well as the length of the data to be written. Since we always want to pull the entire byte array, we'll just set the starting point to zero or the first byte in the array, and the length to the total length of the array. This will write out the entire byte array as encrypted bytes, into the MemoryStream.
- Because the Write doesn't always clear the cache of internal data (unless we call close on the CryptoStream) we'll need to call the FlushFinalBlock before returning the data from the MemoryStream. "But Jim, you're calling Close on the CryptoStream in the finally block" I hear you saying. To wich I reply, you're right, however, I'm also calling return before the finally block. So the question is: does calling return prior to the finally block return the data as it is when return is called, or as it is after the finally block executes? That's a question for you and you can leave a comment to this article with your answer.
So, that's it for the Encrypt method. We return the memory stream as an array of bytes and close our streams to clean up. Next we'll take a look at the Decrypt method which will be used when receiving encrypted data. Here's is the Decrypt method...
Decrypting Data:
/// <summary>
/// This method is used to decrypt previously
/// encrypted bytes back into a string.
/// </summary>
/// <param name="BytesToDecrypt">Encrypted Bytes</param>
/// <returns>Decrypted String</returns>
public string Decrypt(byte[] BytesToDecrypt)
{
// make sure we have something
BytesToDecrypt = BytesToDecrypt.ToArrayOrEmpty();
// we should create the new memory stream
// and crypto stream first. If this fails
// we should bubble up the error.
MemoryStream MS = new MemoryStream();
CryptoStream CS = new CryptoStream(
MS,
this.Aes.CreateDecryptor(),
CryptoStreamMode.Write
);
try
{
CS.Write(BytesToDecrypt, 0, BytesToDecrypt.Length);
CS.FlushFinalBlock();
// return the string
return Encoding.UTF8.GetString(MS.ToArray());
}
finally
{
// close the streams
if (MS != null) MS.Close();
if (CS != null) CS.Close();
}
}
As you recall, while the Encrypt method expected a string as input, due to the fact that the messenger class would be sending the Json string directly into the Encrypt method, on the flip side, the Decrypt method expects a byte array as input. This of course is because the messenger class will be pulling the encrypted bytes directly off the wire and into the Decrypt method. So, for ease of use, we've design this method to expect a byte array called BytesToDecrypt. Let's take a look at the guts of the Decrypt method...
- First as always, we'll call the ToArrayOrEmpty extension which we discussed earlier. This extension will insure that the byte array is always an array and never null so that we don't have trouble in our down stream functionality.
- Step two, just as with the Encrypt method, is to create a MemoryStream to hold our decrypted data.
- Step three is also slimier to the Encrypt method. We create a new CryptoStream to manage the stream into and out of the decryption process. As previously, the constructor of the CryptoStream requires that we pass in the MemoryStream we created in the previous step, a new Decryptor which we get from the private Aes class created within the constructor of our own class and finally, the write direction, which is still Write, since we'll be writing the decrypted data out to the memory stream.
I know, it seems like déjà vu but it's not. Just as in the Encrypt method, we'll create a Try...Fetch block to insure that our MemoryStream and CryptoStream get closed before we leave the Decrypt method.
- The next step is to write out the encrypted data from the CryptoStream to the MemoryStream and to do this, we'll pass in the BytesToDecrypt array, we'll specify that we should start at index zero of the array, and we should decrypt the entire array.
- Finally, we call the FlushFinalBlock just as before, to insure that the cache is emptied prior to returning the MemoryStream which we also convert to a string as it's returned.
Now that we've completed the AesCryptor class, let's setup a sample application to test it. We'll create an instance of the AesCryptor class and call the Encrypt method with a test string. If everything works, we should get back the byte array of encrypted data. We can then pass that byte array back into the Decrypt method and confirm from the output that the bytes are decrypted correctly. Let's take a look at the code...
class Program
{
static void Main(string[] args)
{
string Data = "The quick brown fox jumps over the lazy dog.";
// create cryptor
ICryptor Cryptor = new AesCryptor(
"Struct",
"Development"
);
// write the starting string
Console.WriteLine(string.Format("StartingString: {0}\n", Data));
// encrypt string to bytes
byte[] EncryptedBytes = Cryptor.Encrypt(Data);
// write the encrypted data
Console.WriteLine(string.Format("EncryptedData: {0}\n", Encoding.UTF8.GetString(EncryptedBytes)));
// decrypt bytes to string
string DecryptedString = Cryptor.Decrypt(EncryptedBytes);
// write the decrypted string
Console.WriteLine(string.Format("DecryptedString: {0}", DecryptedString));
}
}
Pretty simple, here we're creating the AesCryptor class as an ICryptor and passing in "Struct" as the encryption key value and "Development" as the salt key value. We can then call the Encrypt method and pass in our string "The quick brown fox jumps over the lazy dog." and receive back the encrypted bytes. We then pass the encrypted bytes back into the Decrypt method and receive the decrypted string back out. If the two strings match, we're good to go.
Here is the output from the test...
Summary:
So, that's it for the AesCryptor class. To recap, this class is designed to be as simple and re-usable as possible. It inherits from an interface and as I'm sure you're aware, this means the messenger class which we'll eventually be writing, will require us to pass in an ICryptor so that we can use dependency injection to provide future developers with the ability to pass in their own encryption/decryption class. The class provides a simple method for encrypting called Encrypt which will expect the host to pass in a string of data to be encrypted. The Encrypt method will pass out a byte array of encrypted data to most efficiently support the down stream processes. Additionally, the class provides a Decrypt method that expects a byte array, again, supporting the up stream process, and returns a decrypted string.
This class will be part of a larger series of articles that will eventually allow us to create a full messenger class that will send and receive objects across the network. This class could be used in any environment where you, the developer, are creating a server (such as a Windows Service or a server application) and a client(s) that need to communicate.
Note: You can find the complete solution for these code samples on my GitHub: Encryption Sample Code