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

3
我正在编写一个程序,其工作方式如下:
  • 我有一些机密的日志文件需要备份到服务器。
  • 我有一个程序每天生成这些日志文件。
  • 这些日志文件极少需要被打开。
  • 我只有一对RSA公钥/私钥。
  • 该程序仅拥有RSA公钥。
  • 每次程序制作机密文件时,我都会生成一个随机的AES密钥。
  • 程序使用此AES密钥加密日志文件。
  • 然后我使用RSA公钥加密AES密钥。
  • 然后将AES加密文件和RSA加密AES密钥两者备份到服务器上。
据我了解,该协议适用于我的用例。
问题是我在用C#编写它时遇到了麻烦。我需要为我的AES加密使用初始化向量(IV),我尝试使用公共RSA密钥对其进行加密,但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();
            }
        }
    }

2
但是512(2*256)的大小比RSA加密所能处理的更大。这表明两件事情不好:(1)你的IV长度为256位,这不应该是这种情况,因为你将块大小设置为128位,这也是IV的大小。(2)这表明你正在使用少于768位的RSA密钥。今天你至少应该使用2048位的RSA密钥。 - Artjom B.
啊,你说得对。我以为IV等于密钥大小,而不是块大小。当然应该是块大小。我会解决这个问题的。还有RSA密钥,RSA不喜欢加密小于其密钥大小的内容,对吧?我记得我做了一个4096位的RSA密钥,所以我会去检查并验证它在使用之前。谢谢你的帮助,两个部分都非常重要。 - Joshua
1个回答

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

在解密过程中,确保你循环读取直到完全读取了 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;
}

然后只需使用该方法来读取IV。您还需要使用cs.Read而不是cs.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();
                }
            }
        }
    }
}

对于其他读者,请注意RSAEncryptRSADecrypt是对RSACryptoServiceProvider调用的包装器。


哇,那是我所期望的最好答案。非常感谢!你对读取流的写入模式的捕捉很好,这是个愚蠢的错误。 - Joshua
“RSAEncrypt”和“RSADecrypt”方法在哪里? - GooliveR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接