Hello readers! It has been awhile since my last post. Lots of things have been going on lately and I have been busy developing some great code for some great people. In this entry I am going to show you a bit of plain text file encryption using the Rijndael Algorithm. This example is a fairly simple one and I hope to explain the basics as I move through an example class. So sit back, grab your latte and enjoy another episode of the Programming Underground!
Rijndael is a cipher that was developed by two Belgian cryptographers, Joan Daemen and Vincent Rijmen. It is pronounced “rain doll” and is an Advanced Encryption Standard (AES) style of algorithm. AES has been selected by the US government as a secure enough encryption that it can even be used to encrypt classified information. Does this mean it can’t be attacked? Absolutely not. However up until last year it was pretty solid with only side attacks being reported. In November 2009 other attack methods were shown on a reduced version of this algorithm. But for most work this is a pretty solid encryption, especially if you add enough rounds to it (greater than 8 typically).
You can read up more on the history of the algorithm and its successful attack history on the web. Here we are going to use the basics of the Rijndael object on the .NET framework which can be found using the namespace System.Security.Cryptography. In the example below, we wrap this functionality into a class which will be able to write encrypted data to a plain text file and then be able to read it back later.
During the process of encryption, we are going to formulate a key and an initialization vector. These two items are generated after we create an instance of the Rijndael object, stored in our wrapper class as private variables and exposed through two properties called RijndaelKey and RijndaelVector. These two items would be saved elsewhere and then used whenever we want to decrypt the file we encrypted.
Below is our example class…
class Encryption { // Hold information related to the key and vector used during encryption/decryption private byte[] Key; private byte[] Vector; private string fileName; private Rijndael RijndaelAlg; // Constructor public Encryption(string file) { RijndaelAlg = Rijndael.Create(); // Save the Key and Initialization Vector locally Key = RijndaelAlg.Key; Vector = RijndaelAlg.IV; fileName = file; } // Property to get the encryption key public byte[] RijndaelKey { get { return Key; } set { Key = value; } } // Property to get the initialization vector public byte[] RijndaelVector { get { return Vector; } set { Vector = value; } } // Read the saved file source and return its string representation of the data. public string readSource() { string result = ""; if (File.Exists(fileName)) { FileStream fStream = new FileStream(fileName, FileMode.Open); CryptoStream decryptStream = new CryptoStream(fStream, RijndaelAlg.CreateDecryptor(Key, Vector), CryptoStreamMode.Read); StreamReader reader = new StreamReader(decryptStream); try { result = reader.ReadToEnd(); } catch (Exception decryptEx) { throw new Exception("Error decrypting source file. Error: " + decryptEx.Message); } finally { reader.Close(); decryptStream.Close(); fStream.Close(); } } else { throw new Exception("The source file could not be located. File name: " + fileName); } return result; } // Encrypt data and save it into the specified file of this class. // During the process we also save the key and vector used for later decryption. public void saveSource(string data) { try { FileStream fStream = new FileStream(fileName, FileMode.OpenOrCreate); CryptoStream cryptStream = new CryptoStream(fStream, RijndaelAlg.CreateEncryptor(Key, Vector), CryptoStreamMode.Write); StreamWriter writer = new StreamWriter(cryptStream); try { writer.Write(data); } catch (Exception streamException) { throw new Exception("Error saving the data to file. Error: " + streamException.Message); } finally { writer.Close(); cryptStream.Close(); fStream.Close(); } } catch (Exception ex) { throw new Exception("There was a problem saving the data to file. Error: " + ex.Message); } } }
As you can see from the code above, our class is named “Encryption” and we have a simple set a variables which are going to hold the Key and Vector items (byte arrays), the Rijndael object and our string representing the file associated with this object. In the constructor of our class we create an instance of the Rijndael object and take in a path to a file we are going to read/write our encryption from/to. This makes it so that one instance of our class is tied directly to one file location.
When we create the instance of the Rijndael object, it will generate a special key and initialization vector for this particular instance object. Thus we fetch them from the Rijndael object and save them away in the class so we can use them to read or write to the file on command. Since these are unique to this particular object only, it will be important that if you wish to write to a file and either destroy the instance or shut down the program later, we must first save the key and vector so you can get them later to decrypt the file.
This is why we create two properties to get/set the key and vector for our class. This allows us to create an instance of Encryption, encrypt a file using the saveSource() method, get access to the key and vector used to encrypt and save them away. Then later we go fetch that key and vector, set a new instance of Encryption to use these values and then use readSource() to read the file. This will make more sense a little bit later.
Following our properties we have the saveSource() method which will save our data to the file and the readSource() method which will return the string of text from the file decrypted.
During the encryption and decryption process, we use a simple file stream with one twist… we use a CryptoStream. This stream allows us to take a normal file stream and apply a transformation to it. Think of it as a middle man between the buffer on our computer and what makes it to the actual disk. Have you ever played the game Telephone in grade school where you whisper a saying into someone’s ear and then they tell the next person in line what they think they heard? At the end of the line the person says what they were told to see how different it was from what was said at the beginning of the line? Well this CryptoStream is that middle man in the line where he hears “hello world” and decides to tell the file on disk “8=Ò‚J»D>6³{G)Ëw”. This “middle man” is also the one who reads from disk “8=Ò‚J»D>6³{G)Ëw” and then translates it back to “hello world” for the program to use.
This middle man is created using the Rijndael.CreateEncryptor and Rijndael.CreateDecryptor methods. They take in a key and vector (the ones we would have saved) and with it can translate text back and forth. This is why it is important to save this information somewhere from prying eyes or give it to your other party to then use it to decrypt your message. If you destroy the Encryption class instance and try to create another one to read the first file, you will need to set the key and vector first to the one the original instance used to encrypt it. Below is a nice little example of what I mean…
private void button1_Click(object sender, EventArgs e) { // Two instances of our Encryption class. Both are using the same file but will have two different keys/vectors. Encryption enc = new Encryption("d:\\encrypttest.txt"); Encryption enc2 = new Encryption("d:\\encrypttest.txt"); // Get the key and vector of the first instance and save them. byte[] k = enc.RijndaelKey; byte[] v = enc.RijndaelVector; try { // Encrypt "hello world" using the first instance. enc.saveSource("hello world"); // Now use the key and vector to set the second instance // Otherwise the second instance won't be able to read the file. // These keys could have been read in and were created years ago. enc2.RijndaelKey = k; enc2.RijndaelVector = v; // Show the output to show it worked. MessageBox.Show(enc2.readSource()); } catch (Exception ex) { MessageBox.Show(ex.Message); } }
The example sets up two instances of our class, one to encrypt the file and the other one to read it. However, to read the original file, it has to use the same key and vector as the first. So we set them using our properties and read the file. If you do not set them properly, you will see our class throw some custom errors or an error about padding. This means you are using either a bad key or vector. You can trap these messages which custom error handling in your application.
Not too bad to see how this works out right? The class hides away the idea of even needing to setup a stream to read and write to file. The only thing we do have to keep track of is that key and vector. The key and vector could be stored in your application’s settings or somewhere secure. If you shut down your program and restart it, reading that key and vector back in will set you up and get you back in business.
With this class you can even setup multiple files all using different keys and vectors. Just be sure you keep track of which key/vector goes with which file.
So I hope you enjoy the class and find ways to extend it and build it up. All code here on the programming underground is free for the taking and I hope you find some great uses for it. Thanks for reading! 🙂