9

前几天我遇到了一个问题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

1 回答 1

13

前言

.NET [4 和以前的] 用户在任何情况下都不应使用 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 个字节的十六进制转储吗?

于 2012-02-27T20:48:24.210 回答