2011-09-14 30 views
3

我對看起來像.NET CryptoStream類的怪異感到困惑:它的Dispose()方法讀取密文末尾以尋找填充它不應該,並且因此拋出CryprographicException.NET CryptoStream在Dispose()中讀取密文的末尾並炸燬

下面的C#程序加密的幾個字節,調整大小密文陣列,使得有更多的(無義)密文的結束後的字節,然後嘗試解密它。要點如下:

  • 密文是8個字節,一個3DES密碼塊。由於我只寫入6個字節到CryptoStream,而它使用PaddingMode.PKCS7(默認值),所以塊中剩下的兩個字節填充了填充值0x02。
  • 密文數組隨後調整大小爲16個字節,即兩個3DES塊。第二個塊是未初始化的廢話,而不是有效的密碼輸出。
  • 解密時,我從CryptoStream剛剛讀取6個字節;我要求它解密成廢話部分,我不依靠它識別填充當它達到明文結束弄清楚。

的問題是,當的Dispose()解密CryptoStream被調用(在using塊結束時自動),得到了一個CryptographicException與消息‘壞數據’。它的堆棧跟蹤顯示它正在執行CryptoStream.FlushFinalBlock(),並且從ciphertextStream消耗了所有16個字節,而不僅僅是對應於實際加密數據的8個字節。

如果我刪除調整大小的ciphertext陣列線,該程序正常工作。如果我在解密之前做了tripleDes.Padding = PaddingMode.None,程序也能正常工作 - 但基本上使填充字節成爲明文的一部分,所以我寧願不這樣做。很明顯,問題在於填充相關;據我所知,它是解密的第二塊,並期待在它的末尾找到有效的PKCS7-樣式的填充。

由於我只從CryptoStream閱讀到需要一個塊進行解密,並且塊是一個正確的填充最終塊,然後我關閉CryptoStream不讀任何更多,爲什麼流認爲,需要讀取另一個塊並尋找更多的填充?爲什麼它甚至試圖消耗更多的輸入作爲其Dispose()的一部分?


using System; 
using System.IO; 
using System.Linq; 
using System.Security.Cryptography; 

namespace Test 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      byte[] plaintext = { 0, 1, 2, 3, 4 }; 

      using (SymmetricAlgorithm tripleDes = TripleDESCryptoServiceProvider.Create()) 
      { 
       // Encrypt the plaintext 
       byte[] ciphertext; 
       using (MemoryStream ciphertextStream = new MemoryStream()) 
       { 
        using (ICryptoTransform encryptor = tripleDes.CreateEncryptor()) 
        { 
         using (CryptoStream cryptoStream = new CryptoStream(ciphertextStream, encryptor, CryptoStreamMode.Write)) 
         { 
          cryptoStream.WriteByte((byte)plaintext.Length); 
          cryptoStream.Write(plaintext, 0, plaintext.Length); 
          cryptoStream.FlushFinalBlock(); 
         } 
        } 

        ciphertext = ciphertextStream.ToArray(); 
       } 

       // *** Add some non-ciphertext garbage to the end *** 
       Array.Resize(ref ciphertext, ciphertext.Length + 8); 

       // Now decrypt it again 
       byte[] decryptedPlaintext; 
       using (MemoryStream ciphertextStream = new MemoryStream(ciphertext, false)) 
       { 
        using (ICryptoTransform decryptor = tripleDes.CreateDecryptor()) 
        { 
         using (CryptoStream cryptoStream = new CryptoStream(ciphertextStream, decryptor, CryptoStreamMode.Read)) 
         { 
          int length = cryptoStream.ReadByte(); 

          decryptedPlaintext = new byte[length]; 

          int i = 0; 
          while (i < length) 
          { 
           int bytesRead = cryptoStream.Read(decryptedPlaintext, i, (length - i)); 
           if (bytesRead == 0) break; 
           else i += bytesRead; 
          } 
         } // CryptographicException: "Bad Data" 
        } 
       } 

       System.Diagnostics.Debug.Assert(decryptedPlaintext.SequenceEqual(plaintext)); 
      } 
     } 
    } 
} 

回答

3

你是故意將垃圾流的末尾,然後不知道爲什麼流的垃圾扼流圈。

在密碼學中必須非常仔細地檢查所有的以確保攻擊者不嘗試偷偷摸摸。如果你指定了PKCS7填充,那麼這個數據流是最後檢查PKCS7填充的權利,如果它沒有在數據流的末尾找到正確的填充,則拋出異常。

流無法知道實際的密文在流的中間結束,而不是結束的方式。你如何期望它知道?在加密中,規則是標記任何和所有的異常,並且在流的(明顯的)結尾有錯誤的填充是文檔會告訴你導致異常的東西。

+0

難道不應該知道基於這樣的事實,我讀一個塊後關閉流,而一個塊正確填充的最後一塊?我從來沒有要求它讀取垃圾,這令人窒息。有沒有辦法告訴它底層流的其餘部分不是密文,應該忽略? (其實我是想有一些未加密的數據在那裏,不是垃圾。) – Wyzard

+0

@Wyzard:它只知道的是,你沒看過你把它的一切。我建議你設置一個普通的流,同時保存密文和未加密的數據。讀取密文並通過CryptoStream管道。從普通流直接讀取未加密的數據。 – rossum

+0

問題不在於爲什麼它在垃圾上窒息,這就是爲什麼它首先讀*垃圾。請注意,垃圾是* not *塊填充的一部分,它是* past *第一個塊上的填充。我也有類似的情況 - 我的文件末尾沒有垃圾,但我有50MB的數據,我不希望被篡改到內存中 - 我只想在幾個塊中進行流式傳輸。但是當我關閉CypherStream時,它堅持讀我過去所要求的地方。 – Mud