C# – AESManaged File Encryption and Decryption and prepending initiaization vector

aesbytecencryption-symmetric

I am trying to encrypt/decrypt bytes – I have done a lot of reading about the Key and IV for the AES algorithm using the AESManaged class in System.Security.Cryptography. I read James Johnson's answer to the following question http://www.techques.com/question/1-7025135/My-Length-of-the-data-to-decrypt-is-invalid-error where he suggests that you use a random IV in the encryption routine and prepend the IV to the encrypted message. The decrypt function strips off the random IV from the beginning of the encrypted message to initialize the decryption class and then decrypts the rest of the bytes. I have attempted to do this in the following code. But I keep getting the "Length of the data to decrypt is invalid." error message when I attempt the decrypt after the encryption. Could someone possibly shed some light on what might be wrong.

       USAGE: (streamToEncrypt/streamToDecrypt are System.IO.Stream)

       using (var cryptoHelper = new AESHelper())
        {
            var encryptedBytes = cryptoHelper.Encrypt(AESHelper.StreamToByteArray(streamToEncrypt));

        }


using (var cryptoHelper = new AESHelper())
        {
            var decryptedBytes = cryptoHelper.Decrypt(AESHelper.StreamToByteArray(streamToDecrypt));                             
        }

 public class AESHelper : IDisposable
 {       
    public AesManaged AESManaged;
    internal ICryptoTransform Encryptor { get; set; }
    internal ICryptoTransform Decryptor { get; set; }
    private const string KEY = "2428GD19569F9B2C2341839416C8E87G";
    private static readonly byte[] Salt = Encoding.ASCII.GetBytes("?pt1$8f]l4g80");
    private const Int32 ITERATIONS = 1042;


    internal AESHelper()
    {
        AESManaged = new AesManaged();
        AESManaged.BlockSize = AESManaged.LegalBlockSizes[0].MaxSize;
        AESManaged.KeySize = AESManaged.LegalKeySizes[0].MaxSize;                    
        AESManaged.Mode= CipherMode.CBC;
    }
   public void KeyGenerator()
    {
        var key = new Rfc2898DeriveBytes(KEY, Salt, ITERATIONS);
        AESManaged.Key = key.GetBytes(AESManaged.KeySize / 8);           
    }
    public byte[] Encrypt(byte[] input)
    {
        KeyGenerator();
        var ms = new MemoryStream();
        //Random IV 
        Encryptor = AESManaged.CreateEncryptor(AESManaged.Key, AESManaged.IV);
        //Add the IV to the beginning of the memory stream
        ms.Write(BitConverter.GetBytes(AESManaged.IV.Length), 0, sizeof(int));
        ms.Write(AESManaged.IV, 0, AESManaged.IV.Length);
        var cs = new CryptoStream(ms,
          Encryptor, CryptoStreamMode.Write);
        cs.Write(input, 0, input.Length);
        cs.Close();
        return ms.ToArray();
    }
    public byte[] Decrypt(byte[] input)
    {
        KeyGenerator();

        // Get the initialization vector from the encrypted stream
        var ms = new MemoryStream(input);
        AESManaged.IV = ReadByteArray(ms);
        Decryptor = AESManaged.CreateDecryptor(AESManaged.Key, AESManaged.IV);
        var cs = new CryptoStream(ms,
         Decryptor, CryptoStreamMode.Write);
        cs.Write(input, 0, input.Length);
        cs.Close();//Error occurs here
        return ms.ToArray();
    }

    internal static byte[] ReadByteArray(Stream s)
    {
        var rawLength = new byte[sizeof(int)];
        if (s.Read(rawLength, 0, rawLength.Length) != rawLength.Length)
        {
            throw new SystemException("Stream did not contain properly formatted byte array");
        }
        var buffer = new byte[16];
        if (s.Read(buffer, 0, buffer.Length) != buffer.Length)
        {
            throw new SystemException("Did not read byte array properly");
        }

        return buffer;
    }
     internal static byte[] StreamToByteArray(Stream inputStream)
    {
        if (!inputStream.CanRead)
        {
            throw new ArgumentException();
        }

        // This is optional
        if (inputStream.CanSeek)
        {
            inputStream.Seek(0, SeekOrigin.Begin);
        }

        var output = new byte[inputStream.Length];
        var bytesRead = inputStream.Read(output, 0, output.Length);
        Debug.Assert(bytesRead == output.Length, "Bytes read from stream matches stream length");
        return output;
    }
  public void Dispose()
    {
        if (AESManaged != null)
            ((IDisposable) AESManaged).Dispose();

    }}

Many Thanks in advance

Best Answer

Probably you have solved this already but I'll just put my answer for others who faces similar issue.

Error occurs due to the additional information present in the input array. In public byte[] Encrypt(byte[] input) method you are writing IV length and IV before the ciphered data is written. Lines:

ms.Write(BitConverter.GetBytes(AESManaged.IV.Length), 0, sizeof(int));
ms.Write(AESManaged.IV, 0, AESManaged.IV.Length);

In public byte[] Decrypt(byte[] input) method you are reading this information and using read IV as initialization vector for AES algorithm. All fine. Then you are constructing CryptoStream with CryptoStreamMode.Write and passing MemoryStream object ms which gets decrypted data. However the passed input array contains not only the encrypted message but also the IV that you wrote during the encryption process. That is why it fails to decrypt.

What you need to do to overcome this is either extract only cipher data from the input array and pass it to: cs.Write(cipherData, 0, cipherData.Length); or change mode into CryptoStreamMode.Read and use cs.Read(outputBuff, 0, outputBuff.Length);.

Also don't use the same MemoryStream object to read and write to because you'll have some garbage in it after CryptoStream will write in it.

Related Topic