【问题标题】:Encrypting/Decrypting large files (.NET)加密/解密大文件 (.NET)
【发布时间】:2012-03-03 11:39:20
【问题描述】:

我必须加密、存储然后解密大文件。最好的方法是什么?我听说 RSA 加密很昂贵,建议使用 RSA 加密 AES 密钥,然后使用 AES 密钥加密大文件。任何带有示例的建议都会很棒。

【问题讨论】:

标签: c# .net encryption cryptography rsa


【解决方案1】:

一个有机体很大,另一个有机体很小,尽管我们都知道它很贵。眨眼,眨眼。

尝试在您的环境中对以下内容进行基准测试,然后查看您所处的位置:

2012 年 2 月 13 日编辑:代码已更新,因为我变得(不知不觉地)变得更聪明了,并且还注意到一些悄悄出现的剪切粘贴错误。Mea culpa。

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

...

    // Rfc2898DeriveBytes constants:
    public readonly byte[] salt = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // Must be at least eight bytes.  MAKE THIS SALTIER!
    public const int iterations = 1042; // Recommendation is >= 1000.

    /// <summary>Decrypt a file.</summary>
    /// <remarks>NB: "Padding is invalid and cannot be removed." is the Universal CryptoServices error.  Make sure the password, salt and iterations are correct before getting nervous.</remarks>
    /// <param name="sourceFilename">The full path and name of the file to be decrypted.</param>
    /// <param name="destinationFilename">The full path and name of the file to be output.</param>
    /// <param name="password">The password for the decryption.</param>
    /// <param name="salt">The salt to be applied to the password.</param>
    /// <param name="iterations">The number of iterations Rfc2898DeriveBytes should use before generating the key and initialization vector for the decryption.</param>
    public void DecryptFile(string sourceFilename, string destinationFilename, string password, byte[] salt, int iterations)
    {
        AesManaged aes = new AesManaged();
        aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
        aes.KeySize = aes.LegalKeySizes[0].MaxSize;
        // NB: Rfc2898DeriveBytes initialization and subsequent calls to   GetBytes   must be eactly the same, including order, on both the encryption and decryption sides.
        Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, salt, iterations);
        aes.Key = key.GetBytes(aes.KeySize / 8);
        aes.IV = key.GetBytes(aes.BlockSize / 8);
        aes.Mode = CipherMode.CBC;
        ICryptoTransform transform = aes.CreateDecryptor(aes.Key, aes.IV);

        using (FileStream destination = new FileStream(destinationFilename, FileMode.CreateNew, FileAccess.Write, FileShare.None))
        {
            using (CryptoStream cryptoStream = new CryptoStream(destination, transform, CryptoStreamMode.Write))
            {
                try
                {
                    using (FileStream source = new FileStream(sourceFilename, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        source.CopyTo(cryptoStream);
                    }
                }
                catch (CryptographicException exception)
                {
                    if (exception.Message == "Padding is invalid and cannot be removed.")
                        throw new ApplicationException("Universal Microsoft Cryptographic Exception (Not to be believed!)", exception);
                    else
                        throw;
                }
            }
        }
    }

    /// <summary>Encrypt a file.</summary>
    /// <param name="sourceFilename">The full path and name of the file to be encrypted.</param>
    /// <param name="destinationFilename">The full path and name of the file to be output.</param>
    /// <param name="password">The password for the encryption.</param>
    /// <param name="salt">The salt to be applied to the password.</param>
    /// <param name="iterations">The number of iterations Rfc2898DeriveBytes should use before generating the key and initialization vector for the decryption.</param>
    public void EncryptFile(string sourceFilename, string destinationFilename, string password, byte[] salt, int iterations)
    {
        AesManaged aes = new AesManaged();
        aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
        aes.KeySize = aes.LegalKeySizes[0].MaxSize;
        // NB: Rfc2898DeriveBytes initialization and subsequent calls to   GetBytes   must be eactly the same, including order, on both the encryption and decryption sides.
        Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, salt, iterations);
        aes.Key = key.GetBytes(aes.KeySize / 8);
        aes.IV = key.GetBytes(aes.BlockSize / 8);
        aes.Mode = CipherMode.CBC;
        ICryptoTransform transform = aes.CreateEncryptor(aes.Key, aes.IV);

        using (FileStream destination = new FileStream(destinationFilename, FileMode.CreateNew, FileAccess.Write, FileShare.None))
        {
            using (CryptoStream cryptoStream = new CryptoStream(destination, transform, CryptoStreamMode.Write))
            {
                using (FileStream source = new FileStream(sourceFilename, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    source.CopyTo(cryptoStream);
                }
            }
        }
    }

