c# - 为什么这个字符串用AesCryptoServiceProvider第二次解密时不相等?

标签 c# c#-4.0 encryption character-encoding aes

我在使用 C#(VS2012、.NET 4.5)加密和解密文本时遇到问题。具体来说,当我加密并随后解密一个字符串时,输出与输入不同。然而,奇怪的是,如果我复制加密输出并将其硬编码为字符串文字,解密就可以进行。下面的代码示例说明了这个问题。我做错了什么?

var key = new Rfc2898DeriveBytes("test password", Encoding.Unicode.GetBytes("test salt"));
var provider = new AesCryptoServiceProvider { Padding = PaddingMode.PKCS7, KeySize = 256 };
var keyBytes = key.GetBytes(provider.KeySize >> 3);
var ivBytes = key.GetBytes(provider.BlockSize >> 3);
var encryptor = provider.CreateEncryptor(keyBytes, ivBytes);
var decryptor = provider.CreateDecryptor(keyBytes, ivBytes);

var testStringBytes = Encoding.Unicode.GetBytes("test string");
var testStringEncrypted = Convert.ToBase64String(encryptor.TransformFinalBlock(testStringBytes, 0, testStringBytes.Length));

//Prove that the encryption has resulted in the following string
Debug.WriteLine(testStringEncrypted == "cc1zurZinx4yxeSB0XDzVziEUNJlFXsLzD2p9TWnxEc="); //Result: True

//Decrypt the encrypted text from a hardcoded string literal
var encryptedBytes = Convert.FromBase64String("cc1zurZinx4yxeSB0XDzVziEUNJlFXsLzD2p9TWnxEc=");
var testStringDecrypted = Encoding.Unicode.GetString(decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length));

//Decrypt the encrypted text from the string result of the encryption process
var encryptedBytes2 = Convert.FromBase64String(testStringEncrypted);
var testStringDecrypted2 = Encoding.Unicode.GetString(decryptor.TransformFinalBlock(encryptedBytes2, 0, encryptedBytes2.Length));

//encryptedBytes and encryptedBytes2 should be identical, so they should result in the same decrypted text - but they don't: 
Debug.WriteLine(testStringDecrypted == "test string"); //Result: True
Debug.WriteLine(testStringDecrypted2 == "test string"); //Result: FALSE
//testStringDecrypted2 is now "૱﷜ୱᵪ㭈盐æing". Curiously, the last three letters are the same.
//WTF?

最佳答案

这似乎是 AES 的 .NET 框架实现中的错误,您在行中引用了 ICryptoTransform:

provider.CreateDecryptor(keyBytes, ivBytes);

对于 CanReuseTransform 返回真但是它似乎在解密后没有清除输入缓冲区。有一些解决方案可以让它发挥作用。

选项 1 创建第二个解密器并用它解密第二个字符串。

var key = new Rfc2898DeriveBytes("test password", Encoding.Unicode.GetBytes("test salt"));
var provider = new AesCryptoServiceProvider { Padding = PaddingMode.PKCS7, KeySize = 256 };
var keyBytes = key.GetBytes(provider.KeySize >> 3);
var ivBytes = key.GetBytes(provider.BlockSize >> 3);
var encryptor = provider.CreateEncryptor(keyBytes, ivBytes);
var decryptor = provider.CreateDecryptor(keyBytes, ivBytes);
var decryptor2 = provider.CreateDecryptor(keyBytes, ivBytes);

var testStringBytes = Encoding.Unicode.GetBytes("test string");
var testStringEncrypted = Convert.ToBase64String(encryptor.TransformFinalBlock(testStringBytes, 0, testStringBytes.Length));

//Prove that the encryption has resulted in the following string
Console.WriteLine(testStringEncrypted == "cc1zurZinx4yxeSB0XDzVziEUNJlFXsLzD2p9TWnxEc="); //Result: True

//Decrypt the encrypted text from a hardcoded string literal
var encryptedBytes = Convert.FromBase64String("cc1zurZinx4yxeSB0XDzVziEUNJlFXsLzD2p9TWnxEc=");

var testStringDecrypted = Encoding.Unicode.GetString(decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length));

//Decrypt the encrypted text from the string result of the encryption process
var encryptedBytes2 = Convert.FromBase64String(testStringEncrypted);

var testStringDecrypted2 = Encoding.Unicode.GetString(decryptor2.TransformFinalBlock(encryptedBytes2, 0, encryptedBytes2.Length));

//encryptedBytes and encryptedBytes2 should be identical, so they should result in the same decrypted text - but they don't: 
Console.WriteLine(testStringDecrypted == "test string"); //Result: True
Console.WriteLine(testStringDecrypted2 == "test string"); //Result: True

Console.Read();

