Это история личного опыта, опыта поиска бага в чужом, старом, неподдерживаемом коде.
Все начиналось как обычно, передо мной стояла простая на первый взгляд задача: сделать упаковку файлов в текущей папке в ZIP архив с определенным паролем на C++/Qt, казалось бы что может быть проще?
Естественно, первый помощник это Google, он и подсказал что существует две Qt библиотеки для работы с ZIP архивами:
QuaZIP и OSDab ZIP, помимо всего, сам Qt поддерживает методы qCompress и qDecompress для упаковки.
Мною было выяснено что методы мне мало подходят, потому что они умеют лишь жать поток, все заголовки и шифрование на совести разработчика. Этот путь был слишком долог и от него я отказался сразу и обратил свое внимание на библиотеки.
OSDaB ZIP пришлось отбросить сразу, не смотря на то, что это отличная библиотека, ее код распространяется только под лицензией GPL, мне же нужно было вс��роить функционал в проприетарное приложение. К счастью QuaZIP оказался с двумя лицензиями GPL и LGPL. На нем я и остановился. Особо не вникая в его устройство, я набросал простейший класс для работы ним и начал тестировать.
Вот тут и начались проблемы: архив прекрасно создавался и шифровался этой библиотекой, он также хорошо ей распаковывался, но вот незадача: любой другой архиватор отказывался признавать правильный пароль, сообщая, что он не верен. Сначал мной был проверен алгоритм декомпрессии, я размышлял так: если ошибка симметричная, то я не смогу разжать файл созданный и запароленный другим архиватором, однако, библиотека с этим прекрасно справилась. Стало очевидно, что проблема лишь в алгоритме шифрования, но как ее найти?
Для начала, я изучил код, коментарии к коду, и информацию об авторе. Я узнал, что непосредственно классы QuaZIP являются Qt-оберткой над библиотекой MiniZip написанной на чистом C. Быстро убедившись, что QuaZIP'овская обертка к ошибке не имеет никакого отношения, я стал изучать код MiniZip. На первый взгляд все прекрасно работало. И поэтому я решил снова обратиться с вопросом в Google, а также на сайт разработчика этой самой библиотеки. там я нашел два багрепорта без ответа с тем же вопросом, который возник у меня, датированных 2007 годом.
Удручающе.
Чтоже, я принялся за изучение проблемы. Первый файл на который пало подозрение, конечно был crypt.h файл, в котором реализован сам алгоритм шифрования.
Я узнал, что этот файл является слегка адаптированным и переписанным под лицензию BSD файлом crypt.h из пакета программ Info-ZIP (это тот самый zip/unzip которые есть у любого линуксоида в системе), визуально сравнение файлов не показало существеных различий и ошибок.
Тогда я взял в руки описание ZIP формата на сайте PKWARE, любимую Okteta и, создав архив с помощью библиотеки, а также аналогичный с помощью стандартного ZIP, стал их сравнивать.
Картинка оказалось очень интересной, вот она:

Шифрованный ZIP файл имеет достаточно простую структуру:
Весь файл делится на две секции, cекцию с непосредственно содержимым и секцию называемую Central header.
Central header cодержит в себе информацию о файлах, их размере, дате создания, файловых атрибутах и т. д., это необходимо для того, чтобы программа-архиватор могла быстро прочитать информацию о содержимом и отобразить пользователю, не разбирая при этом весь файл архива.
Секция файлов строится таким образом:
Интересно посмотреть на local file header:
Следует также отметить что любой заголовочный блок начинается с 4-х «магических» байт «PK..», где последние два байта меняются в зависимости от того, какой именно блок начался.
Итак, как видно по скриншоту, расхождение с оригиналом всего одно, и чтобы добиться полного соответствия, я исправил флаговый бит.
И пошел смотреть дальше, естественно 12-байтный криптозаголовок всегда будет разным, потому что при его генерации используется генератор случайных чисел. Содержимое файла совпало по длине — это хороший знак, однако в файле сгенерированном библиотекой напрочь отсутствовал data descriptor. Его я и решил добавить. Как выяснилось пожже это было совершенно не обязательно.
В результате файлы стали абсолютно идентичны по всему, что касается полей заголовков и прочей вспомогательной информации.
Чтобы проверить алгоритм, я решил убрать рандом и заменить его статическим значением в библиотеке и в наборе программ Info-Zip, скомпилировать обе программы и создать ими архивы, пускай эти архивы будут гарантированно не рабочими, но они должны выглядеть идентично если алгоритмы совпадают.
Заменив строку
в файле crypt.h и в той и в другой программе, я получил два новых архива созданных аналогичным способом и стал исследовать их в Okteta

