0

Rijndael File Encryption Issue

I want to encrypt large files using Rijndael but I getting out of memory exception error. Any idea? Here is my code

     public void Rijndael_EncryptFile(string password, string filepath, int opt)
    {
        try
        {
            byte[] keyBytes;
            keyBytes = Encoding.Unicode.GetBytes(password);
            Rfc2898DeriveBytes derivedKey = new Rfc2898DeriveBytes(password, keyBytes);
            RijndaelManaged rijndaelCSP = new RijndaelManaged();
            rijndaelCSP.BlockSize = opt; //128 256
            rijndaelCSP.KeySize = opt;   //128 256
            rijndaelCSP.Key = derivedKey.GetBytes(rijndaelCSP.KeySize / 8);
            rijndaelCSP.IV = derivedKey.GetBytes(rijndaelCSP.BlockSize / 8);
            rijndaelCSP.Mode = CipherMode.CFB;
            rijndaelCSP.Padding = PaddingMode.Zeros;
            ICryptoTransform encryptor = rijndaelCSP.CreateEncryptor();
            FileStream inputFileStream = new FileStream(filepath, FileMode.Open, FileAccess.Read);
            byte[] inputFileData = new byte[(int)inputFileStream.Length];
            inputFileStream.Read(inputFileData, 0, (int)inputFileStream.Length);
            FileStream outputFileStream = new FileStream(filepath + ".enc", FileMode.Create, FileAccess.Write);
            CryptoStream encryptStream = new CryptoStream(outputFileStream, encryptor, CryptoStreamMode.Write);
            encryptStream.Write(inputFileData, 0, (int)inputFileStream.Length);
            encryptStream.FlushFinalBlock();
            rijndaelCSP.Clear();
            encryptStream.Close();
            inputFileStream.Close();
            outputFileStream.Close();
        }
    }
  • 1
    The IV must be *unique* for CFB mode. Don't use a static IV, because that makes the cipher deterministic and therefore not semantically secure. An attacker who observes ciphertexts can determine when the same message prefix was sent before. And even worse with a streaming mode like CFB, an attacker might even be able to deduce the message if the IV is reused even when the messages are different. The IV is not secret, so you can send it along with the ciphertext. Usually, it is simply prepended to the ciphertext and sliced off before decryption. – Artjom B. Aug 15 '16 at 18:57

2 Answers2

2

OK, first your actual problem.

Your reading the whole file in to memory, encrypting it, then writing it to disk. A large byte array may not fit in to memory (even if you have enough "RAM").

You should instead read a chunk of data at a time. This chunk size is probably going to be a multiple of 16 bytes (the size of the AES block), write it to the use the CryptoStream, and when you reach the end of stream, close the CryptoStream so it applies any padding.

OK, other things.

First, don't use RijndaelManaged. Use AesManaged. Rijndael was the name of AES before it was standardized as AES. AES is identical to Rijndael, except that AES does not allow a block size other than 128, so don't change the block size. The only time Rijndael should be used is when you need a block size other than 128 bits for compatibility with an existing system.

Next, your initialization vector should be unique for CFB mode. An IV is not a secret, you can store it in plain text along with the cipher text. Don't derive it from a password. Crucially, an IV must never be re-used. It doesn't need to be random, either. It could be a simple incrementing counter. Note that for other modes of AES, like CBC, the IV should be random.

vcsjones
  • 138,677
  • 31
  • 291
  • 286
2

You don't need to read the entire file into memory. The best approach is to read a chunk of data at a time from the input and write that chunk to the output.

Something like the following, although I haven't tested the below:

byte[] keyBytes;
keyBytes = Encoding.Unicode.GetBytes(password);
Rfc2898DeriveBytes derivedKey = new Rfc2898DeriveBytes(password, keyBytes);
RijndaelManaged rijndaelCSP = new RijndaelManaged();
// setup key parameters, i.e. IV, etc
ICryptoTransform encryptor = rijndaelCSP.CreateEncryptor();
using (FileStream inputFileStream = new FileStream(filepath, FileMode.Open, FileAccess.Read))
using (FileStream outputFileStream = new FileStream(filepath + ".enc", FileMode.Create, FileAccess.Write))
using (CryptoStream encryptStream = new CryptoStream(outputFileStream, encryptor, CryptoStreamMode.Write))
{ 
  byte[] buffer = new byte[bufSize];
  int readSize = 0;
  while ((readSize = inputFileStream.Read(buffer, 0, buffer.Length)) > 0)
  {
      encryptStream.Write(buffer, 0, readSize);
  }
  encryptStream.FlushFinalBlock();
  rijndaelCSP.Clear();
}

UPDATE: Removed initialization code (copied from original question) as it isn't secure.

Mike Sackton
  • 1,094
  • 7
  • 19
  • I guess the initialization part is for demonstration purposes. This isn't really secure. You need to use a random salt for key derivation and a random IV. Both are not supposed to be secret, so they can be written in front of the ciphertext and used during decryption by reading them first before actually initiating the decryption. – Artjom B. Aug 15 '16 at 18:54
  • Yes, this isn't intended to show proper use of encryption, so much as to address the question as to how to avoid reading the entire block into memory. I've updated the answer to remove the initialization code to make that clear. – Mike Sackton Aug 15 '16 at 20:34