Dedalyan — учебно-исследовательский шифр. Его стойкость не доказана и не проверялась независимыми криптоаналитиками. Всё, что здесь измерено, означает отсутствие дешёвых дефектов, а не стойкость: полноценный анализ требует лет работы специалистов.
Для реальных данных берите AES-GCM или ChaCha20-Poly1305 — у них многолетняя история публичного криптоанализа.
Репозиторий опубликован для изучения и разбора. Если вы найдёте атаку — откройте issue, это ровно то, ради чего он выложен.
Структура блока: 96 бит (два слова по 48), ключ 256 бит, 16 раундов, сеть Фейстеля, ARX-раундовая функция, расписание ключей на ключезависимых перестановках. Реализации на Python и C, 8 тестовых наборов, 9 скриптов криптоанализа, CLI, демонстрация с Argon2id и веб-интерфейс.
powershell -ExecutionPolicy Bypass -File build.ps1build\test_vectors.exepython tests/run_all.py --profile quickpython attacks/run_all.py --profile quickДемонстрация «пароль → Argon2id → шифротекст»:
python demo.pyТребуется Python 3.9+, numpy, желательно scipy; для demo.py —
argon2-cffi. Для C-части — MSVC (Visual Studio Build Tools), clang или
gcc; build.ps1 находит их сам.
pip install numpy scipy argon2-cffi| Путь | Назначение |
|---|---|
dedalyan.py |
Эталонная Python-реализация. Читаемость важнее скорости |
dedalyan_c.py |
Мост к C через ctypes + обёртки над ядрами анализа |
dedalyan_harness.py |
Общая инфраструктура: бюджеты, параллелизм, статистика, отчёты |
dedalyan_gcm.py |
GCM-96: GHASH в GF(2^96), аутентифицированное шифрование |
dedalyan_file.py |
Кадрированный формат файлов поверх GCM-96 |
cli.py |
Командная строка: seal/open, режим CTR, подключи, трассировка |
demo.py |
Демонстрация цепочки пароль → Argon2id → ключи → шифротекст + MAC |
webui.py |
Локальный веб-интерфейс к demo.py (только 127.0.0.1) |
build.ps1 |
Сборка C-части (MSVC / clang / gcc) |
c/dedalyan.{h,c} |
C-реализация шифра |
c/kernels.{h,c} |
Горячие циклы для криптоанализа |
c/test_vectors.c |
Проверка векторов раздела 8 на стороне C |
c/bench.c |
Замер циклов на байт |
tests/ |
Тестовый набор раздела 10.2 |
attacks/ |
Криптоаналитический набор раздела 0 |
ref.py |
Исходная эталонная реализация (не изменялась) |
ref.py был первым делом сверен со спецификацией. Расхождений нет:
таблицы лабиринта для обоих ключей, Lab(0x0123456789AB), все 16 подключей
для двух ключей, все четыре вектора шифрования и все 16 строк пораундовой
трассировки TV3 совпадают побитово. Реализации в этом репозитории
воспроизводят те же значения.
python tests/run_all.py --profile standard| Скрипт | Что проверяет |
|---|---|
test_vectors.py |
Все векторы раздела 8 + константы + паритет с C |
test_pitfalls.py |
Мутационные тесты для семи «точек ошибок» раздела 10.4 |
test_roundtrip.py |
Обратимость, 100 000 пар, граничные значения, раунды 1–16 |
test_cross_c.py |
Паритет Python ↔ C ↔ ref.py по каждому примитиву |
test_avalanche.py |
Лавина по раундам 1–16 против таблицы раздела 9 |
test_diffusion.py |
Матрица зависимостей вход→выход, раунд полного покрытия |
test_schedule.py |
Лавина по подключам, эквивалентные ключи, лабиринты |
test_statistics.py |
Режим счётчика: монобит, хи-квадрат, серии, покер |
test_pitfalls.py устроен иначе остальных: для каждой типовой ошибки
написан заведомо неправильный вариант, и проверяется, что он даёт результат,
отличный от эталонного. Это подтверждает, что тесты вообще способны поймать
такую ошибку.
Все заявленные значения воспроизводятся:
| Показатель | Спецификация | Измерено |
|---|---|---|
| Лавина, раунд 2 | 38.45% | 38.02% |
| Лавина, раунд 4 | 49.94% | 50.00% |
| Лавина, раунд 16 | 50.17% | 50.00% |
| Ст. откл., раунд 16 | 5.12% | 5.10% (идеал 5.10%) |
| Покрытие диффузии, р. 1–4 | 26.0 → 75.5 → 99.97 → 100% | 26.01 → 75.46 → 99.97 → 100% |
| Раунд полной диффузии | 4 | 4 |
| Неподвижные точки лабиринта | 1.68 из 32 | 1.682 |
| Монобит в режиме CTR | 0.4997–0.5002 | 0.500037–0.500153 |
| Хи-квадрат по байтам | 233–281 (крит. 293) | 247.9–285.5 |
| Эквивалентные ключи | нет на 80 006 | нет на 200 000 |
| Повторяющиеся подключи | 0 на 20 000 | 0 |
Утверждение раздела 11 о прогреве проверено напрямую: без прогрева лавина
для k[0] падает до 17.47% против 49.35% с прогревом. Спецификация
называет 17% — совпадает.
python attacks/run_all.py --profile standardПрофили: quick (~5 мин), standard (~40 мин), deep (~8 ч),
overnight (сутки+). Любой скрипт принимает --budget SECONDS,
--samples N, --jobs N, --seed N. Один и тот же --seed
воспроизводит прогон в точности. Логи пишутся в attacks/reports/.
| Скрипт | Класс атак |
|---|---|
structural.py |
Биективность лабиринта, инвариантные подпространства, комплементарность, неподвижные точки, слабые ключи, слайд-предпосылки |
differential.py |
Матрица смещений 96×96, поиск разностей, точная вероятность, усечённые дифференциалы, дифференциально-линейный |
linear.py |
Однобитовые и произвольные маски, восхождение по холму по 2^192, линейность множителей и таблиц |
integral.py |
Нулевые суммы по подпространствам, алгебраическая степень, насыщение нибблов |
boomerang.py |
Бумеранг, прямоугольник, йо-йо |
impossible.py |
Побитовые и усечённые невозможные дифференциалы, miss-in-the-middle |
related_key.py |
Дифференциалы и бумеранг на связанных ключах, ротационный и RX-анализ |
key_schedule.py |
Инъективность, meet-in-the-middle, корреляции подключей, роль K_L |
randomness.py |
Подмножество NIST SP 800-22 + экспорт для dieharder / PractRand |
Это главное, что отличает набор от «прогнали, ничего не нашли».
Поправка Бонферрони. Матрица 96×96 — это 9216 одновременных гипотез.
При пороге в три сигмы ложные срабатывания посыплются пачками. Всюду порог
берётся по alpha / T, где T — число проверок.
Масштабный тест. Решающая проверка. Настоящее смещение не меняется при
росте выборки, шум убывает как 1/sqrt(n). Каждый кандидат перемеряется на
n, 10n и 100n со свежими зернями, и смотрится отношение bias@n / bias@100n: около 1 — реальное смещение, около 10 — шум. Одиночный повтор
эксперимента этого не различает, потому что максимум из 9216 шумных оценок
воспроизводится и на втором прогоне.
Контроль чувствительности. Каждый статистический скрипт прогоняется на заведомо слабом варианте (1–3 раунда) и обязан его завалить. Тест, который ничего не заваливает, ничего и не доказывает.
Профиль standard, 12 ядер: 200 000 пар на разность, 400 000 текстов на
маску, k = 24 для интегралов, 8 ключей × 16 МиБ для статистики.
| Класс | Глубина различителя | Запас |
|---|---|---|
| Дифференциальный | раунд 4 (смещение 0.0211, подтверждено) | 12 из 16 |
| Бумеранг / йо-йо | раунд 3 (геометрия) | 13 из 16 |
| Невозможные дифференциалы | раунд 2 | 14 из 16 |
| Линейный | раунд 1 (геометрия Фейстеля) | 15 из 16 |
| Интегральный (нулевая сумма) | раунд 1 | 15 из 16 |
| Дифференциально-линейный | не найден | — |
| Связанные ключи | не найден | — |
| Ротационный / RX | не найден | — |
Раунды 1–3 обязаны показывать детерминированные соотношения: в любой сети
Фейстеля после r раундов одна половина выхода — известная функция входа.
Это геометрия, а не слабость. Значим первый раунд, на котором даже
направленный поиск не бьёт порог шума.
Дифференциал на раунде 4 подтверждён независимо из двух источников (однобитовый скан и поиск по 512 кандидатам), выборки до 5 млн пар:
source rounds bias@n bias@10n bias@100n decay verdict
single 4 0.022000 0.022556 0.021130 1.04 REAL BIAS
searched 4 0.022660 0.020890 0.021027 1.08 REAL BIAS
single 5 0.000860 0.000672 0.000259 3.32 noise
searched 7 0.003640 0.000858 0.000040 91.46 noise
Смещение 0.0211 устойчиво: при росте выборки в 100 раз оно не уходит.
На раунде 5 в матрице 96×96 не осталось ни одной ячейки выше порога
(#above = 0, ratio 0.98), то есть раунд 5, помеченный на быстром профиле
как «на грани», разрешился как чистый.
Попутно уточняются числа раздела 9 спецификации. Там указаны смещения 0.0347 / 0.0360 / 0.0370 для раундов 4 / 5 / 6 при пороге шума 0.027. Порог 0.027 соответствует выборке около 5 000 пар; на таком объёме максимум из 9216 оценок сам по себе даёт около 0.03. Настоящим оказывается только раунд 4, и его величина — 0.021, а не 0.0347. Значения для раундов 5 и 6 — шум, а не дифференциалы.
На стандартном профиле фаза 1 линейного анализа отметила одну маску из 9216 на раунде 5: корреляция 0.02268 при пороге Бонферрони 0.02180 (ratio 1.04). Прямая перепроверка этой маски:
n |corr| noise ratio
400000 0.000050 0.004073 0.01
4000000 0.000043 0.001288 0.03
40000000 0.000127 0.000407 0.31
Шум. Поправка Бонферрони предполагает независимость проверок, а максимум по
сильно коррелированному семейству оценок пробивает её регулярно. Поэтому в
linear.py добавлена фаза 3b: масштабный тест теперь применяется и к
кандидатам фаз 1–2, а не только к результатам восхождения по холму. Итоговый
вердикт строится исключительно по подтверждённым находкам, и линейный
различитель остаётся на раунде 1.
Четыре наблюдения, которых в спецификации нет. Ни одно не является взломом, но каждое стоит внести в документ.
Раздел 6 называет лабиринт «парой перестановок» и не утверждает, что
Lab — перестановка. Читатель, однако, это предполагает. Это неверно.
Lab распадается на шесть независимых 8-битных отображений пар нибблов
(j, j+6): селектор ниббла j — бит 2 ниббла (j+6) mod 12, и наоборот,
то есть пара замкнута. Каждое такое отображение перебирается исчерпывающе
(256 входов), так что это точный результат, а не выборка:
- образ 8-битного отображения — в среднем 224 из 256;
- плотность образа
Labна 48 битах — 0.46; - потеря энтропии — 1.12 бита на применение;
- биективен
Labлишь у 0.04% ключей.
Следствия, по убыванию важности:
- Расписание не обращается однозначно. Для Dedalyan это скорее плюс:
meet-in-the-middle по расписанию ветвится вместо того, чтобы сходиться, и
стоит дороже, чем 2^64 на угадывание
K_L. - Состояние расписания сжимается. Итерация неинъективного отображения
kраз оставляет образ порядка2N/k, то есть после 20 шагов 192-битное состояние покрывает ~2^188.7 значений. Практически неважно. - Подключи не строго равномерны. На масштабе 2^-48 измерения этого не видят: проверены отсутствие коллизий, лавина 50% и отсутствие смещения отдельных бит.
Проверяется скриптом attacks/structural.py, раздел 1.
Раздел 10.1 требует для C «использовать unsigned __int128 либо разложить
множитель на половины». Ни то, ни другое не требуется: умножение uint64_t
по стандарту C сворачивается по модулю 2^64, а младшие 48 бит от
(a·b mod 2^64) совпадают с младшими 48 битами истинного произведения.
Достаточно (a * b) & 0xFFFFFFFFFFFFULL.
Это существенно на практике: MSVC не поддерживает __int128, и
следование букве спецификации закрыло бы основной компилятор Windows.
Спецификация предупреждает, что i mod 24 должно давать неотрицательный
результат. В Python эта ошибка не проявляется вообще: поскольку
48 = 2 · 24, замена i mod 24 на любого другого представителя класса
вычетов меняет 2 · (i mod 24) ровно на кратное 48, то есть не меняет
r_i. Проверено: усечённый к нулю остаток даёт те же 45, 47, 1, 3.
Опасность реальна только в языках, где % усекает к нулю и результат
приводится к беззнаковому типу: в C 2·(−4)+5 = −3, а (unsigned)(−3) % 48 = 13 вместо 45. Мутационный тест в tests/test_pitfalls.py воспроизводит
именно этот путь.
Пропущенная маска после умножения наблюдаема только там, где широкое
значение попадает в сдвиг. У сложения, XOR и маскирования младшие 48 бит
результата не зависят от старших, поэтому забытая маска перед ними ничего
не меняет. В F критично лишь Y ⋙ 7 и Y ⋘ 3. Мутант, не маскирующий
Y перед сложением, проходит все тестовые векторы.
Лавина на раунде 1 измерена как 13.54% против 14.08% в спецификации.
Теория даёт ≈13.5%: переворот бита L меняет ровно 1 из 96 бит, переворот
бита R — около 25, среднее (1 + 25) / 2 / 96 ≈ 13.5%. Значение 14.08%,
видимо, получено на малой выборке. Расхождение косметическое.
MSVC /O2, x86-64:
| Операция | Циклов на байт | Скорость |
|---|---|---|
| CTR | 24.8 | 111 МиБ/с |
| Блочное шифрование (пакетно) | 18.8 | 147 МиБ/с |
| Блочная расшифровка (пакетно) | 18.0 | 155 МиБ/с |
| Инициализация ключа | 5037 циклов | 576 000 ключей/с |
Для сравнения: AES-NI даёт около 1 цикла на байт. Разрыв ожидаем — 48-битное слово не машинное и требует явного маскирования после каждой операции.
build\bench.exe 64import dedalyan as D
C = D.encrypt_block(0x0123456789abcdef01234567,
int.from_bytes(bytes(range(32)), "big"))
P = D.decrypt_block(C, int.from_bytes(bytes(range(32)), "big"))
ctx = D.Dedalyan(bytes(range(32)))
ct = ctx.ctr(b"payload", counter=0)
D.encrypt_block(P, K, rounds=5) # Dedalyan-96/256-r5
D.encrypt_block_trace(P, K) # пораундовая трассировкаБыстрый путь через C:
from dedalyan_c import backend
ctx = backend.new_ctx(bytes(range(32)))
backend.encrypt_block(ctx, 0x0123456789abcdef01234567)#include "dedalyan.h"
dedalyan_ctx ctx;
uint8_t key[32], in[12], out[12];
dedalyan_key_setup(&ctx, key);
dedalyan_encrypt_bytes(&ctx, in, out);
dedalyan_ctr(&ctx, counter, plaintext, ciphertext, len, 16);
dedalyan_ctx_wipe(&ctx);Урезанные версии — через dedalyan_encrypt_r(&ctx, block, rounds).
python cli.py genkeypython cli.py encrypt --key-hex <64 hex> --in file.txt --out file.encpython cli.py decrypt --key-hex <64 hex> --in file.enc --out file.txtПо умолчанию шифрование генерирует случайный 96-битный счётчик и пишет его в первые 12 байт файла; расшифровка читает его оттуда. Это не украшение: CTR катастрофически ломается при повторном использовании пары (ключ, счётчик) — XOR двух шифротекстов даёт XOR открытых текстов.
Остальные команды: keystream, block, schedule, labyrinth, trace,
selftest.
python demo.pyПоказывает цепочку целиком: пароль, соль и параметры Argon2id, оба выведенных ключа, все 16 подключей Dedalyan, nonce, шифротекст, тег аутентичности, конверт в base64 и результат расшифровки. Отдельными шагами демонстрирует, что происходит при неверном пароле и при перевороте одного бита шифротекста.
Конверт версии 2:
version (1) │ t (1) │ lanes (1) │ mem_kib (4) │ salt (16) │ nonce (8) │ ciphertext │ tag (16)
Накладные расходы 47 байт. Секретен только пароль; всё остальное хранится открыто. Параметры Argon2 лежат внутри конверта, поэтому старые конверты читаются и после смены настроек по умолчанию.
Обратный ход:
python demo.py --decrypt <base64>Почему Argon2id, а не «просто хеш»: пароль — это не ключ, у него мало
энтропии, и SHA-256 от него перебирается на GPU со скоростью порядка 10^10
попыток в секунду. Argon2id намеренно требует много памяти (по умолчанию
64 МиБ — второй рекомендованный набор RFC 9106), из-за чего GPU и ASIC
теряют преимущество перед обычным процессором. Настраивается флагами
--time-cost, --memory-kib, --lanes.
Почему MAC: режим CTR сам по себе не защищает целостность. Шифротекст — это открытый текст, сложенный с гаммой по XOR, поэтому переворот бита в шифротексте переворачивает ровно тот же бит открытого текста, и расшифровка об этом не сообщит. Схема encrypt-then-MAC закрывает это: тег считается от заголовка и шифротекста, проверяется до расшифровки, и любое изменение конверта отвергается. Argon2 выдаёт 64 байта, которые режутся на ключ шифра и ключ MAC — один ключ нельзя отдавать двум примитивам сразу.
Argon2id и HMAC-SHA256 здесь настоящие и пригодны для продакшена. Шифр — нет.
python demo.py --serveПоднимает страницу на http://127.0.0.1:8765/ и открывает браузер
(--port N, --no-browser). Четыре вкладки:
| Вкладка | Что делает |
|---|---|
| Текст (GCM) | Сырой GCM-96 с пользовательским AAD. Показывает H = E_K(0), nonce, тег и карту конверта по сегментам |
| Файлы | Кадрированный формат: перетаскивание, шифрование и расшифровка, скачивание результата, число кадров и накладные расходы |
| Демонстрация | Цепочка Argon2id → два ключа → CTR → encrypt-then-MAC из demo.py |
| Шифр | Обозреватель: подключи, таблицы лабиринта с подсветкой неподвижных точек, пораундовая трассировка в формате раздела 8.5 |
В любом режиме ключ задаётся паролем (Argon2id) либо готовым 256-битным ключом, который генерируется прямо в браузере. Кнопка «подделать один бит» показывает, как схема отвергает изменённый конверт.
Разделение вкладок «Текст» и «Файлы» не косметическое: текст запечатывается одним вызовом GCM, поэтому видно всю схему целиком, а файл режется на кадры, потому что один тег на весь файл проверялся бы только после дочитывания до конца.
Реализация — на стандартной библиотеке, без веб-фреймворков. Страница самодостаточна: ни одного внешнего запроса, весь CSS и JS встроены. Это не эстетика, а требование — инструмент, показывающий пароли и ключи, не должен ходить в чужие CDN.
Защита локального сервера. Слушать только 127.0.0.1 недостаточно: любая
открытая в браузере страница может отправить запрос на localhost. Поэтому
дополнительно проверяется три вещи.
| Атака | Проверка | Результат |
|---|---|---|
| CSRF с чужого сайта | Origin обязан совпадать с нашим |
403 |
| DNS rebinding | Host обязан быть 127.0.0.1/localhost с нашим портом |
403 |
| CSRF обычной HTML-формой | POST только с Content-Type: application/json |
415 |
| Исчерпание памяти | тело до 4 МиБ, текст до 256 КиБ, память Argon2 до 2 ГиБ | 413 |
Журнал запросов отключён намеренно: в телах запросов ходят пароли. Проверено, что порт недоступен с сетевого адреса машины.
python cli.py seal --in secret.pdf --out secret.dedpython cli.py open --in secret.ded --out secret.pdfБез --key-hex спрашивает пароль (Argon2id, при шифровании — с повтором).
Ключ можно задать явно: --key-hex, --key-file, --password.
GCM-96 — это не NIST SP 800-38D. Настоящий GCM определён для 128-битного блока: GHASH живёт в GF(2^128), IV 96 бит, счётчик 32. У Dedalyan блок 96 бит, поэтому здесь та же схема адаптирована на GF(2^96) по неприводимому многочлену
f(x) = x^96 + x^10 + x^9 + x^6 + 1
Неприводимость проверена вычислением (тест Рабина), а сам тест — на эталонах, включая многочлены AES и GCM. Триномов степени 96 над GF(2) не существует, взят пентаном минимального веса. Соглашение о битах оставлено как в GCM: старший бит блока — коэффициент при x^0, поэтому умножение на x есть сдвиг вправо.
Конструкция проверена независимо на четырёх уровнях: арифметика поля сверена
с реализацией в естественном порядке бит, выполняются все аксиомы поля, а
равенство a^(2^96−1) = 1 подтверждает неприводимость повторно. Умножение
реализовано трижды — таблично в C, побитово в C и побитово в Python — и все
три совпадают. Вырожденный случай схемы сходится с опубликованными векторами
спецификации: H = E_K(0) при нулевом ключе равно TV1, а тег пустого
сообщения равен TV2.
Один тег на весь файл не годится: проверить его можно лишь дочитав файл до конца, а значит либо держать всё в памяти, либо отдавать расшифрованное до проверки — ровно то, от чего аутентификация должна защищать. Поэтому файл режется на кадры по 256 КиБ, каждый запечатывается отдельно, и кадр отдаётся только после успешной проверки его тега.
Кадрирование само порождает три атаки, и каждая закрыта явно:
| Атака | Защита |
|---|---|
| перестановка кадров | индекс кадра входит в AAD |
| усечение файла | флаг последнего кадра в AAD |
| склейка из разных файлов | заголовок (соль + идентификатор) в AAD каждого кадра |
Nonce кадра — его индекс; это безопасно, потому что ключ уникален для файла (Argon2id от случайной 16-байтовой соли), и пара (ключ, nonce) не повторяется.
Накладные расходы: 44 байта заголовка плюс 12 на кадр — 0.005% при кадре 256 КиБ.
| Операция | Скорость |
|---|---|
| CTR без аутентификации | 103 МиБ/с |
| GCM-96 seal / open | 26.7 МиБ/с |
| файловый формат целиком | 24.5 МиБ/с |
| чистый Python (без C) | 0.26 МиБ/с |
Аутентификация стоит 3.9x. Причина не в шифре, а в GHASH: у AES-GCM
умножение в поле выполняет аппаратная инструкция PCLMULQDQ, а здесь оно
программное. Таблица взята 4-битная (16 элементов, 256 байт) намеренно:
8-битная вдвое быстрее, но её 4 КБ дают заметный кэш-таймингововый канал, а
256 байт умещаются в несколько кэш-линий.
Ограничения, вытекающие из размеров: одно сообщение GCM не длиннее ~48 ГиБ (счётчик блоков 32-битный), граница дней рождения — 2^48 блоков на ключ. Файловый формат обходит первое ограничение кадрами.
python attacks/randomness.py --export stream.bin --export-mb 512dieharder -a -g 201 -f stream.binmake && make testbuild.ps1 тоже работает под pwsh на любой платформе, но make привычнее.
Имя разделяемой библиотеки зависит от системы (dedalyan.dll,
libdedalyan.so, libdedalyan.dylib) — dedalyan_c.py ищет нужное сам.
Apache License 2.0, см. LICENSE и NOTICE.
Публичный ключ Ed25519 для проверки подлинности релизов и документов —
в SIGNING.md и signing/publickey.pem.
Подпись удостоверяет авторство, а не качество: шифр остаётся непроверенным независимыми криптоаналитиками независимо от того, кем подписан репозиторий.
Найденная атака, расхождение со спецификацией или ошибка в реализации — самый ценный вклад. Что помогает разобраться быстрее:
- Атака или различитель. Приложите воспроизводимый скрипт и укажите
зерно. Все генераторы в наборе детерминированы, поэтому
--seed Nвоспроизводит прогон точно. - Отличайте кандидата от находки. Превышение порога Бонферрони само по
себе ничего не значит; прогоните масштабный тест (перемер на
n,10n,100n). Пример, как это уже спасло от ложного вывода, — в docs/analysis-2026-07-25. - Ошибка в реализации. Начните с
python tests/run_all.py. Если падаетtest_vectors— расхождение со спецификацией; еслиtest_cross_c— расходятся Python и C. - Новый тест.
tests/test_pitfalls.pyпоказывает подход: к каждой проверке пишется заведомо неправильный вариант, и утверждается, что тест его ловит.
Набор выполняет ограниченные поиски. Отсутствие находки означает отсутствие дешёвой атаки в проверенном пространстве, и ничего больше.
Не покрыто:
- алгебраические атаки с решателями SAT/Gröbner (нужны внешние решатели);
- автоматический поиск характеристик через MILP или SMT — стандартный современный инструмент для ARX, требует внешнего решателя;
- атаки по побочным каналам (время, питание, кэш);
- криптоанализ с квантовым доступом;
- полный перебор пространств масок и разностей — оно имеет размер 2^192, восхождение по холму покрывает пренебрежимую долю.
Полноценный анализ требует лет работы специалистов. Именно поэтому в продакшене используют AES и ChaCha20.