As I mentioned last week, I’m working on a public key encryption tool. Something that requires very little setup, and no cumbersome key management that is usually necessary with established tools such as GPG or PGP. One of the things I did not want to do myself was the implementation of the actual encryption algorithms. While it is a great exercise to do this, there are just so many subtle ways to accidentally leak information that I decided not to bother with it. So I decided to use something that is already implemented and built into the language. For example, C# already has a built in implementation of RSA algorithm. You can generate a key pair like this:
// generate 2048 bit RSA key pair RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(2048));
Looks perfectly acceptable to me. Simple, quick, easy to apply. Once you have the object you can export the key and write it into a file, or you can squirrel it away from the user and hide it into a “key store” like so:
CspParameters csp = new CspParameters(); csp.KeyContainerName = "MyTPK"; RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(2048, csp);
If you do the above, your key pair will be automatically saved under KeyContainerName. If a key was already saved under that name, it will be fetched and used to create the object instead giving you nice way to maintain persistence without actually writing a lot of code or reading/writing temp files. You can clear your key store at any time by calling RSA.Clear();.
Encryption is equally easy – it takes a byte array and returns a byte array so you can do something like this:
// read in a file as a byte array byte plaintext = File.ReadAllBytes("/some/file/here"); byte ciphertext = RSAEncrypt(plaintext, RSA.ExportParameters(false), false);
The first argument is the plaintext, the second one is the encryption key, and the third one tells you whether or not to use padding. In this example I’m using my own public key (by exporting it from the RSA object) to encrypt the message. If you wanted to use some other key, you could import it like this:
string key = "public key in XML format"; RSACryptoServiceProvider temprsa = new RSACryptoServiceProvider(2048); temprsa.FromXmlString(key);
Now you can use temprsa to encrypt with that particular key. You obtain the key by calling analogous ToXML() method on the other end of the equation.
This worked quite well on my test file, which was just the word “text” saved as a plain text file. But as soon as I tried to test it on any larger file it failed with an CryptographicException containing a very descriptive message which said “Bad data”.
I had no clue as to what could have been causing the problem – mostly because I never really looked very closely at how RSA works. I knew it was an asymmetric encryption algorithm and that as enough for me. If I looked at it more closely, I would have discovered that it has an interesting property: namely that the encrypted message must be smaller than the key. That’s just how it works. RSA was designed for exchanging small amounts of information – for example the symmetric encryption key swap which happens at the beginning of an SSL session. It was never intended to be used to encrypt large files.
In other words, it was useless for my purposes. Now I had a choice to make:
- Abandon RSA altogether and look for a different public key algorithm
- Figure out the maximum message size it supports, then encrypt my file piecemeal – one tiny part at a time
- Use RSA for what it was designed: encrypting a symmetric key
Option #2 seemed work intensive an error prone. Not only that, but depending on how I implemented it, slicing and dicing the file could potentially leak information. So I picked the option #3.
This of course meant I would have to modify my concept a little bit. Instead of one key exchange I would have to have two. First I would need the users to exchange their public files, use them to encrypt a symmetric key and then use that… Or perhaps not. I could probably keep it as a single key exchange – at least as far as they user was concerned. The new procedure would look like this:
- The recepient generates an RSA public key and gives it to the sender over unencrypted link
- Sender generates a random symmetric (AES) key and uses it to encrypt a fle
- Sender encrypts the AES key with the RSA key and then concatenates it to the encrypted file then sends the whole bundle to the recepient
- Recipient extracts the RSA encrypted AES key and uses it to decrypt the rest of the file
Here is how the code for the encryption end would look:
string key = "some xml here"; // this is given AES = new AesCryptoServiceProvider(); AES.GenerateKey(); // 32 bytes AES.GenerateIV(); // 16 bytes // concatenate AES key and IV into a single byte array byte  buff = new byte[AES.Key.Length + AES.IV.Length]; //48 bytes Buffer.BlockCopy(AES.Key, 0, buff, 0, AES.Key.Length); Buffer.BlockCopy(AES.IV, 0, buff, AES.Key.Length, AES.IV.Length); // encrypt the AES key and iv, using the public key RSACryptoServiceProvider temprsa = new RSACryptoServiceProvider(2048); temprsa.FromXmlString(key); buff = temprsa.Encrypt(buff, temprsa.ExportParameters(false), false); // 256 bytes // read in the plaintext file byte data = File.ReadAllBytes(path); // encrypt file with AES AesCryptoServiceProvider AES = new AesCryptoServiceProvider(); ICryptoTransform encryptor = AES.CreateEncryptor(); data = encryptor.TransformFinalBlock(data, 0, data.Length); // concatenate the encrypted key, on top of your ciphertext byte output = new byte[buff.Length + data.Length]; Buffer.BlockCopy(buff, 0, output, 0, buff.Length); Buffer.BlockCopy(data, 0, output, buff.Length, data.Length); // now you write output into file, or do whatever
On the opposite end you do the reverse:
// read in the ciphertext byte buff = readFile(path); // create a bunch of empty arrays byte key = new byte; byte iv = new byte; byte temp = new byte; // will hold the encrypted key byte encdata = new byte[buff.Length - 256]; // will hold the encrypted data // copy the key and ciphertext data into the newly created arrays Buffer.BlockCopy(buff, 0, temp, 0, temp.Length); Buffer.BlockCopy(buff, 256, encdata, 0, encdata.Length); // grab the RSA key from the key store CspParameters cspParams = new CspParameters(); cspParams.KeyContainerName = "MyTPK"; RSA = new RSACryptoServiceProvider(2048, cspParams); // decrypt temp = RSA.Decrypt(temp, RSA.ExportParameters(true), false); // copy the key and iv to separate arrays Buffer.BlockCopy(temp, 0, key, 0, key.Length); Buffer.BlockCopy(temp, 32, iv, 0, iv.Length); AesCryptoServiceProvider AES = new AesCryptoServiceProvider(); AES.Key = key; AES.IV = iv; // decrypt ICryptoTransform decryptor = AES.CreateDecryptor(); byte  data = decryptor.TransformFinalBlock(encdata, 0, encdata.Length); // now you write data into a file or whatever
Granted, the method above is going to be impractical for large files – mostly due to the amount of in-memory copying I’m doing. Then again, I have never intended this tool to be used to exchange more than few MB of data.
Now that I did all this work, I believe there is an even simpler way of doing this. I probably could have used the RSAPCKS1KeyExchangeFormatter class in order to abstract much of my mucking around with the RSA keys into 2 or 3 lines of code. But I think that at the end of the day this is how it works.
Anyways, I will release the source code for the whole damn thing shortly. As usual, constructive criticism and tips on how to improve the tool are greatly appreciated.