良好的AES初始化矢量实践

时间:2011-11-07 19:24:27

标签: c# initialization-vector aescryptoserviceprovider

根据我的问题Aes Encryption... missing an important piece,我现在已经了解到我在字符串上创建可逆加密的假设有点过时了。我现在有

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

这会产生一致的结果;但是,如果不知道/设置初始化向量,我将无法解密。我真的不想将三个值传递给这个方法(在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);
                    }
                }
            }
        }
    }

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

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

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

8 个答案:

答案 0 :(得分:44)

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

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

答案 1 :(得分:11)

加密时,生成您的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());
            }

        }

解密后,获取前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());
                    }

答案 2 :(得分: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();
                    }
                }
            }
        }
    }
}

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

答案 3 :(得分:2)

为了解决提供者的IV设置(如Iridium指出的那样):

ms.Read(provider.IV, 0, 16);

我在您的代码中添加了以下内容:

var iv = new byte[provider.IV.Length];
memoryStream.Read(iv, 0, provider.IV.Length);
using (var decryptor = provider.CreateDecryptor(key, iv);

已授予,每次运行时,提供程序都不会设置我的密钥。我生成了一次然后存储它。对于每次加密,IV都是从提供者处随机生成的。

答案 4 :(得分:1)

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

事实证明,这比人们试图做到的要容易 很多 。 NET中的AesCryptoServiceProvider每次构造一个IV时都会自动生成一个加密随机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();
            }
        }
    }  
}

[编辑:我修改了答案,包括如何在加密值中传递IV并在解密时获取它。我还对示例进行了重构]

答案 5 :(得分:1)

乡亲们的宝贵意见。我从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.Default.GetBytes(key));
}

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

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

static string AesDecrypt(string data, byte[] key)
{
    return Encoding.Default.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;
        }
    }
}

答案 6 :(得分:-1)

在我的情况下,为了生成IV,我使用类似这样的东西

    /// <summary>
    /// Derives password bytes
    /// </summary>
    /// <param name="Password">password</param>
    /// <returns>derived bytes</returns>
    private Rfc2898DeriveBytes DerivePass(string Password)
    {
        byte[] hash = CalcHash(Password);
        Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(Password, hash, _KEY_ITER);
        return pdb;
    }

    /// <summary>
    /// calculates the hash of the given string
    /// </summary>
    /// <param name="buffer">string to hash</param>
    /// <returns>hash value (byte array)</returns>
    private byte[] CalcHash(string buffer)
    {
        RIPEMD160 hasher = RIPEMD160.Create();
        byte[] data = Encoding.UTF8.GetBytes(buffer);
        return hasher.ComputeHash(data);
    }

也就是说,我使用RIPEMD160计算密码哈希并使用它来生成派生字节,此时,当初始化加密/解密时我只是使用这样的东西

        Rfc2898DeriveBytes pdb = DerivePass(Password);
        SymmetricAlgorithm alg = _engine;
        alg.Key = pdb.GetBytes(_keySize);
        alg.IV = pdb.GetBytes(_IVSize);

我不知道它是否“正确”(可能这里的加密大师会向我开枪:D),但是,至少,它给了我一个体面的IV,我不必将它存储在“某处”只需输入正确的密码即可返回所需的IV值;作为注释,上面示例中的_engine被声明为“SymmetricAlgorithm”并使用类似的东西进行初始化

_engine = Rijndael.Create();
_keySize = (_engine.KeySize / 8);
_IVSize = (_engine.BlockSize / 8);

创建所需的加密对象并初始化键和IV大小

答案 7 :(得分:-1)

要生成随机IV,您需要一个真正随机的数字。无论您使用哪种语言特定的API来生成随机数,都应该生成真正的随机数。 android和ios都有api,可根据传感器数据生成随机数。

我最近使用随机IV(使用真正的随机数生成)和哈希密钥实现了AES 256。对于更安全(随机IV +哈希键)跨平台(android,ios,c#)的AES实现,请参阅我的答案 - https://stackoverflow.com/a/24561148/2480840