21

I need to encrypt and decrypt large file (~1GB). I tried using this example: http://www.codeproject.com/Articles/769741/Csharp-AES-bits-Encryption-Library-with-Salt But my problem is since the file is very large, I'm getting outOfMemory exception. So I need to replace the memory stream with file stream, I just not sure how to do it...

(Adding my code:)

private static void AES_Encrypt(string srcFile, string encryptedFile,  byte[] passwordBytes)
    {


        // Set your salt here, change it to meet your flavor:
        // The salt bytes must be at least 8 bytes.
        byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8};

        FileStream fsInput = new FileStream(srcFile,
            FileMode.Open,
            FileAccess.Read);

        FileStream fsEncrypted = new FileStream(encryptedFile,
                        FileMode.Create,
                        FileAccess.Write);

        using (RijndaelManaged AES = new RijndaelManaged())
        {
            AES.KeySize = 256;
            AES.BlockSize = 128;

            var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
            AES.Key = key.GetBytes(AES.KeySize / 8);
            AES.IV = key.GetBytes(AES.BlockSize / 8);

            AES.Mode = CipherMode.CBC;

            using (var cs = new CryptoStream(fsEncrypted, AES.CreateEncryptor(), CryptoStreamMode.Write))
            {
                byte[] bytearrayinput = new byte[fsInput.Length - 1];
                fsInput.Read(bytearrayinput, 0, bytearrayinput.Length);
                cs.Write(bytearrayinput, 0, bytearrayinput.Length);
                cs.Close();
                fsInput.Flush();
                fsInput.Close();
                fsEncrypted.Close();
            }

        }


    }

    public static void AES_Decrypt(string encryptedFile, string decryptedFile, byte[] passwordBytes)
    {
        byte[] decryptedBytes = null;

        // Set your salt here, change it to meet your flavor:
        // The salt bytes must be at least 8 bytes.
        byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8};

        FileStream fsread = new FileStream(encryptedFile,
                               FileMode.Open,
                               FileAccess.Read);

        using (RijndaelManaged AES = new RijndaelManaged())
        {
            AES.KeySize = 256;
            AES.BlockSize = 128;

            var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
            AES.Key = key.GetBytes(AES.KeySize / 8);
            AES.IV = key.GetBytes(AES.BlockSize / 8);

            AES.Mode = CipherMode.CBC;

            FileStream fsDecrypted = new FileStream(decryptedFile,
                        FileMode.Create,
                        FileAccess.Write);

            using (var cs = new CryptoStream(fsDecrypted, AES.CreateDecryptor(), CryptoStreamMode.Write))
            {
                byte[] bytearrayinput = new byte[fsread.Length - 1];
                fsread.Read(bytearrayinput, 0, bytearrayinput.Length);
                cs.Write(bytearrayinput, 0, bytearrayinput.Length);
                cs.Close();
                fsread.Close();
                fsDecrypted.Close();
            }

        }
    }
leppie
  • 115,091
  • 17
  • 196
  • 297
Sharas
  • 1,985
  • 3
  • 20
  • 43
  • I have edited your title. Please see, "[Should questions include “tags” in their titles?](http://meta.stackexchange.com/questions/19190/)", where the consensus is "no, they should not". – John Saunders Dec 25 '14 at 23:29
  • DO NOT EXTRACT THE IV FROM THE KEY. This breaks AES! – mafu Nov 15 '21 at 20:12

4 Answers4

12

Eventually, this is the code that worked for me:

 private static void AES_Encrypt(string inputFile, string outputFile, byte[] passwordBytes)
 {
        byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8};
        string cryptFile = outputFile;
        FileStream fsCrypt = new FileStream(cryptFile, FileMode.Create);

        RijndaelManaged AES = new RijndaelManaged();

        AES.KeySize = 256;
        AES.BlockSize = 128;


        var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
        AES.Key = key.GetBytes(AES.KeySize / 8);
        AES.IV = key.GetBytes(AES.BlockSize / 8);
        AES.Padding = PaddingMode.Zeros;

        AES.Mode = CipherMode.CBC;

        CryptoStream cs = new CryptoStream(fsCrypt,
             AES.CreateEncryptor(),
            CryptoStreamMode.Write);

        FileStream fsIn = new FileStream(inputFile, FileMode.Open);

        int data;
        while ((data = fsIn.ReadByte()) != -1)
            cs.WriteByte((byte)data);


        fsIn.Close();
        cs.Close();
        fsCrypt.Close();

    }

    private static void AES_Decrypt(string inputFile, string outputFile, byte[] passwordBytes)
    {



        byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8};
        FileStream fsCrypt = new FileStream(inputFile, FileMode.Open);

        RijndaelManaged AES = new RijndaelManaged();

        AES.KeySize = 256;
        AES.BlockSize = 128;


        var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
        AES.Key = key.GetBytes(AES.KeySize / 8);
        AES.IV = key.GetBytes(AES.BlockSize / 8);
        AES.Padding = PaddingMode.Zeros;

        AES.Mode = CipherMode.CBC;

        CryptoStream cs = new CryptoStream(fsCrypt,
            AES.CreateDecryptor(),
            CryptoStreamMode.Read);

        FileStream fsOut = new FileStream(outputFile, FileMode.Create);

        int data;
        while ((data = cs.ReadByte()) != -1)
            fsOut.WriteByte((byte)data);

        fsOut.Close();
        cs.Close();
        fsCrypt.Close();

    }
}
Sharas
  • 1,985
  • 3
  • 20
  • 43