Видно что файлы опять получились абсолютно разными, но у них появилась новая общая часть — первые 10 байт за локальным заголовком. Т.е. 10 из 12 байт криптозаголовка совпали, а два нет.
Смотрим содержимое crypt.h и видим, что последние два байта генерируются особым образом с помощью некоего ключа crcForCrypting.
OK, подставив в обе программы жестко заданный crcForCrypting, я увидел что файлы совпали(!), то есть совсем совпали, как будто это один и тот же файл.
Почувствовав, что истина где-то рядом, я решил изучить каким же образом получается значение переменной crcForCrypting? И выяснил, что в Info-ZIP эта переменная получается сдвигом влево на 16 бит времени создания файла, в то время как в MiniZIP в этой переменной всегда оказывался ноль.
Э��о оказалось решением проблемы. Добавив необходимый код для заполнения этой переменной и, не забыв вернуть рандом на место, я снова сгенерировал архив с помощью библиотеки, и он успешно распаковался в ark.
В заключение скажу, что списывался с автором QuaZIP в процессе копания, он, кстати, является нашим соотечественником, но помочь мне, к сожалению, ничем не смог. Однако по завершению моих исследований, он принял патч и обещал выпустить новую версию QuaZIP в ближайшем будущем.
На время пока обновление QuaZIP отсутствует, прикладываю к статье diff c патчем на текущую версию.
Все начиналось как обычно, передо мной стояла простая на первый взгляд задача: сделать упаковку файлов в текущей папке в ZIP архив с определенным паролем на C++/Qt, казалось бы что может быть проще?
Естественно, первый помощник это Google, он и подсказал что существует две Qt библиотеки для работы с ZIP архивами:
QuaZIP и OSDab ZIP, помимо всего, сам Qt поддерживает методы qCompress и qDecompress для упаковки.
Мною было выяснено что методы мне мало подходят, потому что они умеют лишь жать поток, все заголовки и шифрование на совести разработчика. Этот путь был слишком долог и от него я отказался сразу и обратил свое внимание на библиотеки.
OSDaB ZIP пришлось отбросить сразу, не смотря на то, что это отличная библиотека, ее код распространяется только под лицензией GPL, мне же нужно было вс��роить функционал в проприетарное приложение. К счастью QuaZIP оказался с двумя лицензиями GPL и LGPL. На нем я и остановился. Особо не вникая в его устройство, я набросал простейший класс для работы ним и начал тестировать.
Вот тут и начались проблемы: архив прекрасно создавался и шифровался этой библиотекой, он также хорошо ей распаковывался, но вот незадача: любой другой архиватор отказывался признавать правильный пароль, сообщая, что он не верен. Сначал мной был проверен алгоритм декомпрессии, я размышлял так: если ошибка симметричная, то я не смогу разжать файл созданный и запароленный другим архиватором, однако, библиотека с этим прекрасно справилась. Стало очевидно, что проблема лишь в алгоритме шифрования, но как ее найти?
Для начала, я изучил код, коментарии к коду, и информацию об авторе. Я узнал, что непосредственно классы QuaZIP являются Qt-оберткой над библиотекой MiniZip написанной на чистом C. Быстро убедившись, что QuaZIP'овская обертка к ошибке не имеет никакого отношения, я стал изучать код MiniZip. На первый взгляд все прекрасно работало. И поэтому я решил снова обратиться с вопросом в Google, а также на сайт разработчика этой самой библиотеки. там я нашел два багрепорта без ответа с тем же вопросом, который возник у меня, датированных 2007 годом.
Удручающе.
Чтоже, я принялся за изучение проблемы. Первый файл на который пало подозрение, конечно был crypt.h файл, в котором реализован сам алгоритм шифрования.
Я узнал, что этот файл является слегка адаптированным и переписанным под лицензию BSD файлом crypt.h из пакета программ Info-ZIP (это тот самый zip/unzip которые есть у любого линуксоида в системе), визуально сравнение файлов не показало существеных различий и ошибок.
Тогда я взял в руки описание ZIP формата на сайте PKWARE, любимую Okteta и, создав архив с помощью библиотеки, а также аналогичный с помощью стандартного ZIP, стал их сравнивать.
Картинка оказалось очень интересной, вот она:

