Dot-Net

為什麼 BCL GZipStream(帶有 StreamReader)不能可靠地檢測 CRC32 的數據錯誤?

  • May 7, 2013

前幾天我遇到了一個問題GZipStream 沒有檢測到損壞的數據(甚至 CRC32 通過)?(這很可能是一個“重複”,我對這個問題有復雜的感覺。我也是在標題中添加 CRC32 的人,但回想起來,這與文章的其餘部分格格不入)。在我自己探索了一下這個問題之後,我認為這個問題遠遠大於其他問題最初描述的問題。

我擴展了另一個問題並使測試程式碼可在 LINQPad 下執行,並嘗試更好地展示CRC32(循環冗餘檢查)問題(如果確實存在)。(由於程式碼只是基於原始程式碼的輕微修改,因此測試設置/方法可能存在缺陷,或者兩者都有另一個奇怪的怪癖/PEBCAK。)

結果很奇怪,因為**損壞的數據並不總是導致(任何!)引發異常。請注意,只有有時CRC32 檢查似乎實際上是“工作”。可以忽略導致 index-out-of-range/bad header/bad footer 的損壞字節,因為我們可以假設這些字節會在 CRC32 檢查*之前殺死解壓縮(這是完全可以理解*的,即使 IndexOutOfRangeException 應該可能被包裝由 InvalidDataException) 所以,

為什麼 CRC32 檢查的可靠性明顯低於應有的水平?(為什麼下面會出現“Invalid data (No Exception)”呢?)

由於GZip 頁腳包含 CRC32未壓縮數據的長度,似乎錯誤檢測率應該“顯著更高” ——也就是說,我希望下面出現一個失敗案例,更不用說許多未檢測到的損壞流. (當然,盡快檢測到損壞的蒸汽是件好事:但在某些情況下,最終的保護校驗和似乎完全被忽略了。)

格式為CorruptByteIndex+FailedDetections: Message

