2013-01-05 99 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

相關問題