10

So I created a fairly fast and low memory consumption version:
I use a "temporary buffer" and also "use a random salt and store it with the ciphertext".
To encrypt:

private void AES_Encrypt(string inputFile, string password)
    {
        //http://stackoverflow.com/questions/27645527/aes-encryption-on-large-files

        //generate random salt
        byte[] salt = GenerateRandomSalt();

        //create output file name
        FileStream fsCrypt = new FileStream(inputFile + ".aes", FileMode.Create);

        //convert password string to byte arrray
        byte[] passwordBytes = System.Text.Encoding.UTF8.GetBytes(password);

        //Set Rijndael symmetric encryption algorithm
        RijndaelManaged AES = new RijndaelManaged();
        AES.KeySize = 256;
        AES.BlockSize = 128;
        AES.Padding = PaddingMode.PKCS7;

        //http://stackoverflow.com/questions/2659214/why-do-i-need-to-use-the-rfc2898derivebytes-class-in-net-instead-of-directly
        //"What it does is repeatedly hash the user password along with the salt." High iteration counts.
        var key = new Rfc2898DeriveBytes(passwordBytes, salt, 50000);
        AES.Key = key.GetBytes(AES.KeySize / 8);
        AES.IV = key.GetBytes(AES.BlockSize / 8);

        //Cipher modes: http://security.stackexchange.com/questions/52665/which-is-the-best-cipher-mode-and-padding-mode-for-aes-encryption
        AES.Mode = CipherMode.CFB;

        //write salt to the begining of the output file, so in this case can be random every time
        fsCrypt.Write(salt, 0, salt.Length);

        CryptoStream cs = new CryptoStream(fsCrypt, AES.CreateEncryptor(), CryptoStreamMode.Write);

        FileStream fsIn = new FileStream(inputFile, FileMode.Open);

        //create a buffer (1mb) so only this amount will allocate in the memory and not the whole file
        byte[] buffer = new byte[1048576];
        int read;

        try
        {
            while ((read = fsIn.Read(buffer, 0, buffer.Length)) > 0)
            {
                Application.DoEvents(); // -> for responsive GUI, using Task will be better!
                cs.Write(buffer, 0, read);
            }

            //close up
            fsIn.Close();

        }
        catch (Exception ex)
        {
            Debug.WriteLine("Error: " + ex.Message);
        }
        finally
        {
            cs.Close();
            fsCrypt.Close();
        }
    }

To decrypt:

private void AES_Decrypt(string inputFile, string password)
    {
        //todo:
        // - create error message on wrong password
        // - on cancel: close and delete file
        // - on wrong password: close and delete file!
        // - create a better filen name
        // - could be check md5 hash on the files but it make this slow

        byte[] passwordBytes = System.Text.Encoding.UTF8.GetBytes(password);
        byte[] salt = new byte[32];

        FileStream fsCrypt = new FileStream(inputFile, FileMode.Open);
        fsCrypt.Read(salt, 0, salt.Length);

        RijndaelManaged AES = new RijndaelManaged();
        AES.KeySize = 256;
        AES.BlockSize = 128;
        var key = new Rfc2898DeriveBytes(passwordBytes, salt, 50000);
        AES.Key = key.GetBytes(AES.KeySize / 8);
        AES.IV = key.GetBytes(AES.BlockSize / 8);
        AES.Padding = PaddingMode.PKCS7;
        AES.Mode = CipherMode.CFB;

        CryptoStream cs = new CryptoStream(fsCrypt, AES.CreateDecryptor(), CryptoStreamMode.Read);

        FileStream fsOut = new FileStream(inputFile + ".decrypted", FileMode.Create);

        int read;
        byte[] buffer = new byte[1048576];

        try
        {
            while ((read = cs.Read(buffer, 0, buffer.Length)) > 0)
            {
                Application.DoEvents();
                fsOut.Write(buffer, 0, read);
            }
        }
        catch (System.Security.Cryptography.CryptographicException ex_CryptographicException)
        {
            Debug.WriteLine("CryptographicException error: " + ex_CryptographicException.Message);
        }
        catch (Exception ex)
        {
            Debug.WriteLine("Error: " + ex.Message);
        }

        try
        {
            cs.Close();
        }
        catch (Exception ex)
        {
            Debug.WriteLine("Error by closing CryptoStream: " + ex.Message);
        }
        finally
        {
            fsOut.Close();
            fsCrypt.Close();
        }
    }

