Заметка пятая. Разбор задания Crypto Backdoor с Google CTF 2017
Это заметка о том, как мы с Костей Плотниковым решали задание на ассиметричную криптографию с Google CTF, который прошёл неделю назад. Она кишит кодом на питоне, несложными терминами из теории групп и математическими выкладками. Смело пропускайте, если боитесь чего-нибудь из этого.
Формулировка как всегда бесконечно расплывчата:
This public-key cryptosystem has a flaw. Can you exploit it? crypto_backdoor.py
Костя попробовал меня ввести в курс дела, когда я присоединился к нему. Он рассказал, что в скрипте реализовано что-то вроде эллиптической криптографии. Работает она так: сначала на плоскости рисуется специальная кривая, а затем для каждой пары точек на ней определяется операция сложения. Сумма двух точек с кривой тоже лежит на кривой за исключением случая, когда получается особое значение — ноль. После этого естественным образом определяется умножение точки на натуральное число.
В эллиптической криптографии Боб, желающий получать зашифрованные сообщения от Алисы, выбирает секретное число $N$ и точку на эллиптической кривой $g$. Последняя называется генератором и сообщается всем желающим, а вот секретное число становится закрытым ключом Боба. Открытым ключом в этом случае называется произведение генератора и закрытого ключа; $B = Ng$.
Собственно, на мысли об эллиптических кривых Костю натолкнули наличие в скрипте точки-генератора и двух открытых ключей:
# Modulus
p = 606341371901192354470259703076328716992246317693812238045286463
# g is the generator point.
g = (160057538006753370699321703048317480466874572114764155861735009, 255466303302648575056527135374882065819706963269525464635673824)
# Alice's public key A:
A = (460868776123995205521652669050817772789692922946697572502806062, 263320455545743566732526866838203345604600592515673506653173727)
# Bob's public key B:
B = (270400597838364567126384881699673470955074338456296574231734133, 526337866156590745463188427547342121612334530789375115287956485)
Модуль $p$ здесь тоже неслучаен — в эллиптической криптографии как раз рассматриваются кривые над полем $\mathbb{Z}_p$. Позже, правда, мы пришли к выводу, что эллиптическая криптография тут совсем ни при чём... Но сначала мы внимательно изучили операцию сложения двух точек:
def add(a, b, p):
if a == -1:
return b
if b == -1:
return a
x1, y1 = a
x2, y2 = b
x3 = ((x1*x2 - x1*y2 - x2*y1 + 2*y1*y2) * modinv(x1 + x2 - y1 - y2 - 1, p)) % p
y3 = ((y1*y2) * modinv(x1 + x2 - y1 - y2 - 1, p)) % p
return (x3, y3)
Нейтральный элемент (он же ноль) обозначен как $-1$, поэтому первые четыре строки функции очевидны. Функция $modinv(x, p)$ находит обратный элемент к $x$ в кольце $\mathbb{Z}_p$ с помощью расширенного алгоритма Евклида. Значит, сумма точек $(x_1, y_1)$ и $(x_2, y_2)$ это точка
$$\left(\frac{x_1x_2 – x_1y_2 – x_2y_1 + 2y_1y_2}{x_1+x_2-y_1-y_2-1}, \frac{y_1y_2}{x_1+x_2-y_1-y_2-1}\right)$$
Дробь означает целочисленное деление в кольце $\mathbb{Z}_p$.
Упрощаем сложение
Математик во мне захотел разобраться с этой операцией. Больше всего смущали одинаковые знаменатели и сложный числитель первой координаты. Стало проще, когда я переписал числитель как $(x_1-y_1)(x_2-y_2)+y_1y_2$, а знаменатель как $(x_1-y_1) + (x_2-y_2)$. Давайте перейдём из системы координат $(x, y)$ в систему $(t, y)$, где $t = x-y$. Обратный переход тоже очень простой: $x = t + y$. Если переписать операцию сложения в новой системе координат, то получится
$$(t_1, y_1) + (t_2, y_2) = \left(\frac{t_1t_2+y_1y_2}{t_1+t_2-1} – \frac{y_1y_2}{t_1+t_2-1}, \frac{y_1y_2}{t_1+t_2-1}\right) = \left(\frac{t_1t_2}{t_1+t_2-1}, \frac{y_1y_2}{t_1+t_2-1}\right)$$
Получившаяся формула в некотором смысле даже симметрична. Это дало нам оптимизм на несколько следующих часов.
Мастер-секрет
Однако в скрипте само по себе сложение нигде не используются. Зато используется умножение точки на число:
from secret_data import aliceSecret, bobSecret, flag
assert A == mul(aliceSecret, g, p)
assert B == mul(bobSecret, g, p)
aliceMS = mul(aliceSecret, B, p)
bobMS = mul(bobSecret, A, p)
assert aliceMS == bobMS
masterSecret = aliceMS[0]*aliceMS[1]
Настало время разобраться, что вообще происходит в скрипте и что нужно найти.
$aliceSecret$ — это натуральное число, закрытый ключ Алисы, он умножается на известный нам генератор, и в результате получается известный нам открытый ключ Алисы. Аналогичное проделывается с ключами Боба. После этого открытый ключ Боба умножается на закрытый ключ Алисы, получая точку $aliceMS = aliceSecret \times bobSecret \times g$. Симметричная операция вычисляет $bobMS = bobSecret \times aliceSecret \times g$. Очевидно, что эти две точки должны совпасть, что и проверяется последним assert’ом. Мастер-секретом объявляется произведение координат этой общей точки. Этот мастер-секрет и надо найти, так как в конце скрипта он ксорится с флагом:
def encrypt(message, key):
return message ^ key
length = len(flag)
encrypted_message = encrypt(I(flag), masterSecret)
print "length = %d, encrypted_message = %d" % (length, encrypted_message)
# length = 31, encrypted_message = 137737300119926924583874978524079282469973134128061924568175107915062758827931077214500356470551826348226759580545095568667325
Умножение в новых координатах
Раз для подсчёта мастер-секрета используется умножение, то вернёмся к нашим формулам и выясним, как точка в новых координатах $(t, y)$ умножается на число $k$. Переберём несколько маленьких коэффициентов:
$$2(t, y) = (t, y) + (t, y) = \left(\frac{t^2}{2t-1}, \frac{y^2}{2t-1}\right)$$
$$3(t, y) = 2(t, y) + (t, y) = \left(\frac{t^2}{2t-1}, \frac{y^2}{2t-1}\right) + (t, y) =
\left(\frac{\frac{t^3}{2t-1}}{\frac{t^2}{2t-1} + t – 1}, \frac{{\frac{y^3}{2t-1}}}{\frac{t^2}{2t-1} + t – 1}\right) =
\left(\frac{t^3}{3t^2-3t+1}, \frac{y^3}{3t^2-3t+1}\right)$$
$$4(t, y) = 2(t, y) + 2(t, y) = \left(\frac{t^2}{2t-1}, \frac{y^2}{2t-1}\right) + \left(\frac{t^2}{2t-1}, \frac{y^2}{2t-1}\right) =
\left(\frac{\frac{t^4}{(2t-1)^2}}{\frac{2t^2}{2t-1} – 1}, \frac{\frac{y^4}{(2t-1)^2}}{\frac{2t^2}{2t-1} – 1}\right) =
\left(\frac{\frac{t^4}{2t-1}}{2t^2-2t+1}, \frac{\frac{y^4}{2t-1}}{2t^2-2t+1}\right) =
\left(\frac{t^4}{4t^3-6t^2+4t-1}, \frac{y^4}{4t^3-6t^2+4t-1}\right) $$
Умножать дальше становилось всё сложнее, поэтому пора найти закономерность. В числителях всё время получается $t^k$ и $y^k$, а в знаменателях видны биномиальные коэффициенты. Смотрите, $4t^3-6t^2+4t-1$ похоже на $t^4 + (4t^3-6t^2+4t-1)$, что в свою очередь равно $(t + 1)^4$. Так у нас появилась гипотеза, что знаменатель равен $(t+1)^k – t^k$.
Итоговую формулу
$$k \times (t, y) = \left(\frac{t^k}{(t+1)^k – t^k}, \frac{y^k}{(t+1)^k – t^k}\right)$$
теперь можно доказать по индукции по $k$. Делать я этого, конечно же, не буду.
Решение
Задача будет решена, если мы узнаем закрытый ключ Алисы или Боба. В случае с Бобом нам достаточно найти такое $k \in \mathbb{Z}_p$, что $kg = B$. В наших новых координатах и формулах это выглядит так:
$$\left(\frac{g_t^k}{(g_t+1)^k – g_t^k}, \frac{g_y^k}{(g_t+1)^k – g_t^k}\right) = (B_t, B_y)$$
где $(g_t, g_y)$ — координаты генератора $g$ в системе $(t, y)$, а $(B_t, B_y)$ — аналогичные координаты точки $B$. Взяв из этого равенства первую координату, получаем:
$$ \frac{g_t^k}{(g_t+1)^k – g_t^k} = B_t$$
$$g_t^k = B_t ((g_t+1)^k – g_t^k)$$
$$g_t^k (1 + B_t) = B_t (g_t+1)^k$$
$$\left(\frac{g_t}{g_t+1}\right)^k = \frac{B_t}{1 + B_t}$$
Чтобы найти искомое $k$, достаточно вычислить логарифм в кольце $\mathbb{Z}_p$, то есть решить задачу дискретного локарифмирования. Здесь мы с Костей приуныли, потому что поняли, что зашли в тупик. Дискретное логарифмирование — задача, которую нельзя решить за нормальное время.
Только через час Костя догадался проверить данный нам модуль $p$ на простоту. Совершенно неожиданно он оказался составным:
$$p = 961236149 \times 951236179 \times 941236273 \times 911236121 \times 931235651 \times 921236161 \times 901236131$$
Наверняка все знают, как посчитать дискретный логарифм по непростому основанию. А мы не знаем. Так что мы взяли бумажку и стали придумывать алгоритм.
Когда-то Асанов рассказывал байку, что студенты УПИ обычно знают способ решить задачу, а студенты матмеха не знают. Зато студенты матмеха могут придумать решение. Кажется, теперь я понял эту байку.
Дискретный логарифм по составному основанию
Пусть мы нашли дискретный логарифм от $b$ по основанию $a$ в кольцах $\mathbb{Z}_p$ и $\mathbb{Z}_q$. То есть мы нашли такие $n$ и $m$, что
$$a^n \equiv b \pmod{p}$$
$$a^m \equiv b \pmod{q}$$
Теперь мы хотим решить задачу в кольце $\mathbb{Z}_{pq}$, то есть найти такое $k$, что $a^k \equiv b \pmod{pq}$. Порядком числа $a$ в кольце называется такое минимальное натуральное $z$, что $a^z = 1$. Пусть число $a$ имеет порядки $u$ и $v$ в кольцах $\mathbb{Z}_p$ и $\mathbb{Z}_q$ соответственно. Тогда $b \equiv a^n \equiv a^{n + u} \equiv a^{n+2u} \equiv a^{n+3u} \equiv \dots \pmod{p}$ и $b \equiv a^m \equiv a^{m + v} \equiv a^{m+2v} \equiv a^{m+3v} \equiv \dots \pmod{q}$. Если мы найдём число, которое можно выразить и как $n + ?u$, и как $m + ?v$, то оно станет ответом задачи дискретного логарифмирования по модулю $pq$.
Ну а найти такое число — дело техники и расширенного алгоритма Евклида. Если $n + ?u = m + ?v$, то $?u – ?v = m – n$. Если $m-n$ не делится на $НОД(u, v)$, то решения нет, иначе расширенный алгоритм Евклида решит это линейное диофантово уравнение, и всё станет хорошо.
Так как данный в задании модуль $p$ мы разложили на простые множители $p_1 \times p_2 \times \dots \times p_7$, которые к тому же получились не очень большими, то мы можем решить задачу дискретного логарифмирования в каждом из $\mathbb{Z}_{p_i}$. Соединить семь чисел в один большой ответ поможет описанный только что алгоритм.
Как решить дискретный логарифм для $p_i$
Простые делители модуля $p$ были не очень большими, но и не настолько маленькими, чтобы дискретный логарифм можно было найти простым перебором. К счастью, есть алгоритм Baby Steps Giant Steps, который позволяет сделать это намного быстрее — за $O(\sqrt{n})$. Его мы и реализовали. Да, мы думали о том, что Baby Steps Giant Steps уже реализован в куче библиотек, но лично меня это только раззадоривает — так интересно ведь всё сделать самому.
Как найти порядок элемента в кольце
На часах было уже около часа ночи, а мне предстояло запрограммировать на питоне поиск порядка элемента в кольце $\mathbb{Z}_p$. Теорема Эйлера шептала, что $x^{\phi(p)} \equiv 1 \pmod{p}$, но $\phi(p)$ могло оказаться не минимальным числом с таким свойством. Значит, искомый порядок — это делитель $\phi(p)$. Но делителей слишком много, чтобы перебирать их все. Так что пришла пора узнать более быстрый способ находить порядок элемента. На помощь пришла пдфка из интернета, в которой есть чудесный алгоритм 4.79, я реализовал его и научился быстро находить порядок элемента в кольце, зная разложение на простые множители у $\phi(p)$.
Заключение
Применив всю магию сверху, мы нашли закрытый ключ Боба $bobSecret$. Умножив его на открытый ключ Алисы, мы нашли мастер-секрет, который и выдал нам флаг:
Master secret found: (254828745614253797280016043417264027645246572307317271091197847, 540509273347153402828726537667691800163306365090607497812000946)
Flag: CTF{Anyone-can-make-bad-crypto}
Если вкратце повторить всё наше решение, то оно перестаёт казаться страшным и длинным:
- переходим в систему координат $(t, y)$,
- вычисляем $a = \frac{g_t}{g_t+1}$ и $b = \frac{B_t}{1 + B_t}$,
- решаем задачу дискретного логарифма $a^{?} = b$ в кольце $\mathbb{Z}_p$, зная разложение $p$ на простые множители,
- умножаем полученный ответ на открытый ключ Алисы $A$, это и есть мастер-секрет,
- переходим обратно в координаты $(x, y)$, перемножаем координаты мастер-секрета, расшифровываем флаг.