加密/解密大文件(.NET)

本文关键字:NET 文件 解密 加密 | 更新日期: 2023-09-27 18:24:01

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

加密/解密大文件(.NET)

一个生物体的大就是另一个生物体小,尽管我们看到它时都知道它很贵。Wink,Wink。

尝试在您的环境中进行类似以下的基准测试,看看您所处的位置:

2012年2月13日编辑:代码已经更新,因为我变得(不知不觉地)更聪明了,还注意到了一些偷拍错误。真是罪有应得

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

这可能有助于

/// 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

通常,当数据在一台机器上(如服务器)加密,然后由另一台机器(客户端)解密时,会使用您所描述的策略。服务器将使用新生成的密钥使用对称密钥加密(为了提高性能)对数据进行加密,并使用公钥(与客户端的私钥匹配)对该对称密钥进行加密。服务器向客户端发送加密的数据和加密的对称密钥。客户端可以使用其私钥解密对称密钥,然后使用此对称密钥解密数据。如果您在同一台机器上对数据进行加密和解密,则同时使用RSA和AES可能没有意义,因为您不会试图将加密密钥传递给另一台机器。

正如您所听到的,非对称密码学,如RSA,比对称密码学(如AES)慢得多,但它确实有的优点(更简单的密钥管理,如要保护的单个私钥)。

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

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

这里有一个链接到我以前的(但仍然是真实的)博客文章,它给出了一个使用C#和.NET框架(微软的Mono)实现这一点的例子。

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