0+0:System.IO.InvalidDataException:GZip 標頭中的幻數不正確。確保您傳入的是 GZip 流。
1+0:System.IO.InvalidDataException:GZip 標頭中的幻數不正確。確保您傳入的是 GZip 流。
2+0: System.IO.InvalidDataException: GZip header 中指定的壓縮模式未知。
3+0:良好的數據(無例外)
4+0:良好的數據(無例外)
5+0:良好的數據(無例外)
6+0:良好的數據(無例外)
7+0:良好的數據(無例外)
8+0:良好的數據(無例外)
9+0:良好的數據(無例外)
10+0:System.IO.InvalidDataException:未知的塊類型。流可能已損壞。
11+1:無效數據(無異常)
12+1:System.IO.InvalidDataException:解碼時發現無效數據。
13+1:System.IO.InvalidDataException:解碼時發現無效數據。
14+1:System.IO.InvalidDataException:解碼時發現無效數據。
15+1:System.IO.InvalidDataException:解碼時發現無效數據。
16+1:System.IO.InvalidDataException:解碼時發現無效數據。
17+2:無效數據(無例外)
18+2:System.IO.InvalidDataException:解碼時發現無效數據。
19+2:System.IndexOutOfRangeException:Index 超出了數組的範圍。
20+2:System.IndexOutOfRangeException:Index 超出了數組的範圍。
21+3:無效數據(無例外)
22+3:System.IndexOutOfRangeException:Index 超出了數組的範圍。
23+3:System.IndexOutOfRangeException:Index 超出了數組的範圍。
24+4:無效數據(無例外)
25+4:System.IndexOutOfRangeException:Index 超出了數組的範圍。
26+4:System.IndexOutOfRangeException:Index 超出了數組的範圍。
27+4:System.IndexOutOfRangeException:Index 超出了數組的範圍。
28+4:System.IndexOutOfRangeException:Index 超出了數組的範圍。
29+5:無效數據(無例外)
30+5:System.IndexOutOfRangeException:Index 超出了數組的範圍。
31+6:無效數據(無例外)
32+7:無效數據(無例外)
33+7:System.IndexOutOfRangeException:Index 超出了數組的範圍。
34+7:System.IndexOutOfRangeException:Index 超出了數組的範圍。
35+7:System.IndexOutOfRangeException:Index 超出了數組的範圍。
36+8:無效數據(無例外)
37+8:System.IndexOutOfRangeException:Index 超出了數組的範圍。
38+8:System.IndexOutOfRangeException:Index 超出了數組的範圍。
39+9:無效數據(無例外)
40+9:System.IndexOutOfRangeException:Index 超出了數組的範圍。
41+9:System.IndexOutOfRangeException:Index 超出了數組的範圍。
42+10:無效數據(無例外)
43+10:System.IO.InvalidDataException:解碼時發現無效數據。
44+10:System.IndexOutOfRangeException:Index 超出了數組的範圍。
45+10:System.IO.InvalidDataException:解碼時發現無效數據。
46+11:無效數據(無例外)
47+11:System.IndexOutOfRangeException:Index 超出了數組的範圍。
48+11:System.IndexOutOfRangeException:Index 超出了數組的範圍。
49+11:System.IndexOutOfRangeException:Index 超出了數組的範圍。
50+12:無效數據(無例外)
51+12:System.IndexOutOfRangeException:Index 超出了數組的範圍。
52+12:System.IndexOutOfRangeException:Index 超出了數組的範圍。
53+13:無效數據(無異常)
54+13:System.IndexOutOfRangeException:Index 超出了數組的範圍。
55+14:無效數據(無例外)
56+14:System.IndexOutOfRangeException:Index 超出了數組的範圍。
57+15:無效數據(無例外)
58+15:System.IndexOutOfRangeException:Index 超出了數組的範圍。
59+15:System.IndexOutOfRangeException:Index 超出了數組的範圍。
60+16:無效數據(無例外)
61+17:無效數據(無例外)
62+18:無效數據(無例外)
63+19:無效數據(無例外)
64+19:System.IndexOutOfRangeException:Index 超出了數組的範圍。
65+19:System.IndexOutOfRangeException:Index 超出了數組的範圍。
66+19:System.IndexOutOfRangeException:Index 超出了數組的範圍。
67+19:System.IndexOutOfRangeException:Index 超出了數組的範圍。
68+19:System.IndexOutOfRangeException:Index 超出了數組的範圍。
69+19:System.IndexOutOfRangeException:Index 超出了數組的範圍。
70+19:System.IO.InvalidDataException:解碼時發現無效數據。
71+19:System.IndexOutOfRangeException:Index 超出了數組的範圍。
72+19:System.IndexOutOfRangeException:Index 超出了數組的範圍。
73+19:System.IndexOutOfRangeException:Index 超出了數組的範圍。
74+19:System.IndexOutOfRangeException:Index 超出了數組的範圍。
75+19:System.IO.InvalidDataException:解碼時發現無效數據。
76+19:System.IndexOutOfRangeException:Index 超出了數組的範圍。
77+19:System.IndexOutOfRangeException:Index 超出了數組的範圍。
78+19:System.IndexOutOfRangeException:Index 超出了數組的範圍。
79+19:System.IndexOutOfRangeException:Index 超出了數組的範圍。
80+19:System.IndexOutOfRangeException:Index 超出了數組的範圍。
81+19:System.IO.InvalidDataException:解碼時發現無效數據。
82+19:System.IndexOutOfRangeException:Index 超出了數組的範圍。
83+20:無效數據(無例外)
84+21:無效數據(無異常)
85+22:無效數據(無例外)
86+22:System.IndexOutOfRangeException:Index 超出了數組的範圍。
87+23:無效數據(無例外)
88+24:無效數據(無例外)
89+25:無效數據(無例外)
90+25:System.IndexOutOfRangeException:Index 超出了數組的範圍。
91+26:無效數據(無例外)
92+26:System.IO.InvalidDataException:解碼時發現無效數據。
93+26:System.IndexOutOfRangeException:Index 超出了數組的範圍。
94+27:無效數據(無例外)
95+27:System.IndexOutOfRangeException:Index 超出了數組的範圍。
96+27:System.IndexOutOfRangeException:Index 超出了數組的範圍。
97+28:無效數據(無例外)
98+28:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
99+28:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
100+29:無效數據(無例外)
101+30:無效數據(無例外)
102+31:無效數據(無異常)
103+32:無效數據(無異常)
104+32:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
105+33:無效數據(無異常)
106+34:無效數據(無異常)
107+35:無效數據(無異常)
108+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
109+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
110+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
111+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
112+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
113+35: System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
114+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
115+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
116+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
117+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
118+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
119+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
120+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
121+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
122+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
123+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
124+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
125+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
126+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
127+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與根據解壓縮數據計算的 CRC 不匹配。
128+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
129+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與根據解壓縮數據計算的 CRC 不匹配。
130+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
131+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
132+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
133+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
134+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
135+35: System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
136+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與根據解壓縮數據計算的 CRC 不匹配。
137+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
138+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
139+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與根據解壓縮數據計算的 CRC 不匹配。
140+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與根據解壓縮數據計算的 CRC 不匹配。
141+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與根據解壓縮數據計算的 CRC 不匹配。
142+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
143+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與根據解壓縮數據計算的 CRC 不匹配。
144+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
145+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
146+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
147+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與根據解壓縮數據計算的 CRC 不匹配。
148+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與根據解壓縮數據計算的 CRC 不匹配。
149+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與根據解壓縮數據計算的 CRC 不匹配。
150+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與根據解壓縮數據計算的 CRC 不匹配。
151+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
152+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
153+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
154+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
155+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與根據解壓縮數據計算的 CRC 不匹配。
156+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與從解壓縮數據計算的 CRC 不匹配。
157+35:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與根據解壓縮數據計算的 CRC 不匹配。
158+36:無效數據(無異常)
159+36:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與根據解壓縮數據計算的 CRC 不匹配。
160+36:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與根據解壓縮數據計算的 CRC 不匹配。
161+37:無效數據(無異常)
162+38:無效數據(無異常)
163+39:無效數據(無異常)
164+40:無效數據(無例外)
165+41:無效數據(無異常)
166+41:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與根據解壓縮數據計算的 CRC 不匹配。
167+41:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與根據解壓縮數據計算的 CRC 不匹配。
168+41:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與根據解壓縮數據計算的 CRC 不匹配。
169+41:System.IO.InvalidDataException:GZip 頁腳中的 CRC 與根據解壓縮數據計算的 CRC 不匹配。
170+41:System.IO.InvalidDataException:GZip 頁腳中的流大小與實際流大小不匹配。
171+41:System.IO.InvalidDataException:GZip 頁腳中的流大小與實際流大小不匹配。
172+41:System.IO.InvalidDataException:GZip 頁腳中的流大小與實際流大小不匹配。
173+41:System.IO.InvalidDataException:GZip 頁腳中的流大小與實際流大小不匹配。

