GZipStream无法检测到损坏的数据(即使CRC32也通过了)?

5
我正在使用GZipStream来压缩/解压数据。我选择它而不是DeflateStream,因为文档说明GZipStream还添加了CRC以检测损坏的数据,这是我想要的另一个功能。我的“正面”单元测试效果很好,因为我可以压缩一些数据,保存压缩的字节数组,然后成功地再次解压缩它。.NET GZipStream compress and decompress problem帖子帮助我意识到在访问压缩或解压缩数据之前需要关闭GZipStream。
接下来,我继续编写“负面”单元测试,以确保可以检测到损坏的数据。我之前使用了MSDN的GZipStream类示例来压缩文件,用文本编辑器打开压缩文件,更改一个字节以使其损坏(好像用文本编辑器打开它已经够糟糕的了!),保存并解压缩它,以确保我得到了预期的InvalidDataException异常。
当我写单元测试时,我选择了一个任意的字节进行损坏(例如,compressedDataBytes[50] = 0x99),然后得到了一个InvalidDataException异常。到目前为止还不错。我很好奇,所以我选择了另一个字节,但令我惊讶的是我没有得到异常。这可能是可以接受的(例如,我巧合地命中了数据块中未使用的字节),只要数据仍然可以成功恢复。然而,我也没有得到正确的数据!
为了确保“不是我”的问题,我从.NET GZipStream compress and decompress problem底部清理过的代码,并修改为按顺序破坏压缩数据中的每个字节,直到无法正确解压缩为止。以下是更改(请注意,我正在使用Visual Studio 2010测试框架):
// successful compress / decompress example code from:
//    https://dev59.com/pnI-5IYBdhLWcg3w99kH
[TestMethod]
public void Test_zipping_with_memorystream_and_corrupting_compressed_data()
{
   const 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 GZipStream(cmpStream, 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++)
   {
      // corrupt the data
      cmpData[byteToCorrupt]++;

      using (var decomStream = new MemoryStream(cmpData))
      {
         using (var hgs = new GZipStream(decomStream, CompressionMode.Decompress))
         {
            using (var reader = new StreamReader(hgs))
            {
               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
                  corruptBytesNotDetected++;

                  var message = string.Format("ByteCorrupted = {0}, CorruptBytesNotDetected = {1}",
                     byteToCorrupt, corruptBytesNotDetected);

                  Assert.IsNotNull(sampleOut, message);
                  Assert.AreEqual(sample, sampleOut, message);
               }
               catch(InvalidDataException)
               {
                  // data was corrupted, so we expect to get here
               }
            }
         }
      }

      // restore the data
      cmpData[byteToCorrupt]--;
   }
}

当我运行这个测试时,我得到:
Assert.AreEqual failed. Expected:<This is a compression test of microsoft .net gzip compression method and decompression methods>. Actual:<>. ByteCorrupted = 11, CorruptBytesNotDetected = 8

所以,这意味着实际上有7个案例,在这些案例中破坏数据并没有产生影响(字符串成功恢复),但是破坏第11个字节既没有抛出异常,也没有恢复数据。
我是否遗漏了什么或做错了什么?有人能看出为什么无法检测到损坏的压缩数据吗?

检查被抛出的异常:并不总是InvalidDataException。 - user166390
然而,我无法解释为什么CRC32在所有情况下都不会引发异常(它应该比看起来更可靠),即使它确实可以检测到CRC错误,例如,在“超过11”的60-80%的无效情况下,这些情况不会导致Huffman查找异常或其他内部解码错误。我曾经认为CRC32应该产生超过99.999%(甚至更多)的检测率,但这似乎并非在这里的情况。 - user166390
我只捕获InvalidDataException异常,所以任何其他异常都会导致测试失败,因为那个异常。然而,测试之所以失败是因为没有抛出异常,并且解压缩的数据与预期不符。谢谢你的想法。虽然我仍然觉得我错过了什么,但测试似乎非常简单明了。 - Andrew Teare
我错了,所以我更新/删除了旧的注释。在某些情况下(不是InvalidDataException),当Huffman表查找失败时(Microsoft泄漏了一些细节!),测试会停止,抛出IndexOutOfRangeException异常。 - user166390
与此同时,我已经在简单的CRC16校验和中“包装”了压缩数据结果,并且它捕获了100%的字节损坏(与上面相同的测试),正如我所预期的那样。这让我想到GZipStream应用的“CRC”并没有覆盖所有数据。即使如此,我也不希望它“通过”,但仍然提供错误的数据。 - Andrew Teare
显示剩余5条评论
1个回答

8

在gzip格式中有一个10字节的头部,其中最后7个字节可以更改而不会导致解压错误。 因此,你所指出的没有损坏的七种情况是可以预期的。

在流的任何其他位置发生损坏而未检测到错误应该是非常罕见的。大多数情况下,解压缩器将检测到压缩数据格式的错误,并且甚至不会到达检查crc的点。如果它确实到达检查crc的点,那么在输入流损坏的情况下,这个检查几乎总是会失败。("几乎总是"意味着概率约为1-2^-32。)

我刚刚使用你的样本字符串在C语言中使用zlib尝试了一下,生成了一个84字节的gzip流。像你一样将这84个字节中的每个都递增,但保持其余部分不变,结果是:两个错误的头检查,一个无效的压缩方法,七个成功,一个无效的块类型,四个无效的距离设置,七个无效的代码长度设置,四个缺少块结束符,11个无效的位长度重复,三个无效的位长度重复,两个无效的位长度重复,两个意外的流结束,36个不正确的数据检查(即实际的CRC错误)和四个不正确的长度检查(gzip格式中用于正确的未压缩数据长度的另一个检查)。在任何情况下,损坏的压缩流都没有被检测到。

因此,必须有一个bug,要么在你的代码中,要么在类中。

更新:

看起来类中存在一个bug。

值得注意的是(或许也不),微软已经得出结论,他们won't fix|不会修复这个bug!


.net的GZipStream类产生了178个字节的压缩数据。更改其中的45个字节没有抛出异常,在其中的38个情况下,数据也没有被恢复。这强烈指向框架类中存在一个错误,特别是因为您已经测试了该方法和另一个完全不同的实现。尽管如此,我明天会与其他人一起审查代码。感谢您抽出时间来尝试这个。 - Andrew Teare
@Mark Adler,问题在于CRC32检查似乎不在可接受的可靠性范围内工作:(尝试使用发布的示例(但捕获异常而不是InvalidDataException),您会发现只有有时 CRC被确定为无效...这很奇怪。(有时内部Huffman表的使用会生成IOBE,这就是为什么我建议将其更改为捕获异常进行测试,但这是另一个问题,可能会导致读取器在到达CRC检查之前呕吐。) - user166390
@AndrewTeare 我知道这是旧的问题,但我也遇到了同样的问题。这个链接http://connect.microsoft.com/VisualStudio/feedback/details/726860/gzipstream-does-not-seem-to-detect-corrupt-data 是你的反馈案例吗? - Johannes Rudolph
是的,那就是我报告的问题。我也应该在这里放一个链接...谢谢你添加它。很遗憾它“无法修复”。最终我使用了GZipStream,但计算了一个校验和并将其与数据一起保存以检测损坏的数据。 - Andrew Teare
2
@AndrewTeare 这个链接已经失效了。微软的问题跟踪器出了很多问题。如果你能帮我找到或者在这里重新发布微软的答案,我会很感激。特别是在Mark Adler在这里引用了这个问题之后:https://dev59.com/THPYa4cB1Zd3GeqPkIoe - tsul
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接