為什麼 BCL GZipStream(帶有 StreamReader)不能可靠地檢測 CRC32 的數據錯誤?
前幾天我遇到了一個問題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 個字節的十六進制轉儲嗎?