2013-01-05 50 views
3

我遇到了C#(VS2012,.NET 4.5)中文本的加密和解密问题。特别是,当我加密并随后解密一个字符串时,输出与输入不同。然而,奇怪的是,如果我复制加密输出并将其作为字符串文字进行硬编码,那么解密就可以工作。以下代码示例说明了此问题。我究竟做错了什么?为什么这个字符串与AesCryptoServiceProvider第二次解密时不相等?

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? 
+0

所以这个问题是重复使用。在深入讨论如何重置IV之前:这有多重要? –

+0

您不提供任何第一个问题的代码,不能回答。 –

+0

道歉 - 我不太明白你在说什么。代码示例是对更大的类中的问题的简化,它为字符串提供了简单的加密和解密方法。问题出现的原因是因为我正在使用BinaryFormatter将一些加密文本序列化到数据库中,并稍后再加载它,因此,当我解密时,调用加密文本的序列化结果不会生成相同的文本它并尝试使用它。希望澄清事情。 – wwarby

回答

6

这似乎是在.NET框架的实现AES的你与你的行引用ICryptoTransform的错误:

provider.CreateDecryptor(keyBytes, ivBytes); 

返回true为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既限制块大小为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:改为使用使用说明

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(); 
+1

非常感谢你 - 这个(以及后面的两个)是我需要的答案。我没有想到,一旦我创建了一个解密器,我只能使用它一次。看起来我在这里陷入了试图编写过于高效的代码的陷阱 - 在我简化了这个例子的真正的类中,解密器(和加密器)被作为静态字段存储在一个类中,这样我就不会重新创建他们。显然这不是一个好主意。我现在重构了这个类,一切正常。有时候我讨厌密码学 - 它太挑剔了! – wwarby

+0

我已经使用'使用';尝试AesManaged - 仍然是一个问题http://stackoverflow.com/questions/14937707/getting-incorrect-decryption-value-using-aescryptoserviceprovider – Lijo

+0

注意:但请注意,RijandaelManaged不符合FIPS。因此,如果您希望您的应用程序符合FIPS标准,则应该使用选项1。 – Akshay

2

即使您在两种情况下都使用相同的输入,问题是解密器.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)); 
+0

感谢您的输入!我有三个答案,所有这些答案都是大致正确的,所以我选择了第一个作为正确答案的答案,并且对其他答案进行了“提高” - 希望我的堆栈溢出礼节就在那里(我'对它来说相当新颖)。 – wwarby

1

我会假设,如在评论中提到,它的重复使用解密,这可能仍具有从第一解密的最后一块地方在其状态的问题,所以它不是从头开始,而且你会得到奇怪的结果。

我实际上不得不在前面写一个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)); 
    } 
} 
+0

感谢您的输入!我有三个答案,所有这些答案都大致正确,所以我选择了第一个答案作为正确答案 - 希望我的堆栈溢出礼节就在那里(我对它很陌生)。 – wwarby

相关问题