Temporary Public Key: Continued

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:

  1. Abandon RSA altogether and look for a different public key algorithm
  2. Figure out the maximum message size it supports, then encrypt my file piecemeal – one tiny part at a time
  3. 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:

  1. The recepient generates an RSA public key and gives it to the sender over unencrypted link
  2. Sender generates a random symmetric (AES) key and uses it to encrypt a fle
  3. 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
  4. Recipient extracts the RSA encrypted AES key and uses it to decrypt the rest of the file
  5. ???
  6. Profit

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[32];
byte[] iv = new byte[16];
byte[] temp = new byte[256]; // 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.

This entry was posted in Uncategorized. Bookmark the permalink.



One Response to Temporary Public Key: Continued

  1. Martinho Fernandes Google Chrome Windows says:

    I used to forget about that particular limitation of RSA, too. I’ve made that mistake two or three times before I finally gave up and started remembering it.

    Regarding the praticality of use against large files: instead of reading all of the data into memory, you could use a CryptoStream and “stream through it”. You provide it with the encryptor or decryptor creating from the Aes algorithm and it takes care of all the error-prone streaming and buffering operations.

    If you can use .NET4 (maybe you can’t because your victims audience won’t have it installed), there’s a CopyTo method on the Stream class that simply pumps an entire stream to another, with a buffer size parameter. If you can’t you’ll have to do the pumping “by hand”.

    Reply  |  Quote

Leave a Reply

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