选项 2 使用 RijandaelManaged(或 AesManaged)而不是 AesCryptoServiceProvider,它应该是相同的算法(尽管 AesCryptoServiceProvider 和 AesManaged 都将 block 大小限制为 128)

var key = new Rfc2898DeriveBytes("test password", Encoding.Unicode.GetBytes("test salt"));
var provider = new RijndaelManaged { Padding = PaddingMode.PKCS7, KeySize = 256 };
var keyBytes = key.GetBytes(provider.KeySize >> 3);
var ivBytes = key.GetBytes(provider.BlockSize >> 3);
var encryptor = provider.CreateEncryptor(keyBytes, ivBytes);
var decryptor = provider.CreateDecryptor(keyBytes, ivBytes);

var testStringBytes = Encoding.Unicode.GetBytes("test string");
var testStringEncrypted = Convert.ToBase64String(encryptor.TransformFinalBlock(testStringBytes, 0, testStringBytes.Length));

//Prove that the encryption has resulted in the following string
Console.WriteLine(testStringEncrypted == "cc1zurZinx4yxeSB0XDzVziEUNJlFXsLzD2p9TWnxEc="); //Result: True

//Decrypt the encrypted text from a hardcoded string literal
var encryptedBytes = Convert.FromBase64String("cc1zurZinx4yxeSB0XDzVziEUNJlFXsLzD2p9TWnxEc=");

var testStringDecrypted = Encoding.Unicode.GetString(decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length));

//Decrypt the encrypted text from the string result of the encryption process
var encryptedBytes2 = Convert.FromBase64String(testStringEncrypted);

var testStringDecrypted2 = Encoding.Unicode.GetString(decryptor.TransformFinalBlock(encryptedBytes2, 0, encryptedBytes2.Length));

//encryptedBytes and encryptedBytes2 should be identical, so they should result in the same decrypted text - but they don't: 
Console.WriteLine(testStringDecrypted == "test string"); //Result: True
Console.WriteLine(testStringDecrypted2 == "test string"); //Result: True

Console.Read();

选项 3:改用 using 语句

var key = new Rfc2898DeriveBytes("test password", Encoding.Unicode.GetBytes("test salt"));
var provider = new AesCryptoServiceProvider { Padding = PaddingMode.PKCS7, KeySize = 256 };
var keyBytes = key.GetBytes(provider.KeySize >> 3);
var ivBytes = key.GetBytes(provider.BlockSize >> 3);
var encryptor = provider.CreateEncryptor(keyBytes, ivBytes);

var testStringBytes = Encoding.Unicode.GetBytes("test string");
var testStringEncrypted = Convert.ToBase64String(encryptor.TransformFinalBlock(testStringBytes, 0, testStringBytes.Length));

//Prove that the encryption has resulted in the following string
Console.WriteLine(testStringEncrypted == "cc1zurZinx4yxeSB0XDzVziEUNJlFXsLzD2p9TWnxEc="); //Result: True

//Decrypt the encrypted text from a hardcoded string literal
var encryptedBytes = Convert.FromBase64String("cc1zurZinx4yxeSB0XDzVziEUNJlFXsLzD2p9TWnxEc=");

string testStringDecrypted, testStringDecrypted2;

using (var decryptor = provider.CreateDecryptor(keyBytes, ivBytes))
{
    testStringDecrypted =
        Encoding.Unicode.GetString(decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length));
}

//Decrypt the encrypted text from the string result of the encryption process
var encryptedBytes2 = Convert.FromBase64String(testStringEncrypted);

using (var decryptor = provider.CreateDecryptor(keyBytes, ivBytes))
{
    testStringDecrypted2 =
        Encoding.Unicode.GetString(decryptor.TransformFinalBlock(encryptedBytes2, 0, encryptedBytes2.Length));
}

//encryptedBytes and encryptedBytes2 should be identical, so they should result in the same decrypted text - but they don't: 
Console.WriteLine(testStringDecrypted == "test string"); //Result: True
Console.WriteLine(testStringDecrypted2 == "test string"); //Result: True

Console.Read();

关于c# - 为什么这个字符串用AesCryptoServiceProvider第二次解密时不相等?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14175272/

相关文章:

java - 获取错误 java.lang.ArrayIndexOutOfBoundsException : too much data for RSA block

c# - ForEach 在运行时给出无效的转换

c# - 如何管理应用程序关闭的方式?

encryption - 在提交时加密添加到 Mercurial 存储库的文件

c# - 获取属性类型以传递给泛型方法

C# 动态 - "RuntimeBinderException"

c++ - 从文件末尾检索字符串时失败

c# - 像 Stack Overflow 一样格式化数字(用 K 后缀四舍五入到千位)

c# - 缺少 Nuget.targets

c# GetManifestResourceNames() 什么都不返回