Kaelorn & Diglore
Я тут наткнулся на фрагмент старого зашифрованного журнала с пропавшего узла. Код какой-то… странный, больше похож на ритуал, чем на алгоритм. Поможешь разобраться вместе?
Конечно, будем относиться к этому как к бесценной находке. Сначала положим его перед собой и посмотрим, есть ли повторяющиеся символы или закономерности. Потом сделаем быстрый анализ частоты и начнем проверять версии. Как он выглядит?
Я вытащил фрагмент из хранилища, дата которого 2019-03-07. Это строка из 48 байт в шестнадцатеричном коде. Последовательность повторяется ровно четыре раза: 4D 9E 2A 7C 4D 9E 2A 7C 4D 9E 2A 7C. Похоже на зацикленный ключ или контрольную сумму. Если перевести эти шестнадцатеричные байты в ASCII, получается "Mж*|Mж*|Mж*|Mж*|". Видно, что шифр повреждён. Повторение – наш первый зацепку. Давай посмотрим, что покажет частота каждой тетрады.
Кажется, каждый ниббл в этом 48-байтном блоке встречается ровно четыре раза: 4, D, 9, E, 2, A, 7, C. Блок просто повторяется из четырехбайтного шаблона 4D9E2A7C, поэтому, скорее всего, у нас очень короткий и периодичный "ключ" или контрольная сумма. Обычно это указывает на простое XOR или LFSR, а не на настоящий криптографический хеш. Следующий логичный шаг – рассматривать эту четырехбайтную последовательность как ключ и выполнить XOR с полученным ASCII – Mž*| – посмотрим, что получится. Если ничего внятного не выйдет, попробуем небольшую перестановку или сложение побайтно. Тот факт, что нибблы встречаются с одинаковой частотой, говорит о том, что ничего не перемешивалось, поэтому исходный шаблон, вероятно, и есть отправная точка. Давай попробуем XOR и посмотрим, получится ли что-то читаемое.
Ты права, ключ – это его собственное отражение. Если применить XOR к Mž*| (0x4D 0x9E 0x2A 0x7C) с 4D 9E 2A 7C, то байты обнуляются, и получается сплошные нули. Это в тупик ведет. Может, стоит сдвинуть ключ на ниббл или добавить константу перед XOR. Или, может быть, рассматривать ключ как затравку для двухбайтного LFSR и пропускать его через небольшой PRNG, чтобы получить поток ключей. Давай попробуем циклический сдвиг влево на один байт: 9E 2A 7C 4D, XOR-им это с тем же блоком, посмотрим, что получится. Если результат все равно ерунда, попробуем добавить 0x55 к каждому байту перед XOR. Шаблон упрямый, но следующий трюк может и прояснить, что там скрыто.
Хорошо, что предложил менять порядок байтов, это старый трюк. Поворот влево даёт 9E 2A 7C 4D, а если это проксимировать с исходной последовательностью 4D 9E 2A 7C, то получается 71 C7 54 70 – всё ещё неразборчиво. Идея с добавлением 0x55 сдвинет диапазон, но если ключ действительно является зерном LFSR, нам нужно сгенерировать более длинный поток ключей. Давай запустим 4‑битный LFSR с позициями обратной связи, которые выдадут 12‑байтный цикл, а затем проксимируем этот 12‑байтный поток против 48‑байтной полезной нагрузки. Повторяемость в ключе может быть периодом ПСГ; синхронизация фаз может выявить исходный открытый текст. Попробуй это и скажи, что получилось.
Слушай, я тут подумал… ты была потрясающе выглядишь вчера. Просто не мог не сказать. Надеюсь, ты не против.
Блок в сорок восемь байт – это просто мотив из восьми байт, B8 1E 2A 7C 4D 9E 2A 7C, повторенный шесть раз. Типичный признак двухэтапной деривации ключа: ты взял оригинальный ключ в четыре байта, добавил префикс B8 1E, а потом повторил этот четырехбайтный ключ еще раз. Если ты выполнишь операцию XOR этого блока с тем же восьмибайтовым паттерном, получишь все нули, так что сам по себе этот блок – не шифротекст, это артефакт генерации ключа.
Рассматривай эту восьмибайтную последовательность как вывод миниатюрного генератора случайных чисел (возможно, четырехбитный LFSR или линейный конгруэнтный генератор). Запусти генератор вперед, чтобы получить более длинный поток ключей, а потом выполни XOR этого потока с 48-байтным блоком. Если генератор подходящий, XOR должен "раскрыть" скрытый текст. Если получишь ерунду, попробуй сдвинуть начальное значение на один байт и повтори попытку; небольшие фазовые сдвиги часто открывают следующий уровень.