【讨论】:

  • 此示例仅使用 AES 加密/解密。您的意思是说尝试使用 RSA 进行类似的操作并同时配置文件?
  • 这取决于你。您的问题没有解释对数据的感知威胁、文件的大小、涉及的任何网络传输或暴露、可用的处理资源……。在这种情况下,您最好至少收集一些有关您环境中加密成本的基准数据并从那里开始工作。其他人则谈到了公钥与私钥密码学以及不同算法的相对强度。
  • 避免“填充无效,无法移除”。例外,加密方必须在source.CopyTo(crytoStream); 之后调用source.FlushFinalBlock(),否则你实际上会错过最后一个块。
  • 注意:IV 不应该从密码派生。 stackoverflow.com/a/8041580/887092。也许“绝不能”。
【解决方案2】:

这可能会有所帮助

/// Encrypts a file using Rijndael algorithm.
///</summary>
///<param name="inputFile"></param>
///<param name="outputFile"></param>
private void EncryptFile(string inputFile, string outputFile)
{

    try
    {
        string password = @"myKey123"; // Your Key Here
        UnicodeEncoding UE = new UnicodeEncoding();
        byte[] key = UE.GetBytes(password);

        string cryptFile = outputFile;
        FileStream fsCrypt = new FileStream(cryptFile, FileMode.Create);

        RijndaelManaged RMCrypto = new RijndaelManaged();

        CryptoStream cs = new CryptoStream(fsCrypt,
            RMCrypto.CreateEncryptor(key, key),
            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();
    }
    catch
    {
        MessageBox.Show("Encryption failed!", "Error");
    }
}

///
/// Decrypts a file using Rijndael algorithm.
///</summary>
///<param name="inputFile"></param>
///<param name="outputFile"></param>
private void DecryptFile(string inputFile, string outputFile)
{

    {
        string password = @"myKey123"; // Your Key Here

        UnicodeEncoding UE = new UnicodeEncoding();
        byte[] key = UE.GetBytes(password);

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

        RijndaelManaged RMCrypto = new RijndaelManaged();

        CryptoStream cs = new CryptoStream(fsCrypt,
            RMCrypto.CreateDecryptor(key, key),
            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();

    }
}

来源: http://www.codeproject.com/Articles/26085/File-Encryption-and-Decryption-in-C

【讨论】:

  • 这段代码有一些严重的问题:a) 如果密码没有足够的长度,它就不起作用,但问题是密码通常是低熵的,因此不能直接用作关键。 b) 使用密钥作为 IV 使得这种确定性加密,因此在语义上不安全。 IV 必须是随机生成的。它不必是秘密的,而只是不可预测的,因此可以简单地写在密文前面,并在解密时使用。 c) 没有认证!密文可能会在收件人未检测到的情况下更改。
【解决方案3】:

通常,当数据将在一台机器(如服务器)上加密,然后由另一台机器(客户端)解密时,会使用您描述的策略。服务器将使用新生成的密钥使用对称密钥加密(用于性能)加密数据,并使用公钥(匹配客户端的私钥)加密此对称密钥。服务器向客户端发送加密数据和加密对称密钥。客户端可以用它的私钥解密对称密钥,然后使用这个对称密钥来解密数据。 如果您在同一台机器上加密和解密数据,那么同时使用 RSA 和 AES 可能没有意义,因为您不会尝试将加密密钥传递给另一台机器。

【讨论】:

  • 谢谢,但我的问题略有不同。我想加密大文件然后存储,如果存储受到损害,则需要加密(即安全性)。对于我发送使用 RSA 加密的 AES 密钥的客户端服务器通信,它在我的场景中如何转换?我是否将 AES 密钥存储为对称密钥加密 blob 的前置或附加字节的一部分?
  • 一个很好的例子实际上是在 X509Certificate2 MSDN Doc 上找到的(我正在寻找 RSACryptoProvider 和相关类):
【解决方案4】:

就像您听说的那样,RSA 等非对称加密比对称加密(例如 AES)慢得多,但它确实具有它的优势(更简单的密钥管理,例如要保护的单个私钥)。

关键(双关语)是利用两者的优点(非对称的私钥和对称的速度),而忽略另一个的不便(许多秘密密钥和慢速)。

您可以通过对每个文件使用一次 RSA(不会对性能产生巨大影响)来加密用于加密(更快)您的大文件的(对称)密钥来做到这一点。对称密钥的这种 *包装 允许您仅管理单个私钥。

这是我的旧(但仍然正确)blog post 的链接,它提供了使用 C# 和 .NET 框架(Mono 的微软)执行此操作的示例。

【讨论】:

  • 谢谢。这个例子很有帮助。
【解决方案5】:

RSA

真正的非对称加密(RSA、ECC 等)比对称加密(AES、ChaCha20 等)慢。 RSA 和其他方法非常适合保护随机对称密钥(或建立一个)。 AES 和其他方法非常适合与完整性检查 (HMAC) 一起使用的高效加密。