To generate random salt:

        public static byte[] GenerateRandomSalt()
    {
        //Source: http://www.dotnetperls.com/rngcryptoserviceprovider
        byte[] data = new byte[32];

        using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
        {
            // Ten iterations.
            for (int i = 0; i < 10; i++)
            {
                // Fill buffer.
                rng.GetBytes(data);
            }
        }
        return data;
    }
Joe
  • 129
  • 3
  • 6
  • 3
    Why ten iterations? You don't trust the `RNGCryptoServiceProvider`? – hdev Jul 28 '16 at 11:07
  • Well, good question. I do not trust completely, when I add this code to the program, I thought, it may provide a little extra security and cost little CPU time and memory. – Joe Sep 01 '16 at 15:10
  • @Joe How do I avoid the NULL NULL NULL at the end of the decryption method. – Pablo Gonzalez Oct 08 '18 at 03:53
  • @PabloGonzalez did you find how to remove the null null null at the end of the decryted file ? I do have the same issue, any one what's causing this, thank ? – Robin Leblond Jun 05 '20 at 13:12
  • @RobinLeblond, I believe the nulls are being caused because of padding that was inserted during the encryption process. – Xiver Jul 16 '20 at 20:18
  • Do not get the IV from the key. This breaks AES. And leave that nonsense with the RNG iterations... – mafu Nov 15 '21 at 20:14
4

Since you are reading from a file and writing to a file just replace the memory streams by IOStream or FileStream.

You'll have to refactor the procedures a bit so they don't expect/return byte arrays.

DrKoch
  • 9,556
  • 2
  • 34
  • 43
  • Hi @DrKoch, Thanks for your answer. I tried your suggestion, but now I'm getting "Length of the data to decrypt is invalid" exception when decrypting. I've edited my question and added my encrypt and decrypt code. any idea what am i doing wrong? Thanks! – Sharas Dec 25 '14 at 20:22
  • WEll, you should not use byte arrays (with length of the complete file) istead 'stream' the data. That's what a Stream is for. – DrKoch Dec 26 '14 at 10:55
1

For anyone still looking into this and don't want the output to a file but rather a stream the key is to make sure to call cryptoStream.FlushFinalBlock(); otherwise the decrypt will miss the last few characters.

    public MemoryStream FileEncrypt(string inputFilePath, byte[] passwordBytes)
    {
        var saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };

        var memoryStream = new MemoryStream();
        var aes = new RijndaelManaged {KeySize = 256, BlockSize = 128};

        var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
        aes.Key = key.GetBytes(aes.KeySize / 8);
        aes.IV = key.GetBytes(aes.BlockSize / 8);
        aes.Padding = PaddingMode.Zeros;
        aes.Mode = CipherMode.CBC;

        var cryptoStream = new CryptoStream(memoryStream,  aes.CreateEncryptor(),  CryptoStreamMode.Write);
        var fileStream = new FileStream(inputFilePath, FileMode.Open);

        int data;
        while ((data = fileStream.ReadByte()) != -1)
            cryptoStream.WriteByte((byte)data);

        cryptoStream.FlushFinalBlock();

        return memoryStream;
    }

    public MemoryStream FileDecrypt(Stream encryptedFileStream, byte[] passwordBytes)
    {
        var saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };

        var AES = new RijndaelManaged {KeySize = 256, BlockSize = 128};
        var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
        AES.Key = key.GetBytes(AES.KeySize / 8);
        AES.IV = key.GetBytes(AES.BlockSize / 8);
        AES.Padding = PaddingMode.Zeros;
        AES.Mode = CipherMode.CBC;

        var cryptoStream = new CryptoStream(encryptedFileStream, AES.CreateDecryptor(), CryptoStreamMode.Read);
        var memoryStream = new MemoryStream();

        int data;
        while ((data = cryptoStream.ReadByte()) != -1)
            memoryStream.WriteByte((byte)data);

        return memoryStream;
    }
Ricky Gummadi
  • 4,559
  • 2
  • 41
  • 67
  • The IV must be random, getting it from the key is illegal – mafu Nov 15 '21 at 20:14
  • This is just an example, in this instance, the file being sent by a third party has coded the IV and key in the file itself. Yes, the IV should be random. – Ricky Gummadi Nov 16 '21 at 07:45