這是在 LINQPad 中可複制粘貼執行的測試(對於 .NET 3.5 和 4,使用“作為 C# 語句”模式):

  string sample = "This is a compression test of microsoft .net gzip compression method and decompression methods";
  var encoding = new ASCIIEncoding();
  var data = encoding.GetBytes(sample);
  string sampleOut = null;
  byte[] cmpData;

  // Compress 
  using (var cmpStream = new MemoryStream())
  {
     using (var hgs = new System.IO.Compression.GZipStream(cmpStream, System.IO.Compression.CompressionMode.Compress))
     {
        hgs.Write(data, 0, data.Length);
     }
     cmpData = cmpStream.ToArray();
  }

  int corruptBytesNotDetected = 0;

  // corrupt data byte by byte
  for (var byteToCorrupt = 0; byteToCorrupt < cmpData.Length; byteToCorrupt++)
  {
     var corruptData = new List<byte>(cmpData).ToArray();
     // corrupt the data
     corruptData[byteToCorrupt]++;

     using (var decomStream = new MemoryStream(corruptData))
     {
        using (var hgs = new System.IO.Compression.GZipStream(decomStream, System.IO.Compression.CompressionMode.Decompress))
        {
           using (var reader = new StreamReader(hgs))
           {
              string message;
              try
              {
                 sampleOut = reader.ReadToEnd();

                 // if we get here, the corrupt data was not detected by GZipStream
                 // ... okay so long as the correct data is extracted

                 if (!sample.SequenceEqual(sampleOut)) {
                   corruptBytesNotDetected++;
                   message = "Invalid data (No Exception)";
                 } else {
                   message = "Good data (No Exception)";
                 }
              }
              catch(Exception ex)
              {
                   message = (ex.GetType() + ":" + ex.Message);
              }
              string.Format("{0}+{1}: {2}",
                   byteToCorrupt, corruptBytesNotDetected, message).Dump();
           }
        }
     }

  }

這是*.NET 3.5*中的壓縮數據(GZipStream 在“壓縮”小​​負載方面出了名的差,但這是一個“無法修復”的問題,因為該流在技術上仍然有效):

1F 8B 08 00 00 00 00 00 04 00 ED BD 07 60 1C 49 96 25 26 2F
6D CA 7B 7F 4A F5 4A D7 E0 74 A1 08 80 60 13 24 D8 90 40 10
EC C1 88 CD E6 92 EC 1D 69 47 23 29 AB 2A 81 CA 65 56 65 5D
66 16 40 CC ED 9D BC F7 DE 7B EF BD F7 DE 7B EF BD F7 BA 3B
9D 4E 27 F7 DF FF 3F 5C 66 64 01 6C F6 CE 4A DA C9 9E 21 80
AA C8 1F 3F 7E 7C 1F 3F 22 DE CC 8B 26 A5 FF 65 E9 B4 5A 交流
EA BC 69 8A 6A 99 B6 79 D3 A6 D5 79 BA 28 A6 75 D5 54 E7 6D
3A 5E E6 6D 7A F1 83 62 15 B4 5B E4 ED BC 9A A5 D9 72 96 CE
F2 FE 17 CD FF 03 5C 51 5E 27 5E 00 00 00

(而且,只是為了傻笑,在 .NET 4 中它會生成一個稍大/不同的壓縮流。)

1F 8B 08 00 00 00 00 00 04 00 EC BD 07 60 1C 49 96 25 26 2F
6D CA 7B 7F 4A F5 4A D7 E0 74 A1 08 80 60 13 24 D8 90 40 10
EC C1 88 CD E6 92 EC 1D 69 47 23 29 AB 2A 81 CA 65 56 65 5D
66 16 40 CC ED 9D BC F7 DE 7B EF BD F7 DE 7B EF BD F7 BA 3B
9D 4E 27 F7 DF FF 3F 5C 66 64 01 6C F6 CE 4A DA C9 9E 21 80
AA C8 1F 3F 7E 7C 1F 3F 22 DE CC 8B 26 A5 FF 65 E9 B4 5A 交流
EA BC 69 8A 6A 99 B6 79 D3 A6 D5 79 BA 28 A6 75 D5 54 E7 6D
3A 5E E6 6D 7A F1 83 62 15 B4 5B E4 ED BC 9A A5 D9 72 96 CE
F2 FE 17 CD FF 13 00 00 FF FF 5C 51 5E 27 5E 00 00 00

補充說明:

在這種情況下,測試可能存在*細微的缺陷。*當 GZipStream “未能檢測到損壞”(無異常)時,從 StreamReader 讀取的數據為“”(空字元串):在這種情況下,為什麼不ReadToEnd() 引發異常(IOException 或其他)?

因此不是GZipStream 而是這裡“古怪”的 StreamReader 還是 GZipStream 仍然存在問題(因為不拋出異常)?是否有一些正確的方法來可靠地處理這個案例?(考慮當來自目前位置的輸入流真的是空的時候。)

前言

。網

$$ 4 and previous $$使用者在任何情況下都不應使用 Microsoft 提供的 GZipStream 或 DeflateStream 類,除非 Microsoft 將它們完全替換為可用的東西。請改用 DotNetZip 庫。

更新前言

.NET Framework 4.5 及更高版本已修復壓縮問題,GZipStream 和 DeflateStream 在這些版本中使用 zlib。我不知道下面提到的 CRC 問題是否已修復。

另一個更新

CRC錯誤不僅沒有修復,而且微軟已經決定他們不會修復它!

 

我在為什麼我的 C# gzip 生成的文件比 Fiddler 或 PHP 大?表明此行為並未反映 gzip 損壞檢測的正確實現。在所有測試的情況下,正確的實現都會檢測到錯誤。(該回復還說明了為什麼其中七個案例產生了良好的數據。)

另一個問題是:這種“壓縮”方法如何從 94 字節的字元串中產生 174 字節的輸出?特別是看到字元串是如何可壓縮的——gzip 將其減少到 84 字節,即使有 18 字節的標頭和尾標的成本。你能提供那 174 個字節的十六進制轉儲嗎?

引用自:https://stackoverflow.com/questions/9471826