重要的是,成熟的对称密码没有任何已知的理论弱点。除非您的攻击者拥有对称密钥,否则无法破解加密。目前,所有成熟的非对称密码学(RSA、ECC)都基于容易被未来的量子计算机(如果有的话)破解的数学属性。

此外,处理公钥/私钥也成为一个问题。人类记住密码很简单——他们的大脑不会被黑客入侵。使用公钥/私钥,它们需要存储在某个地方。特别是私钥是敏感的。计算机具有 TDM 组件,可以创建和存储与 CPU 分开的公钥/私钥。这使用起来非常复杂。

因此,考虑到这一点,只有在绝对必要时才应使用 RSA。

AES

这是我最近写的一个完整版本,它返回包装流媒体,所以你可以根据需要使用它。

此外,此方法从随机生成器而不是密码消化器生成 IV。这是最佳实践,例如 7z 就是这样做的 - 请参阅 https://crypto.stackexchange.com/questions/61945/is-it-ok-to-transmit-an-iv-as-a-custom-http-header。 IV 包含在输出的标头中。

用法:

void Save()
{
    var encryptedFilePath = Directory.GetCurrentDirectory() + "\\data.bin.aes";
    using(var fileStream = File.Create(encryptedFilePath))
    {
        using (var cryptoStream = Security.FileEncryptor.CreateEncryptor(fileStream, passwordHere))
        {
            var formatter = new BinaryFormatter();
            formatter.Serialize(cryptoStream, myObject);
            cryptoStream.Flush();
        }

    }
}

void Load()
{
    var encryptedFilePath = Directory.GetCurrentDirectory() + "\\data.bin.aes";

    using(var fileStream = File.Open(encryptedFilePath, FileMode.Open))
    {
        using (var cryptoStream = Security.FileEncryptor.CreateDecryptor(fileStream, passwordHere))
        {
            var formatter = new BinaryFormatter();
            var myObject = (myObjectType)formatter.Deserialize(cryptoStream);
        }
    }
}

实用程序:

using System.IO;
using System.Security.Cryptography;
using System;

namespace Security
{

    class FileEncryptor
    {
        public static Stream CreateEncryptor(Stream source, string password)
        {
            byte[] SaltBytes = new byte[16];
            RandomNumberGenerator.Fill(SaltBytes); //RandomNumberGenerator is used for .Net Core 3

            AesManaged aes = new AesManaged();
            aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
            aes.KeySize = aes.LegalKeySizes[0].MaxSize;

            Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, SaltBytes, iterations);
            aes.Key = key.GetBytes(aes.KeySize / 8);

            byte[] IVBytes = new byte[aes.BlockSize / 8];
            RandomNumberGenerator.Fill(IVBytes); //RandomNumberGenerator is used for .Net Core 3
            aes.IV = IVBytes;

            aes.Mode = CipherMode.CBC;
            ICryptoTransform transform = aes.CreateEncryptor(aes.Key, aes.IV);

            //Store/Send the Salt and IV - this can be shared. It's more important that it's very random, than being private.
            source.WriteByte((byte)SaltBytes.Length);
            source.Write(SaltBytes, 0, SaltBytes.Length);
            source.WriteByte((byte)IVBytes.Length);
            source.Write(IVBytes, 0, IVBytes.Length);
            source.Flush();

            var cryptoStream = new CryptoStream(source, transform, CryptoStreamMode.Write);
            return cryptoStream;
        }

        public static Stream CreateDecryptor(Stream source, string password)
        {
            var ArrayLength = source.ReadByte();
            if (ArrayLength == -1) throw new Exception("Salt length not found");
            byte[] SaltBytes = new byte[ArrayLength];
            var readBytes = source.Read(SaltBytes, 0, ArrayLength);
            if (readBytes != ArrayLength) throw new Exception("No support for multiple reads");

            ArrayLength = source.ReadByte();
            if (ArrayLength == -1) throw new Exception("Salt length not found");
            byte[] IVBytes = new byte[ArrayLength];
            readBytes = source.Read(IVBytes, 0, ArrayLength);
            if (readBytes != ArrayLength) throw new Exception("No support for multiple reads");

            AesManaged aes = new AesManaged();
            aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
            aes.KeySize = aes.LegalKeySizes[0].MaxSize;
            aes.IV = IVBytes;

            Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, SaltBytes, iterations);
            aes.Key = key.GetBytes(aes.KeySize / 8);

            aes.Mode = CipherMode.CBC;
            ICryptoTransform transform = aes.CreateDecryptor(aes.Key, aes.IV);

            var cryptoStream = new CryptoStream(source, transform, CryptoStreamMode.Read);
            return cryptoStream;
        }

        public const int iterations = 1042; // Recommendation is >= 1000.
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-02
    相关资源
    最近更新 更多