Почему BCL GZipStream (с StreamReader) не позволяет надежно обнаруживать ошибки данных в CRC32?
На днях я столкнулся с вопросом, GZipStream не обнаруживает поврежденные данные (даже проходы CRC32)? (Из этого вполне может быть "дубликат", у меня смешанные чувства по этому вопросу. Я также был тем, кто добавил CRC32 к названию, но в ретроспективе, который кажется неуместным с остальной частью поста). После самостоятельного изучения проблемы, я думаю, что эта проблема намного больше, чем изначально представляет другой вопрос.
Я расширил другой вопрос и сделал тестовый код работающим под LINQPad и попытался лучше продемонстрировать проблему CRC32 (Cyclic Redundancy Check), если она действительно существует. (Так как код является лишь незначительной модификацией, основанной на оригинале, возможно, что установка / методология теста ошибочна, или есть другая странная особенность /PEBCAK оба.)
Результаты странные, потому что поврежденные данные не всегда вызывают (любое!) Исключение. Обратите внимание, что только иногда проверка CRC32 кажется "работающей". Поврежденные байты, которые приводят к тому, что индекс выходит за пределы диапазона / плохой заголовок / плохой нижний колонтитул, могут быть проигнорированы, потому что мы можем предположить, что они убивают декомпрессию до проверки CRC32 (что вполне понятно, даже если вероятность исключения IndexOutOfRangeException InvalidDataException) так,
Почему проверка CRC32 значительно менее надежна, чем должна быть? (Почему это так: "Неверные данные (без исключения)" ниже?)
Поскольку нижний колонтитул 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, неизвестен. 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: индекс находился за пределами массива. 20+2: System.IndexOutOfRangeException: индекс находился за пределами массива. 21+3: неверные данные (без исключения) 22+3: System.IndexOutOfRangeException: индекс находился за пределами массива. 23+3: System.IndexOutOfRangeException: индекс находился за пределами массива. 24+4: неверные данные (без исключения) 25+4: System.IndexOutOfRangeException: индекс находился за пределами массива. 26+4: System.IndexOutOfRangeException: индекс находился за пределами массива. 27+4: System.IndexOutOfRangeException: индекс находился за пределами массива. 28+4: System.IndexOutOfRangeException: индекс находился за пределами массива. 29+5: недействительные данные (без исключения) 30+5: System.IndexOutOfRangeException: индекс находился за пределами массива. 31+6: неверные данные (без исключений) 32+7: неверные данные (без исключений) 33+7: System.IndexOutOfRangeException: индекс находился за пределами массива. 34+7: System.IndexOutOfRangeException: индекс находился за пределами массива. 35+7: System.IndexOutOfRangeException: индекс находился за пределами массива. 36+8: недействительные данные (без исключения) 37+8: System.IndexOutOfRangeException: индекс находился за пределами массива. 38+8: System.IndexOutOfRangeException: индекс находился за пределами массива. 39+9: недействительные данные (без исключения) 40+9: System.IndexOutOfRangeException: индекс находился за пределами массива. 41+9: System.IndexOutOfRangeException: индекс находился за пределами массива. 42+10: неверные данные (без исключения) 43+10: System.IO.InvalidDataException: обнаружены неверные данные при декодировании. 44+10: System.IndexOutOfRangeException: индекс находился за пределами массива. 45+10: System.IO.InvalidDataException: обнаружены неверные данные во время декодирования. 46+11: неверные данные (без исключений) 47+11: System.IndexOutOfRangeException: индекс находился за пределами массива. 48+11: System.IndexOutOfRangeException: индекс находился за пределами массива. 49+11: System.IndexOutOfRangeException: индекс находился за пределами массива. 50+12: недействительные данные (без исключения) 51+12: System.IndexOutOfRangeException: индекс находился за пределами массива. 52+12: System.IndexOutOfRangeException: индекс находился за пределами массива. 53+13: недействительные данные (без исключения) 54+13: System.IndexOutOfRangeException: индекс находился за пределами массива. 55+14: неверные данные (без исключения) 56+14: System.IndexOutOfRangeException: индекс находился за пределами массива. 57+15: неверные данные (без исключения) 58+15: System.IndexOutOfRangeException: индекс находился за пределами массива. 59+15: System.IndexOutOfRangeException: индекс находился за пределами массива. 60+16: неверные данные (без исключения) 61+17: неверные данные (без исключения) 62+18: неверные данные (без исключения) 63+19: неверные данные (без исключения) 64+19: System.IndexOutOfRangeException: индекс был за пределами массива. 65+19: System.IndexOutOfRangeException: индекс находился за пределами массива. 66+19: System.IndexOutOfRangeException: индекс находился за пределами массива. 67+19: System.IndexOutOfRangeException: индекс находился за пределами массива. 68+19: System.IndexOutOfRangeException: индекс находился за пределами массива. 69+19: System.IndexOutOfRangeException: индекс находился за пределами массива. 70+19: System.IO.InvalidDataException: обнаружены неверные данные во время декодирования. 71+19: System.IndexOutOfRangeException: индекс находился за пределами массива. 72+19: System.IndexOutOfRangeException: индекс находился за пределами массива. 73+19: System.IndexOutOfRangeException: индекс находился за пределами массива. 74+19: System.IndexOutOfRangeException: индекс находился за пределами массива. 75+19: System.IO.InvalidDataException: обнаружены неверные данные при декодировании. 76+19: System.IndexOutOfRangeException: индекс находился за пределами массива. 77+19: System.IndexOutOfRangeException: индекс находился за пределами массива. 78+19: System.IndexOutOfRangeException: индекс находился за пределами массива. 79+19: System.IndexOutOfRangeException: индекс находился за пределами массива. 80+19: System.IndexOutOfRangeException: индекс находился за пределами массива. 81+19: System.IO.InvalidDataException: обнаружены неверные данные во время декодирования. 82+19: System.IndexOutOfRangeException: индекс находился за пределами массива. 83+20: неверные данные (без исключений) 84+21: неверные данные (без исключений) 85+22: неверные данные (без исключений) 86+22: System.IndexOutOfRangeException: индекс находился за пределами массива. 87+23: неверные данные (без исключений) 88+24: неверные данные (без исключений) 89+25: неверные данные (без исключений) 90+25: System.IndexOutOfRangeException: индекс находился за пределами массива. 91+26: недействительные данные (без исключения) 92+26: System.IO.InvalidDataException: обнаружены недопустимые данные при декодировании. 93+26: System.IndexOutOfRangeException: индекс находился за пределами массива. 94+27: неверные данные (без исключения) 95+27: System.IndexOutOfRangeException: индекс находился за пределами массива. 96+27: System.IndexOutOfRangeException: индекс находился за пределами массива. 97+28: недопустимые данные (без исключения) 98+28: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 99+28: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 100+29: неверные данные (без исключения) 101+30: неверные данные (без исключения) 102+31: неверные данные (без исключения) 103+32: неверные данные (без исключения) 104+32: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не совпадает с CRC, рассчитанным по распакованным данным. 105+33: неверные данные (без исключения) 106+34: неверные данные (без исключения) 107+35: неверные данные (без исключения) 108+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC рассчитывается по распакованным данным. 109+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 110+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 111+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 112+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 113+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 114+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 115+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 116+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 117+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 118+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 119+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 120+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 121+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 122+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 123+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 124+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 125+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 126+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 127+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 128+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 129+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 130+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 131+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 132+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 133+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 134+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 135+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 136+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 137+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 138+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 139+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 140+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 141+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 142+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 143+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 144+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 145+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 146+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 147+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 148+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 149+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 150+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 151+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 152+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 153+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 154+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 155+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 156+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 157+35: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 158+36: неверные данные (без исключения) 159+36: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 160+36: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 161+37: неверные данные (без исключения) 162+38: неверные данные (без исключения) 163+39: неверные данные (без исключения) 164+40: неверные данные (без исключения) 165+41: неверные данные (без исключения) 166+41: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 167+41: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 168+41: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует CRC, вычисленному из распакованных данных. 169+41: System.IO.InvalidDataException: CRC в нижнем колонтитуле GZip не соответствует 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 не соответствует реальному размеру потока.
Вот тест, который можно запустить copy'n'paste в 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 AC 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 AC 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 (из-за отсутствия исключения)? Есть ли какой-то правильный способ надежно обработать этот вариант использования? (Рассмотрим, когда входной поток из текущей позиции действительно пуст.)
1 ответ
Предисловие
Пользователи.NET [4 и более ранние] ни при каких обстоятельствах не должны использовать предоставляемые Microsoft классы GZipStream или DeflateStream, если Microsoft полностью не заменит их чем-то, что работает. Вместо этого используйте библиотеку DotNetZip.
Обновить до предисловия
В.NET Framework 4.5 и более поздних версиях исправлена проблема сжатия, и GZipStream и DeflateStream используют zlib в этих версиях. Я не знаю, была ли решена проблема CRC, упомянутая ниже.
Еще одно обновление
Ошибка CRC не только не исправлена, но Microsoft решила, что они не исправят ее!
Мой ответ: Почему мой C# gzip создает файл большего размера, чем Fiddler или PHP? показывает, что это поведение не отражает правильную реализацию обнаружения повреждения gzip. Во всех протестированных случаях ошибка будет обнаружена правильной реализацией. (В этом ответе также отмечается, почему в семи случаях получены хорошие данные.)
Другой вопрос: как этот метод "сжатия" создает 174 байта вывода из 94-байтовой строки? Особенно с учетом того, как строка является сжимаемой - gzip уменьшает ее до 84 байт, даже с учетом заголовка и трейлера из 18 байт. Можете ли вы предоставить шестнадцатеричный дамп этих 174 байтов?