良好的AES初始化vector实践
根据我的问题Aesencryption…缺less一个重要的一块 ,我现在已经了解到,我对string创build可逆encryption的假设是有点closures。 我现在有
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进行硬编码或者从密钥中派生出来。 我想知道这是否是一个好的做法,或者如果它会使encryption的值容易受到攻击……或者我是否真的在意这个,应该只是对IV进行硬编码?
更新每铱的build议,我尝试了这样的事情,而不是:
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); } } } } }
然而,这在我的unit testing中显示出一些奇怪的东西
[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”h,hpv0 我正在encryption。 有很多喜欢它,但这是我的encryption“,这似乎是正确的,但当然是完全错误的,看起来像我很接近,但我错过了内存stream的偏移?
IV应该是随机的,并且对于每一次encryption方法都是唯一的。 从密钥/消息中导出或硬编码是不够安全的。 IV可以在这个方法中生成,而不是传入它,并在encryption数据之前写入输出stream。
解密时,可以在encryption数据之前从input中读取IV。
当encryption时,生成你的IV并将其预先join到密文中(像这样)
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个字节,并在密码stream中使用它
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()); }
我修改你的解密方法如下,它的工作原理:
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
简化了这一点,因为您不必担心任何地方的偏移。
为了解决提供商的IV的设置(正如铱星所指出的那样):
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是从供应商随机产生的每一种encryption。
就我而言,为了生成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来计算密码哈希,并使用它来生成派生的字节,在这一点上,当涉及到初始化encryption/解密我只是使用这样的东西
Rfc2898DeriveBytes pdb = DerivePass(Password); SymmetricAlgorithm alg = _engine; alg.Key = pdb.GetBytes(_keySize); alg.IV = pdb.GetBytes(_IVSize);
我不知道这是不是“正确”(可能这里的密码大师会对我开枪:D),但是,至less,它给了我一个体面的IV,我不必将它存储在“某处”,因为刚刚进入正确的密码将返回所需的IV值; 作为一个说明,在上面的例子中的_engine被声明为“SymmetricAlgorithm”,并使用这样的东西初始化
_engine = Rijndael.Create(); _keySize = (_engine.KeySize / 8); _IVSize = (_engine.BlockSize / 8);
它创build所需的密码对象并初始化密钥和IV大小
为了生成随机的IV,你需要一个真正的随机数。 无论您用于生成随机数的语言特定的API,都应该生成真正的随机数。 android和ios都有apis,根据传感器数据生成随机数。
我最近使用随机IV(使用真正的随机数生成)和哈希密钥实现了AES 256。 为了更安全(随机IV +哈希键)跨平台(android,ios,c#)AES的实现请参阅我的答案在这里 – https://stackoverflow.com/a/24561148/2480840