良好的AES初始化矢量实践

Jer*_*acs 50 c# aescryptoserviceprovider initialization-vector

根据我的问题Aes Encryption ...错过了一个重要的部分,我现在已经了解到我在字符串上创建可逆加密的假设有点过时了.我现在有

    public static byte[] EncryptString(string toEncrypt, byte[] encryptionKey)
    {
        var toEncryptBytes = Encoding.UTF8.GetBytes(toEncrypt);
        using (var provider = new AesCryptoServiceProvider())
        {
            provider.Key = encryptionKey;
            provider.Mode = CipherMode.CBC;
            provider.Padding = PaddingMode.PKCS7;
            using (var encryptor = provider.CreateEncryptor(provider.Key, provider.IV))
            {
                using (var ms = new MemoryStream())
                {
                    using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                    {
                        cs.Write(toEncryptBytes, 0, toEncryptBytes.Length);
                        cs.FlushFinalBlock();
                    }
                    return ms.ToArray();
                }
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

这产生了一致的结果; 但是,如果不知道/设置初始化向量,我将无法解密.我真的不想将三个值传递给这个方法(在IV上),这让我硬编码IV或从密钥中导出它.我想知道这是不是一个好的做法,或者它是否会使加密的值容易受到某种程度的攻击......或者我是否真的过度思考这个并且应该硬编码IV?

更新 根据铱星的建议,我尝试了类似的东西:

    public static byte[] EncryptString(string toEncrypt, byte[] encryptionKey)
    {
        if (string.IsNullOrEmpty(toEncrypt)) throw new ArgumentException("toEncrypt");
        if (encryptionKey == null || encryptionKey.Length == 0) throw new ArgumentException("encryptionKey");
        var toEncryptBytes = Encoding.UTF8.GetBytes(toEncrypt);
        using (var provider = new AesCryptoServiceProvider())
        {
            provider.Key = encryptionKey;
            provider.Mode = CipherMode.CBC;
            provider.Padding = PaddingMode.PKCS7;
            using (var encryptor = provider.CreateEncryptor(provider.Key, provider.IV))
            {
                using (var ms = new MemoryStream())
                {
                    ms.Write(provider.IV, 0, 16);
                    using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                    {
                        cs.Write(toEncryptBytes, 0, toEncryptBytes.Length);
                        cs.FlushFinalBlock();
                    }
                    return ms.ToArray();
                }
            }
        }
    }

    public static string DecryptString(byte[] encryptedString, byte[] encryptionKey)
    {
        using (var provider = new AesCryptoServiceProvider())
        {
            provider.Key = encryptionKey;
            provider.Mode = CipherMode.CBC;
            provider.Padding = PaddingMode.PKCS7;
            using (var ms = new MemoryStream(encryptedString))
            {
                byte[] buffer;
                ms.Read(buffer, 0, 16);
                provider.IV = buffer;
                using (var decryptor = provider.CreateDecryptor(provider.Key, provider.IV))
                {
                    using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
                    {
                        byte[] decrypted = new byte[encryptedString.Length];
                        var byteCount = cs.Read(decrypted, 0, encryptedString.Length);
                        return Encoding.UTF8.GetString(decrypted, 0, byteCount);
                    }
                }
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

然而,这在我的单元测试中显示了一些奇怪的东西

    [TestMethod]
    public void EncryptionClosedLoopTest()
    {
        var roundtrip = "This is the data I am encrypting.  There are many like it but this is my encryption.";
        var encrypted = Encryption.EncryptString(roundtrip, encryptionKey);
        var decrypted = Encryption.DecryptString(encrypted, encryptionKey);
        Assert.IsTrue(roundtrip == decrypted);
    }
Run Code Online (Sandbox Code Playgroud)

我的解密文本显示为"92ʪ F" ,hpv0 我正在加密.有许多人喜欢它,但这是我的加密."这看起来几乎是正确但当然完全错误.看起来我很接近.我是否错过了内存流上的偏移量?

Iri*_*ium 51

对于每次加密方法的运行,IV应该是随机且唯一的.从密钥/消息中导出或硬编码它是不够安全的.IV可以在此方法中生成,而不是传递到其中,并在加密数据之前写入输出流.

在解密时,然后可以在加密数据之前从输入读取IV.

  • 密钥保护加密数据,而使用随机IV确保信息不会被密文本身泄露.IT通过防止相同的明文在使用相同密钥加密时生成相同的密文来实现此目的. (21认同)
  • 你能解释一下为什么吗?这对于维护加密和解密需要引用的 2 条任意数据(密钥和 IV)与 1 条(仅密钥)有何价值? (2认同)
  • @EricYin是的,您需要使用与加密相同的IV进行解密。不存在将IV与加密消息一起明文传输的风险。至于是否需要过多的工作,这取决于您,但是如果您不这样做的话,它的安全性就会降低。 (2认同)
  • @Iridium谢谢,我想我现在终于可以使用IV了.这个信息加上我昨晚读到的评论说IV与盐类似,所以主要是为了防止彩虹表使这一切都有意义. (2认同)

小智 14

加密时,生成您的IV并将其预先挂起到密文(类似这样)

        using (var aes= new AesCryptoServiceProvider()
        {
            Key = PrivateKey,
            Mode = CipherMode.CBC,
            Padding = PaddingMode.PKCS7
        })
        {

            var input = Encoding.UTF8.GetBytes(originalPayload);
            aes.GenerateIV();
            var iv = aes.IV;
            using (var encrypter = aes.CreateEncryptor(aes.Key, iv))
            using (var cipherStream = new MemoryStream())
            {
                using (var tCryptoStream = new CryptoStream(cipherStream, encrypter, CryptoStreamMode.Write))
                using (var tBinaryWriter = new BinaryWriter(tCryptoStream))
                {
                    //Prepend IV to data
                    //tBinaryWriter.Write(iv); This is the original broken code, it encrypts the iv
                    cipherStream.Write(iv);  //Write iv to the plain stream (not tested though)
                    tBinaryWriter.Write(input);
                    tCryptoStream.FlushFinalBlock();
                }

                string encryptedPayload = Convert.ToBase64String(cipherStream.ToArray());
            }

        }
Run Code Online (Sandbox Code Playgroud)

解密后,获取前16个字节并在加密流中使用它

var aes= new AesCryptoServiceProvider()
                    {
                        Key = PrivateKey,
                        Mode = CipherMode.CBC,
                        Padding = PaddingMode.PKCS7
                    };

                    //get first 16 bytes of IV and use it to decrypt
                    var iv = new byte[16];
                    Array.Copy(input, 0, iv, 0, iv.Length);

                    using (var ms = new MemoryStream())
                    {
                        using (var cs = new CryptoStream(ms, aes.CreateDecryptor(aes.Key, iv), CryptoStreamMode.Write))
                        using (var binaryWriter = new BinaryWriter(cs))
                        {
                            //Decrypt Cipher Text from Message
                            binaryWriter.Write(
                                input,
                                iv.Length,
                                input.Length - iv.Length
                            );
                        }

                        return Encoding.Default.GetString(ms.ToArray());
                    }
Run Code Online (Sandbox Code Playgroud)


dwi*_*iss 8

接受的答案是正确的,但没有提供如何获得随机 IV 的好例子。

事实证明,这比人们试图做到的要容易得多。每次构造 IV 时,.NET 中的 AesCryptoServiceProvider 都会自动生成一个加密随机 IV。如果需要使用同一个实例进行多次加密,可以调用GenerateIV()

您还可以在返回加密值之前将 IV 添加到加密值的前面,然后让解密端将其关闭

private static void Main(string[] args) {
    var rnd = new Random(); 
    var key = new byte[32];  // For this example, I'll use a random 32-byte key.
    rnd.NextBytes(key);
    var message = "This is a test";

    // Looping to encrypt the same thing twice just to show that the IV changes.
    for (var i = 0; i < 2; ++i) {
        var encrypted = EncryptString(message, key);
        Console.WriteLine(encrypted);
        Console.WriteLine(DecryptString(encrypted, key));
    }
}

public static string EncryptString(string message, byte[] key) {
    var aes = new AesCryptoServiceProvider();
    var iv = aes.IV;
    using (var memStream = new System.IO.MemoryStream()) {
        memStream.Write(iv, 0, iv.Length);  // Add the IV to the first 16 bytes of the encrypted value
        using (var cryptStream = new CryptoStream(memStream, aes.CreateEncryptor(key, aes.IV), CryptoStreamMode.Write)) {
            using (var writer = new System.IO.StreamWriter(cryptStream)) {
                writer.Write(message);
            }
        }
        var buf = memStream.ToArray();
        return Convert.ToBase64String(buf, 0, buf.Length);
    }
}

public static string DecryptString(string encryptedValue, byte[] key) {
    var bytes = Convert.FromBase64String(encryptedValue);
    var aes = new AesCryptoServiceProvider();
    using (var memStream = new System.IO.MemoryStream(bytes)) {
        var iv = new byte[16];
        memStream.Read(iv, 0, 16);  // Pull the IV from the first 16 bytes of the encrypted value
        using (var cryptStream = new CryptoStream(memStream, aes.CreateDecryptor(key, iv), CryptoStreamMode.Read)) {
            using (var reader = new System.IO.StreamReader(cryptStream)) {
                return reader.ReadToEnd();
            }
        }
    }  
}
Run Code Online (Sandbox Code Playgroud)

[编辑:我修改了我的答案,包括如何在加密值中传递 IV 并在解密时获取它。我还稍微重构了这个例子]


Rob*_*los 5

我修改你的解密方法如下,它的工作原理:

public static string DecryptString(byte[] encryptedString, byte[] encryptionKey)
{
    using (var provider = new AesCryptoServiceProvider())
    {
        provider.Key = encryptionKey;
        using (var ms = new MemoryStream(encryptedString))
        {
            // Read the first 16 bytes which is the IV.
            byte[] iv = new byte[16];
            ms.Read(iv, 0, 16);
            provider.IV = iv;

            using (var decryptor = provider.CreateDecryptor())
            {
                using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
                {
                    using (var sr = new StreamReader(cs))
                    {
                        return sr.ReadToEnd();
                    }
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

你的实现的问题是你正在读取太多的字节CryptoStream.你真的需要阅读encryptedText.Length - 16.使用StreamReader简化了这一点,因为您不再需要担心任何地方的偏移.


pau*_*guy 5

乡亲们的大力投入。我从ankurpatel和Konstantin那里得到了答案,并进行了清理,并添加了一些方便的方法替代。自2019年6月起在.NET Core 2.2中有效。

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

private const int AesKeySize = 16;

public static void Main()
{
    // the data to encrypt
    var message = "Here is some data to encrypt!";

    // create KeySize character key
    var key = "g(KMDu(EEw63.*V`";

    // encrypt the string to a string
    var encrypted = AesEncrypt(message, key);

    // decrypt the string to a string.
    var decrypted = AesDecrypt(encrypted, key);

    // display the original data and the decrypted data
    Console.WriteLine($"Original:   text: {encrypted}");
    Console.WriteLine($"Round Trip: text: {decrypted}");
}

static string AesEncrypt(string data, string key)
{
    return AesEncrypt(data, Encoding.UTF8.GetBytes(key));
}

static string AesDecrypt(string data, string key)
{
    return AesDecrypt(data, Encoding.UTF8.GetBytes(key));
}

static string AesEncrypt(string data, byte[] key)
{
    return Convert.ToBase64String(AesEncrypt(Encoding.UTF8.GetBytes(data), key));
}

static string AesDecrypt(string data, byte[] key)
{
    return Encoding.UTF8.GetString(AesDecrypt(Convert.FromBase64String(data), key));
}

static byte[] AesEncrypt(byte[] data, byte[] key)
{
    if (data == null || data.Length <= 0)
    {
        throw new ArgumentNullException($"{nameof(data)} cannot be empty");
    }

    if (key == null || key.Length != AesKeySize)
    {
        throw new ArgumentException($"{nameof(key)} must be length of {AesKeySize}");
    }

    using (var aes = new AesCryptoServiceProvider
    {
        Key = key,
        Mode = CipherMode.CBC,
        Padding = PaddingMode.PKCS7
    })
    {
        aes.GenerateIV();
        var iv = aes.IV;
        using (var encrypter = aes.CreateEncryptor(aes.Key, iv))
        using (var cipherStream = new MemoryStream())
        {
            using (var tCryptoStream = new CryptoStream(cipherStream, encrypter, CryptoStreamMode.Write))
            using (var tBinaryWriter = new BinaryWriter(tCryptoStream))
            {
                // prepend IV to data
                cipherStream.Write(iv);
                tBinaryWriter.Write(data);
                tCryptoStream.FlushFinalBlock();
            }
            var cipherBytes = cipherStream.ToArray();

            return cipherBytes;
        }
    }
}

static byte[] AesDecrypt(byte[] data, byte[] key)
{
    if (data == null || data.Length <= 0)
    {
        throw new ArgumentNullException($"{nameof(data)} cannot be empty");
    }

    if (key == null || key.Length != AesKeySize)
    {
        throw new ArgumentException($"{nameof(key)} must be length of {AesKeySize}");
    }

    using (var aes = new AesCryptoServiceProvider
    {
        Key = key,
        Mode = CipherMode.CBC,
        Padding = PaddingMode.PKCS7
    })
    {
        // get first KeySize bytes of IV and use it to decrypt
        var iv = new byte[AesKeySize];
        Array.Copy(data, 0, iv, 0, iv.Length);

        using (var ms = new MemoryStream())
        {
            using (var cs = new CryptoStream(ms, aes.CreateDecryptor(aes.Key, iv), CryptoStreamMode.Write))
            using (var binaryWriter = new BinaryWriter(cs))
            {
                // decrypt cipher text from data, starting just past the IV
                binaryWriter.Write(
                    data,
                    iv.Length,
                    data.Length - iv.Length
                );
            }

            var dataBytes = ms.ToArray();

            return dataBytes;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 很好,但是将 `Encoding.Default` 更改为 `Encoding.UTF8`,请参阅 https://learn.microsoft.com/en-us/dotnet/api/system.text.encoding.default#remarks 了解详细信息。 (4认同)
  • 你摇滚。为了使其在 net48 中为我工作,我必须使用缓冲区长度调用“cipherStream.Write”:“cipherStream.Write(iv, 0, iv.Length)”。我刚刚替换了一些旧代码,其中开发人员对 IV 进行了硬编码..有趣。 (2认同)