【问题标题】:AES encryption on large files大文件的 AES 加密
【发布时间】:2015-02-23 01:56:36
【问题描述】:

我需要加密和解密大文件 (~1GB)。 我尝试使用这个例子:http://www.codeproject.com/Articles/769741/Csharp-AES-bits-Encryption-Library-with-Salt 但我的问题是由于文件非常大,我得到了 outOfMemory 异常。 所以我需要用文件流替换内存流,我只是不知道该怎么做......

(添加我的代码:)

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();
            }

        }
    }

【问题讨论】:

标签: c# .net encryption aes


【解决方案1】:

对于仍在研究此问题并且不希望将输出输出到文件而希望输出到流的任何人 关键是确保调用 cryptoStream.FlushFinalBlock();否则解密会丢失最后几个字符。

    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;
    }

【讨论】:

  • IV必须是随机的,从key中获取是非法的
  • 这只是一个例子,在这种情况下,由第三方发送的文件已经在文件本身中编码了 IV 和密钥。是的,IV 应该是随机的。
【解决方案2】:

所以我创建了一个相当快且内存消耗低的版本:
我使用“临时缓冲区”和“使用随机盐并将其与密文一起存储”。
加密:

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();
        }
    }

解密:

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();
        }
    }

生成随机盐:

        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;
    }

【讨论】:

  • 为什么要迭代十次?你不信任RNGCryptoServiceProvider
  • 好吧,好问题。我不完全相信,当我将这段代码添加到程序中时,我想,它可能会提供一点额外的安全性并且花费很少的 CPU 时间和内存。
  • @Joe 解密方法末尾如何避免NULL NULL NULL。
  • @PabloGonzalez 您是否找到了如何删除解密文件末尾的 null null null ?我也有同样的问题,请问是什么原因造成的,谢谢?
  • @RobinLeblond,我认为空值是由于在加密过程中插入的填充造成的。
【解决方案3】:

最终,这是对我有用的代码:

 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();

    }
}

【讨论】:

  • 你应该一次读写超过1个字节,尝试使用字节数组作为临时缓冲区。
  • 除此之外,尝试使用随机盐并将其与密文一起存储。
  • 谢谢!我知道这已经很长时间了,但这对我帮助很大。
  • 当我使用相同的代码时,当我打开我的解密文件时,它显示文件已损坏,你确定你需要恢复文件吗?但是当我给恢复文件正确打开时。请为我的方案提出建议。
  • 这可能是 deruptor 的错误?有一个“createEncryptor”而不是 createDecryptor?
【解决方案4】:

由于您正在读取文件并写入文件,只需将内存流替换为 IOStream 或 FileStream。

您必须稍微重构这些过程,以免它们期望/返回字节数组。

【讨论】:

  • 嗨@DrKoch,感谢您的回答。我尝试了您的建议,但现在解密时出现“要解密的数据长度无效”异常。我已经编辑了我的问题并添加了我的加密和解密代码。知道我在做什么错吗?谢谢!
  • 好吧,您不应该使用字节数组(具有完整文件的长度)而是“流式传输”数据。这就是 Stream 的用途。
猜你喜欢
  • 2011-08-03
  • 1970-01-01
  • 1970-01-01
  • 2015-02-05
  • 1970-01-01
  • 2021-05-19
  • 2016-03-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多