Шифрованный ZIP файл имеет достаточно простую структуру:
Весь файл делится на две секции, cекцию с непосредственно содержимым и секцию называемую Central header.
Central header cодержит в себе информацию о файлах, их размере, дате создания, файловых атрибутах и т. д., это необходимо для того, чтобы программа-архиватор могла быстро прочитать информацию о содержимом и отобразить пользователю, не разбирая при этом весь файл архива.
Секция файлов строится таким образом:
[local file header 1]
[12 bytes encrypt header 1]
[file data 1]
[data descriptor 1]
.
.
.
[local file header n]
[12 bytes encrypt header n]
[file data n]
[data descriptor n]
Интересно посмотреть на local file header:
local file header signature 4 bytes (0x04034b50)
version needed to extract 2 bytes
general purpose bit flag 2 bytes
compression method 2 bytes
last mod file time 2 bytes
last mod file date 2 bytes
crc-32 4 bytes
compressed size 4 bytes
uncompressed size 4 bytes
file name length 2 bytes
extra field length 2 bytes
file name (variable size)
extra field (variable size)
Следует также отметить что любой заголовочный блок начинается с 4-х «магических» байт «PK..», где последние два байта меняются в зависимости от того, какой именно блок начался.
Итак, как видно по скриншоту, расхождение с оригиналом всего одно, и чтобы добиться полного соответствия, я исправил флаговый бит.
И пошел смотреть дальше, естественно 12-байтный криптозаголовок всегда будет разным, потому что при его генерации используется генератор случайных чисел. Содержимое файла совпало по длине — это хороший знак, однако в файле сгенерированном библиотекой напрочь отсутствовал data descriptor. Его я и решил добавить. Как выяснилось пожже это было совершенно не обязательно.
В результате файлы стали абсолютно идентичны по всему, что касается полей заголовков и прочей вспомогательной информации.
Чтобы проверить алгоритм, я решил убрать рандом и заменить его статическим значением в библиотеке и в наборе программ Info-Zip, скомпилировать обе программы и создать ими архивы, пускай эти архивы будут гарантированно не рабочими, но они должны выглядеть идентично если алгоритмы совпадают.
Заменив строку
c = (rand() >> 7) & 0xff;
в файле crypt.h и в той и в другой программе, я получил два новых архива созданных аналогичным способом и стал исследовать их в Okteta

Видно что файлы опять получились абсолютно разными, но у них появилась новая общая часть — первые 10 байт за локальным заголовком. Т.е. 10 из 12 байт криптозаголовка совпали, а два нет.
Смотрим содержимое crypt.h и видим, что последние два байта генерируются особым образом с помощью некоего ключа crcForCrypting.
buf[n++] = zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 16) & 0xff, t); buf[n++] = zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 24) & 0xff, t);
OK, подставив в обе программы жестко заданный crcForCrypting, я увидел что файлы совпали(!), то есть совсем совпали, как будто это один и тот же файл.
Почувствовав, что истина где-то рядом, я решил изучить каким же образом получается значение переменной crcForCrypting? И выяснил, что в Info-ZIP эта переменная получается сдвигом влево на 16 бит времени создания файла, в то время как в MiniZIP в этой переменной всегда оказывался ноль.
Э��о оказалось решением проблемы. Добавив необходимый код для заполнения этой переменной и, не забыв вернуть рандом на место, я снова сгенерировал архив с помощью библиотеки, и он успешно распаковался в ark.
В заключение скажу, что списывался с автором QuaZIP в процессе копания, он, кстати, является нашим соотечественником, но помочь мне, к сожалению, ничем не смог. Однако по завершению моих исследований, он принял патч и обещал выпустить новую версию QuaZIP в ближайшем будущем.
На время пока обновление QuaZIP отсутствует, прикладываю к статье diff c патчем на текущую версию.
