Хеш-функции: коллизии и length extension
Хеш-функция должна быть тремя вещами одновременно: сжимать данные в фиксированный дайджест, не давать восстановить прообраз и не давать найти два разных сообщения с одним дайджестом. Криптографические протоколы полагаются ровно на эти три свойства — и рушатся, когда конструкция даёт четвёртое, о котором никто не просил.
Тема разбирает SHA-256 изнутри: функцию сжатия, дополнение Меркла — Дамгора, итеративное вычисление. Из итеративности растёт первая атака — удлинение сообщения: если тег вычислен как SHA-256(key || msg), противник, зная msg и тег, достраивает сообщение и получает валидный тег, не зная ключа. Вторая атака — коллизии: метод дней рождения даёт их за 2^(n/2) операций, а для усечённых хешей это буквально минуты. Третья часть — деревья Меркле: как из «неудобного» свойства хешей сделать прозрачный журнал, в котором доказуемо нет подмены.
Свойства хеш-функции
Три задачи
Для хеш-функции H: {0,1}* → {0,1}^n различают три задачи. Прообраз: по y найти m с H(m) = y — сложность 2^n. Второй прообраз: по m найти m' ≠ m с H(m') = H(m) — тоже 2^n. Коллизия: найти любые m ≠ m' с равными хешами — 2^(n/2) по парадоксу дней рождения. Коллизия — самая дешёвая задача, и именно поэтому 128-битный хеш даёт только 64 бита стойкости к коллизиям.
Хеш не шифрует и не аутентифицирует сам по себе: он сжимает. Аутентификацию даёт конструкция поверх хеша — HMAC, подпись, дерево Меркле.
Требования к протоколам
Подпись документа требует стойкости ко второму прообразу. Прозрачный журнал — стойкости к коллизиям. Хранение паролей — медленности и соли, а не «криптостойкости» хеша. Разные задачи — разные требования, и путать их дорого: MD5 был приемлем для checksum и катастрофой для подписи.
Merkle — Дамгор и SHA-256
Функция сжатия
SHA-256 обрабатывает сообщение блоками по 64 байта. Внутреннее состояние — восемь 32-битных слов H_0…H_7, инициализированных константами FIPS 180-4. Каждый блок расширяется до 64 слов расписания, затем 64 раунда перемешивают состояние: сложения по модулю 2^32, XOR, циклические сдвиги и выборки Ch/Maj 1. Функция сжатия f(state, block) — это и есть вся криптографическая начинка; всё остальное — режим итерации.
Дополнение фиксирует длину: байт 0x80, нули, затем 64-битная длина сообщения в битах, чтобы итог делился на 64. Без длины два разных сообщения могли бы дать одинаковую последовательность блоков.
Итеративность и её цена
Схема Меркла — Дамгора: h_0 = IV, h_i = f(h_{i-1}, M_i), дайджест — h_k. Это позволяет хешировать поток и не хранить сообщение целиком. Плата — структурная: состояние после обработки префикса полностью определяет продолжение; Келси и Шнайер показали, что из этого следуют и атаки на второй прообраз 3. Если противник знает h_i, он может продолжить вычисление на любых данных, как будто префикс уже обработан.
Именно это свойство эксплуатирует атака удлинения. Оно не является «багом SHA-256»: так устроена любая итеративная хеш-функция. Лечится оно не изменением хеша, а правильной конструкцией MAC.
Удлинение сообщения
Схема атаки
Пусть сервис выдаёт пропуск msg и тег tag = SHA-256(key || msg), а затем проверяет пару целиком. Противник знает msg, tag и не знает key. Он строит new_msg = msg || padding(len(key) + len(msg)) || suffix и вычисляет new_tag, продолжив хеширование с состояния, восстановленного из tag, на данных suffix. Ключ при этом не нужен: он уже «съеден» вместе с префиксом.
Длина ключа неизвестна, но она коротка: перебор от 1 до 64 байт занимает мгновения. Для каждой гипотезы строится своя попытка, и сервис сам скажет, какая верна.
Условия и последствия
Атака работает, когда MAC вычисляется как «хеш от ключа и сообщения» — в любом порядке, где ключ стоит в начале. Она не работает с HMAC, потому что HMAC хеширует ключ дважды и внешний слой не позволяет продолжить вычисление; не работает и с SHA-3, потому что губка не даёт доступа к состоянию между блоками 7.
SHA-3 устроен иначе: губка (sponge) с внутренним состоянием, из которого наружу отдаётся только часть, поэтому продолжить вычисление нельзя 8.
Правило: хеш-функция — не MAC. Если нужен MAC, берётся HMAC или AEAD; если нужна подпись — подписывается хеш, но не «хеш с секретом внутри».
Коллизии и дни рождения
Парадокс дней рождения
Если n бит хеша, то после примерно 1.18·2^(n/2) случайных сообщений вероятность коллизии превышает половину. Для 256-битного хеша это 2^128 — недостижимо; для усечённого до 32 бит — около 77 тысяч попыток, то есть секунды.
Коллизия ломает не конфиденциальность, а целостность: два разных документа с одинаковым хешем проходят любую проверку, основанную на хеше. Для подписи это означает возможность подсунуть один документ вместо другого.
Ро-метод
Хранить все значения дорого; ро-метод Флойда находит цикл в последовательности x → h(encode(x)) за O(1) памяти. Вход в цикл даёт два разных прообраза одного значения — коллизию. Для 24–32 бит это учебная задача на минуту; для 256 бит — недостижимо, и это ровно та граница, на которой держится стойкость.
Реальные коллизии
MD5 практически ломается с 2004 года 4, и это использовали: в 2012 году вредонос FLAME подписывался поддельным сертификатом, построенным на коллизии MD5, выданным удостоверяющим центром 6. SHA-1 получил первую практическую коллизию в 2017 году (SHAttered): два разных PDF с одинаковым SHA-1, стоимость — около 110 тысяч GPU-лет 5. Обе истории закончились отзывом алгоритмов из протоколов, но не мгновенно.
Деревья Меркле
Зачем дерево
Хеш одного файла проверяется легко, но если файлов миллион — проверять все нельзя. Дерево Меркле позволяет доказать включение одного листа за O(log n) хешей: корень публикуется, путь аудита передаётся вместе с листом, проверяющий пересчитывает корень.
Такая конструкция лежит в основе Certificate Transparency: журнал сертификатов публикует корень, а любой может проверить, что его сертификат включён и что журнал не подменяет историю 2. Важная деталь — доменная сепарация: листья хешируются с префиксом 0x00, узлы — с 0x01, иначе можно подставить узел вместо листа (атака второй прообраз).
Что даёт и чего не даёт
Дерево даёт доказуемость включения и согласованности, но не конфиденциальность: листья видны. Оно не защищает от «журнала, который вообще не существует», — для этого нужны сплетни (gossip) и подписанные контрольные точки.
Итоги темы
- Хеш решает три задачи с разной сложностью; коллизии — самая дешёвая (
2^(n/2)). - SHA-256 итеративен: состояние после префикса определяет продолжение.
SHA-256(key || msg)— не MAC: атака удлинения достраивает сообщение без ключа.- HMAC и губка SHA-3 закрывают удлинение конструктивно.
- Коллизии ломают целостность; MD5 и SHA-1 сломаны практически.
- Дерево Меркле превращает хеш в доказуемый журнал; доменная сепарация обязательна.
Литература
- NIST. FIPS 180-4: Secure Hash Standard. 2015. — SHA-256, функция сжатия и дополнение.
- RFC 9162: Certificate Transparency Version 2.0. 2021. — деревья Меркле, пути аудита и доказательства согласованности.
- Kelsey J., Schneier B. Second Preimages on n-bit Hash Functions for Much Less than 2^n Work. EUROCRYPT 2005. — структурные атаки на итеративные хеши.
- Wang X., Feng D., Lai X., Yu H. Collisions for Hash Functions MD4, MD5, HAVAL-128 and RIPEMD. 2004. — практический взлом MD5.
- Stevens M., Bursztein E., Karpman P., Albertini A., Markov Y. The First Collision for Full SHA-1. CRYPTO 2017. — SHAttered.
- Sotirov A. et al. MD5 Considered Harmful Today. 2008. — поддельный сертификат для FLAME.
- Krawczyk H., Bellare M., Canetti R. RFC 2104: HMAC: Keyed-Hashing for Message Authentication. 1997. — почему HMAC устойчив к удлинению.
- NIST. FIPS 202: SHA-3 Standard. 2015. — губка, устойчивая к удлинению конструктивно.