C# AES and RSA File Encryption - How to use IV?

5.7k views Asked by At

I'm writing a program at the moment that works under the following scenario:

  • I've got some confidential log files that I need to backup to a server.
  • I have a program that generates these log files every day.
  • These log files would rarely if ever need to be opened.
  • I have only one RSA public/private key pair.
  • The program has only the RSA public key.
  • I generate a random AES key each time the program makes one of these confidential files.
  • The program uses this AES key to encrypt the log file.
  • I then use the RSA public key to encrypt the AES Key
  • I then backup both the AES encrypted file and RSA encrypted AES key to the server.

As far as I understand, that protocol is fitting for my use case.

The issue I'm having is coding it up in C#. I ran into needing an Initialization Vector(IV) for my AES encryption, I tried to encrypt this along with the AES key by using the public RSA key on both. But the 512(2 * 256) size is larger than RSA is happy to encrypt. So I figured out since I created the Initialization Vector randomly each time just like the AES Key, I can add the IV to the front of the AES ciphertext. However, I'm not sure where the code to do this would be inserted in my functions

Any help in the right direction to the "protocol" or other ways to write the IV to the ciphertext would be great. Thank you in advance.

static public Tuple<byte[], byte[]> EncryptAES(byte[] toEncryptAES, RSAParameters RSAPublicKey)
    {
        byte[] encryptedAES = null;
        byte[] encryptedRSA = null;

        using (MemoryStream ms = new MemoryStream())
        {
            using (RijndaelManaged AES = new RijndaelManaged())
            {
                AES.KeySize = 256;
                AES.BlockSize = 128;
                AES.Mode = CipherMode.CBC;
                AES.GenerateIV();
                AES.GenerateKey();
                encryptedRSA = RSAEncrypt(AES.Key, RSAPublicKey);

                using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    ms.Write(AES.IV, 0, AES.KeySize); //DOESNT WORK HERE
                    //Can't use CS to write it to the stream else it will encrypt along with file
                    cs.Write(toEncryptAES, 0, toEncryptAES.Length);
                    cs.Close();
                }
                encryptedAES = ms.ToArray();
            }
        }
        return new Tuple<byte[], byte[]>(encryptedAES, encryptedRSA);
    }
    static public byte[] DecryptAES(byte[] toDecryptAES, byte[] AESKeyAndIV, RSAParameters RSAPrivateKey)
    {
        byte[] AESKey = RSADecrypt(AESKeyAndIV, RSAPrivateKey);

        using (MemoryStream ms = new MemoryStream())
        {
            using (RijndaelManaged AES = new RijndaelManaged())
            {
                AES.KeySize = 256;
                AES.BlockSize = 128;
                AES.Key = AESKey;
                ms.Read(AES.IV, 0, AES.KeySize); //Not sure if can read MS here
                AES.Mode = CipherMode.CBC;

                using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
                {
                    //Would I need to move 0 to 256?
                    cs.Write(toDecryptAES, 0, toDecryptAES.Length);
                    cs.Close();
                }
                return ms.ToArray();
            }
        }
    }
1

There are 1 answers

2
Scott Chamberlain On BEST ANSWER

You where quite close, write out the IV before you create the CryptoStream

static public Tuple<byte[], byte[]> EncryptAES(byte[] toEncryptAES, RSAParameters RSAPublicKey)
{
    byte[] encryptedAES = null;
    byte[] encryptedRSA = null;

    using (MemoryStream ms = new MemoryStream())
    {
        using (RijndaelManaged AES = new RijndaelManaged())
        {
            AES.KeySize = 256;
            AES.BlockSize = 128;
            AES.Mode = CipherMode.CBC;
            AES.GenerateIV();
            AES.GenerateKey();
            encryptedRSA = RSAEncrypt(AES.Key, RSAPublicKey);

            ms.Write(AES.IV, 0, AES.KeySize); //Move the write here.

            using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
            {
                cs.Write(toEncryptAES, 0, toEncryptAES.Length);
                cs.Close();
            }
            encryptedAES = ms.ToArray();
        }
    }
    return new Tuple<byte[], byte[]>(encryptedAES, encryptedRSA);
}

For the decrypt, make sure you loop over the read till you have fully read the byte[] for the IV, Stream.Read is not guaranteed to read all the bytes you asked it to read. I usually make a static method ReadFully to ensure all bytes are read.

private static byte[] ReadFully(Stream stream, int length)
{
    int offset = 0;
    byte[] buffer = new byte[length];
    while(offset < length)
    {
        offset += stream.Read(buffer, offset, length - offset);
    }
    return buffer;
}

Then just use that method to read in the IV. You also want to use cs.Read not cs.Write to read out the encrypted data and put the stream in to read mode, however it is easier to just use .CopyTo and copy the data to a new MemoryStream.

static public byte[] DecryptAES(byte[] toDecryptAES, byte[] AESKeyAndIV, RSAParameters RSAPrivateKey)
{
    byte[] AESKey = RSADecrypt(AESKeyAndIV, RSAPrivateKey);

    using (MemoryStream source = new MemoryStream(toDecryptAES))
    {
        using (RijndaelManaged AES = new RijndaelManaged())
        {
            AES.KeySize = 256;
            AES.BlockSize = 128;
            AES.Key = AESKey;
            var iv = ReadFully(source, AES.KeySize);
            AES.IV = iv;
            AES.Mode = CipherMode.CBC;

            using (var cs = new CryptoStream(source, AES.CreateDecryptor(), CryptoStreamMode.Read))
            {
                using(var dest = new MemoryStream())
                {
                    cs.CopyTo(dest);
                    return dest.ToArray();
                }
            }
        }
    }
}

For other readers, note that RSAEncrypt and RSADecrypt are wrappers for calls to the RSACryptoServiceProvider.