为什么这个字符串在使用AesCryptoServiceProvider第二次解密时不相等?
本文关键字:解密 第二次 不相等 AesCryptoServiceProvider 字符串 为什么 | 更新日期: 2023-09-27 17:50:44
我在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?
这似乎是您引用的ICryptoTransform的。net框架实现中的一个错误:
provider.CreateDecryptor(keyBytes, ivBytes);
为CanReuseTransform返回true,但是在解密后似乎没有清除输入缓冲区。有一些解决方案可以让它工作。
选项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都将块大小限制为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();
即使在两种情况下使用相同的输入,问题是decryptor.TransformFinalBlock()的行为在第一次调用之后发生了变化。值是字符串字面值还是变量都没有区别。这个页面似乎暗示解密器在第一次使用后将自己"重置"到某种初始状态:
http://www.pcreview.co.uk/forums/icryptotransform-transformfinalblock-behavior-bug-t1233029.html似乎你可以通过重新调用provider.CreateDecryptor(keyBytes, ivBytes)
来为你想要做的每个解密获得一个新的解密器来解决这个问题:
//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));
decryptor = provider.CreateDecryptor(keyBytes, ivBytes);
//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));
我认为,正如在评论中提到的,这是一个重用解密器的问题,它可能仍然在其状态的某个地方有第一次解密的最后一个块,所以它不是从头开始,并且您得到奇怪的结果。
前几天我实际上必须编写一个AES字符串加密/解密器,我在这里包含了它,以及单元测试(需要Xunit)。
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Xunit;
public interface IStringEncryptor {
string EncryptString(string plainText);
string DecryptString(string encryptedText);
}
public class AESStringEncryptor : IStringEncryptor {
private readonly Encoding _encoding;
private readonly byte[] _key;
private readonly Rfc2898DeriveBytes _passwordDeriveBytes;
private readonly byte[] _salt;
/// <summary>
/// Overload of full constructor that uses UTF8Encoding as the default encoding.
/// </summary>
/// <param name="key"></param>
/// <param name="salt"></param>
public AESStringEncryptor(string key, string salt)
: this(key, salt, new UTF8Encoding()) {
}
public AESStringEncryptor(string key, string salt, Encoding encoding) {
_encoding = encoding;
_passwordDeriveBytes = new Rfc2898DeriveBytes(key, _encoding.GetBytes(salt));
_key = _passwordDeriveBytes.GetBytes(32);
_salt = _passwordDeriveBytes.GetBytes(16);
}
/// <summary>
/// Encrypts any string to a Base64 string
/// </summary>
/// <param name="plainText"></param>
/// <exception cref="ArgumentNullException">String to encrypt cannot be null or empty.</exception>
/// <returns>A Base64 string representing the encrypted version of the plainText</returns>
public string EncryptString(string plainText) {
if (string.IsNullOrEmpty(plainText)) {
throw new ArgumentNullException("plainText");
}
using (var alg = new RijndaelManaged { BlockSize = 128, FeedbackSize = 128, Key = _key, IV = _salt })
using (var ms = new MemoryStream())
using (var cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write)) {
var plainTextBytes = _encoding.GetBytes(plainText);
cs.Write(plainTextBytes, 0, plainTextBytes.Length);
cs.FlushFinalBlock();
return Convert.ToBase64String(ms.ToArray());
}
}
/// <summary>
/// Decrypts a Base64 string to the original plainText in the given Encoding
/// </summary>
/// <param name="encryptedText">A Base64 string representing the encrypted version of the plainText</param>
/// <exception cref="ArgumentNullException">String to decrypt cannot be null or empty.</exception>
/// <exception cref="CryptographicException">Thrown if password, salt, or encoding is different from original encryption.</exception>
/// <returns>A string encoded</returns>
public string DecryptString(string encryptedText) {
if (string.IsNullOrEmpty(encryptedText)) {
throw new ArgumentNullException("encryptedText");
}
using (var alg = new RijndaelManaged { BlockSize = 128, FeedbackSize = 128, Key = _key, IV = _salt })
using (var ms = new MemoryStream())
using (var cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write)) {
var encryptedTextBytes = Convert.FromBase64String(encryptedText);
cs.Write(encryptedTextBytes, 0, encryptedTextBytes.Length);
cs.FlushFinalBlock();
return _encoding.GetString(ms.ToArray());
}
}
}
public class AESStringEncryptorTest {
private const string Password = "TestPassword";
private const string Salt = "TestSalt";
private const string Plaintext = "This is a test";
[Fact]
public void EncryptionAndDecryptionWorkCorrectly() {
var aesStringEncryptor = new AESStringEncryptor(Password, Salt);
string encryptedText = aesStringEncryptor.EncryptString(Plaintext);
Assert.NotEqual(Plaintext, encryptedText);
var aesStringDecryptor = new AESStringEncryptor(Password, Salt);
string decryptedText = aesStringDecryptor.DecryptString(encryptedText);
Assert.Equal(Plaintext, decryptedText);
}
[Fact]
public void EncodingsWorkWhenSame()
{
var aesStringEncryptor = new AESStringEncryptor(Password, Salt, Encoding.ASCII);
string encryptedText = aesStringEncryptor.EncryptString(Plaintext);
Assert.NotEqual(Plaintext, encryptedText);
var aesStringDecryptor = new AESStringEncryptor(Password, Salt, Encoding.ASCII);
string decryptedText = aesStringDecryptor.DecryptString(encryptedText);
Assert.Equal(Plaintext, decryptedText);
}
[Fact]
public void EncodingsFailWhenDifferent() {
var aesStringEncryptor = new AESStringEncryptor(Password, Salt, Encoding.UTF32);
string encryptedText = aesStringEncryptor.EncryptString(Plaintext);
Assert.NotEqual(Plaintext, encryptedText);
var aesStringDecryptor = new AESStringEncryptor(Password, Salt, Encoding.UTF8);
Assert.Throws<CryptographicException>(() => aesStringDecryptor.DecryptString(encryptedText));
}
[Fact]
public void EncryptionAndDecryptionWithWrongPasswordFails()
{
var aes = new AESStringEncryptor(Password, Salt);
string encryptedText = aes.EncryptString(Plaintext);
Assert.NotEqual(Plaintext, encryptedText);
var badAes = new AESStringEncryptor(Password.ToLowerInvariant(), Salt);
Assert.Throws<CryptographicException>(() => badAes.DecryptString(encryptedText));
}
[Fact]
public void EncryptionAndDecryptionWithWrongSaltFails()
{
var aes = new AESStringEncryptor(Password, Salt);
string encryptedText = aes.EncryptString(Plaintext);
Assert.NotEqual(Plaintext, encryptedText);
var badAes = new AESStringEncryptor(Password, Salt.ToLowerInvariant());
Assert.Throws<CryptographicException>(() => badAes.DecryptString(encryptedText));
}
}