Init. commit
This commit is contained in:
50
crypto-digital-fingerprint/WRITEUP.md
Normal file
50
crypto-digital-fingerprint/WRITEUP.md
Normal file
@@ -0,0 +1,50 @@
|
||||
<h1 align="center">Digital Fingerprint</h1>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/category-Crypto-blueviolet" alt="Crypto"/>
|
||||
<img src="https://img.shields.io/badge/points-781-yellow" alt="781 pts"/>
|
||||
</p>
|
||||
|
||||
Есть два файла: `hashlib_custom.py` с реализацией кастомного `CaPlagHash64` и `verify.py`, который регистрирует и проверяет решение. По названию как будто бы нужно найти коллизию, но это не все. Если просто собрать две разные строки с одинаковым хешем и отправить, `verify.py` скажет, что «ты близко», но флаг не отдаст. Читаем верификатор внимательнее и видим вторую проверку:
|
||||
|
||||
| Требование | Описание |
|
||||
|---|---|
|
||||
| Префикс | оба сообщения начинаются с `CAPLAG:` |
|
||||
| Различие | `msg1 != msg2` |
|
||||
| Коллизия | `caplag_hash(msg1) == caplag_hash(msg2)` |
|
||||
| CRC-фильтр | `CRC32(msg) & 0xff == 0` для обоих |
|
||||
|
||||
## Решение
|
||||
|
||||
Начнём с хеша — его нужно разобрать и найти, где он прогибается. Функция сжатия:
|
||||
|
||||
$$\mathrm{compress}(s, b) = \bigl((s \cdot A + b) \oplus B\bigr) \bmod 2^{64}$$
|
||||
|
||||
XOR с константой $B$ вроде бы делает функцию нелинейной, но сам $B$ фиксирован — значит, это просто сдвиг, и структура остаётся почти линейной. Можно обойтись без перебора.
|
||||
|
||||
> **Multi-block collision в [Merkle–Damgård](https://en.wikipedia.org/wiki/Merkle%E2%80%93Damg%C3%A5rd_construction)-хеше.** При фиксированном состоянии $s$ функция $\mathrm{compress}(s, b) = (s \cdot A + b) \oplus B$ — биекция по $b$ с обратимой структурой. Для любых двух состояний после первого блока всегда можно подобрать второй блок, чтобы их уравнять. Коллизия строится алгебраически за одно вычисление.
|
||||
|
||||
Строим коллизию из двух блоков после общего префикса `CAPLAG:\x00`. Префикс одинаковый, значит после первого блока состояние тоже одинаковое:
|
||||
|
||||
$$s_0 = \mathrm{compress}(\mathrm{IV},\, \mathrm{prefix\_block})$$
|
||||
|
||||
Выбираем два разных блока $b_{1a}$ и $b_{1b}$, получаем два разных состояния $s_{1a}$ и $s_{1b}$. Теперь хотим, чтобы следующий раунд их обратно уравнял:
|
||||
|
||||
$$\mathrm{compress}(s_{1a}, b_{2a}) = \mathrm{compress}(s_{1b}, b_{2b})$$
|
||||
|
||||
Раскрываем формулу сжатия — и получаем красивую связь:
|
||||
|
||||
$$b_{2b} = b_{2a} + (s_{1a} - s_{1b}) \cdot A \pmod{2^{64}}$$
|
||||
|
||||
То есть $b_{1a}$, $b_{1b}$ и $b_{2a}$ можно брать любые, а $b_{2b}$ просто вычисляется.
|
||||
|
||||
Вторая часть — CRC32. Просто дописать хвост, чтобы подогнать CRC, не получится: сломается сам хеш. Идем в лоб, коллизии строятся мгновенно, поэтому гоняем генератор в цикле и ждём, пока оба сообщения случайно попадут под `CRC32(msg) & 0xff == 0`. Вероятность для одной пары:
|
||||
|
||||
$$P = \frac{1}{256} \cdot \frac{1}{256} = \frac{1}{65536}$$
|
||||
|
||||
Отправляем подходящую её в `verify.py`, получаем флаг.
|
||||
|
||||
> `verify.py` считает флаг как функцию от найденного `collision_hash`, так что его конкретное значение зависит от того, какую именно коллизию вы нашли. Поэтому флаг - регулярное выражение.
|
||||
|
||||
## Флаг
|
||||
`caplag{...}` (значение зависит от найденной коллизии, принимается по regex)
|
||||
Reference in New Issue
Block a user