C# AES 和 RSA 文件加密 - 如何使用 IV?

Jos*_*hua 3 c# encryption cryptography rsa aes

我目前正在编写一个在以下场景下工作的程序:

  • 我有一些机密日志文件需要备份到服务器。
  • 我有一个程序每天都会生成这些日志文件。
  • 这些日志文件很少需要打开。
  • 我只有一对 RSA 公钥/私钥。
  • 该程序只有 RSA 公钥。
  • 每次程序创建这些机密文件之一时,我都会生成一个随机 AES 密钥。
  • 该程序使用此 AES 密钥来加密日志文件。
  • 然后我使用 RSA 公钥来加密 AES 密钥
  • 然后,我将 AES 加密文件和 RSA 加密 AES 密钥备份到服务器。

据我了解,该协议适合我的用例。

我遇到的问题是用 C# 编码。我遇到了需要初始化向量(IV)来进行 AES 加密的情况,我尝试通过在两者上使用公共 RSA 密钥来将其与 AES 密钥一起加密。但 512(2 * 256) 的大小大于 RSA 愿意加密的大小。所以我发现,既然我每次都像 AES 密钥一样随机创建初始化向量,我可以将 IV 添加到 AES 密文的前面。但是,我不确定执行此操作的代码将插入到我的函数中的何处

任何对“协议”正确方向的帮助或将 IV 写入密文的其他方法都会很棒。先感谢您。

static public Tuple<byte[], byte[]> EncryptAES(byte[] toEncryptAES, RSAParameters RSAPublicKey)
    {
        byte[] encryptedAES = null;
        byte[] encryptedRSA = null;

        using (MemoryStream ms = new MemoryStream())
        {
            using (RijndaelManaged AES = new RijndaelManaged())
            {
                AES.KeySize = 256;
                AES.BlockSize = 128;
                AES.Mode = CipherMode.CBC;
                AES.GenerateIV();
                AES.GenerateKey();
                encryptedRSA = RSAEncrypt(AES.Key, RSAPublicKey);

                using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    ms.Write(AES.IV, 0, AES.KeySize); //DOESNT WORK HERE
                    //Can't use CS to write it to the stream else it will encrypt along with file
                    cs.Write(toEncryptAES, 0, toEncryptAES.Length);
                    cs.Close();
                }
                encryptedAES = ms.ToArray();
            }
        }
        return new Tuple<byte[], byte[]>(encryptedAES, encryptedRSA);
    }
    static public byte[] DecryptAES(byte[] toDecryptAES, byte[] AESKeyAndIV, RSAParameters RSAPrivateKey)
    {
        byte[] AESKey = RSADecrypt(AESKeyAndIV, RSAPrivateKey);

        using (MemoryStream ms = new MemoryStream())
        {
            using (RijndaelManaged AES = new RijndaelManaged())
            {
                AES.KeySize = 256;
                AES.BlockSize = 128;
                AES.Key = AESKey;
                ms.Read(AES.IV, 0, AES.KeySize); //Not sure if can read MS here
                AES.Mode = CipherMode.CBC;

                using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
                {
                    //Would I need to move 0 to 256?
                    cs.Write(toDecryptAES, 0, toDecryptAES.Length);
                    cs.Close();
                }
                return ms.ToArray();
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

Sco*_*ain 5

你已经很接近了,在创建 CryptoStream 之前写出 IV

static public Tuple<byte[], byte[]> EncryptAES(byte[] toEncryptAES, RSAParameters RSAPublicKey)
{
    byte[] encryptedAES = null;
    byte[] encryptedRSA = null;

    using (MemoryStream ms = new MemoryStream())
    {
        using (RijndaelManaged AES = new RijndaelManaged())
        {
            AES.KeySize = 256;
            AES.BlockSize = 128;
            AES.Mode = CipherMode.CBC;
            AES.GenerateIV();
            AES.GenerateKey();
            encryptedRSA = RSAEncrypt(AES.Key, RSAPublicKey);

            ms.Write(AES.IV, 0, AES.KeySize); //Move the write here.

            using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
            {
                cs.Write(toEncryptAES, 0, toEncryptAES.Length);
                cs.Close();
            }
            encryptedAES = ms.ToArray();
        }
    }
    return new Tuple<byte[], byte[]>(encryptedAES, encryptedRSA);
}
Run Code Online (Sandbox Code Playgroud)

对于解密,请确保循环读取直到完全读取 IV 的 byte[],但Stream.Read不能保证读取您要求其读取的所有字节。我通常使用静态方法ReadFully来确保读取所有字节。

private static byte[] ReadFully(Stream stream, int length)
{
    int offset = 0;
    byte[] buffer = new byte[length];
    while(offset < length)
    {
        offset += stream.Read(buffer, offset, length - offset);
    }
    return buffer;
}
Run Code Online (Sandbox Code Playgroud)

然后使用该方法读取 IV 即可。您还希望cs.Readcs.Write读出加密数据并将流置于读取模式,但是仅使用.CopyTo数据并将其复制到新的 MemoryStream 会更容易。

static public byte[] DecryptAES(byte[] toDecryptAES, byte[] AESKeyAndIV, RSAParameters RSAPrivateKey)
{
    byte[] AESKey = RSADecrypt(AESKeyAndIV, RSAPrivateKey);

    using (MemoryStream source = new MemoryStream(toDecryptAES))
    {
        using (RijndaelManaged AES = new RijndaelManaged())
        {
            AES.KeySize = 256;
            AES.BlockSize = 128;
            AES.Key = AESKey;
            var iv = ReadFully(source, AES.KeySize);
            AES.IV = iv;
            AES.Mode = CipherMode.CBC;

            using (var cs = new CryptoStream(source, AES.CreateDecryptor(), CryptoStreamMode.Read))
            {
                using(var dest = new MemoryStream())
                {
                    cs.CopyTo(dest);
                    return dest.ToArray();
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

对于其他读者,请注意RSAEncryptRSADecrypt是对RSACryptoServiceProvider.