Содержание статьи
Шаг 0. Данные и формат
Для этой статьи были использованы данные с самых первых соревнований DPA contest, которые теоретически до сих пор можно скачать с сайта. К сожалению, доступ к этим данным есть не всегда, поэтому я сохранил данные для одного ключа и разместил их у себя на Яндекс.Диске.
Бинарный файл, который доступен на Яндекс.Диске, называется DES_DPA_1.
. Он состоит из заголовка и данных. В заголовке указана следующая информация (весь заголовок занимает 21 байт):
- Первые пять байт — магическое слово
HWSec
, которое я использую для индикации файлов с данными. - Следующие четыре байта типа
unsigned
представляют собой количество шифрований. В рассматриваемом случае было выполнено 2000 шифрований и для каждого были записаны исходный текст, шифротекст и измерена кривая напряжения — все эти значения представлены в блоке данных.int - Следующие четыре байта типа
unsigned
представляют собой количество точек в каждой кривой напряжения. В рассматриваемом случае это число равно 20 003.int - Затем следуют восемь байт ключа алгоритма DES. Все шифрования в указанном файле были сделаны с ключом
0x656A78656A7865
.
За заголовком следуют блоки данных для каждого шифрования, состоящие из исходного текста, шифротекста и кривой напряжения. Количество блоков для данного файла, указанное в заголовке, равняется 2000. Формат этих данных представлен в следующем виде:
- Восемь байт исходного текста.
- Восемь байт шифротекста.
- Сама кривая напряжения, представленная в виде массива точек типа
float
. Количество точек, указанное в заголовке, равно 20 003.
Таким образом, 2000 кривых напряжения, в каждой из которой 20 003 точки в формате float
, занимают 152 Мб. В более сложных системах приходится снимать несколько миллионов кривых, поэтому ты можешь представить объем получаемых данных.
Шаг 1. Извлекаем данные
На первом шаге давай просто попытаемся извлечь данные и построить одну кривую напряжения. Я предлагаю это сделать на Python — он более гибкий, хотя обычно на воркшопах я даю код на С++, поскольку он быстрее. Ты можешь также воспользоваться любым другим языком или системой Matlab (Octave), R, Cobol или Brainfuck 🙂 на свое усмотрение.
Нам потребуются дополнительные библиотеки numpy
, matplotlib
и scipy
, которые необходимо установить отдельно (думаю, ты справишься сам без инструкций по установке библиотек). Они нужны для создания графиков и расчета данных.
Сам код (и результат считывания заголовка файла) приведен на рис. 1. Файл DES_DPA_1.
был сохранен в директории ../
, поэтому в случае другого расположения файла измени путь к нему. Как видно из строки в консоли, заголовок был считан корректно.
Осталось проверить, были ли правильно считаны кривые напряжения, — это определяется простым построением кривых для первого и последнего шифрований. На рис. 2 зеленым цветом показана кривая для первого шифрования и синим цветом — для последнего шифрования. Так как две кривые очень похожи, то данные считаны корректно, в противном случае возможны сдвиги по оси Х или другие изменения в графиках.
На рис. 2 каждый большой всплеск напряжения происходит в момент восходящего сигнала осциллятора. Маленькие всплески между большими происходят в момент нисходящего сигнала осциллятора. В зависимости от того, как спроектирована система, регистры могут переключаться как при восходящем, так и при нисходящем сигнале с осциллятора. Я насчитал 8 + 16 + 8 всплесков: первые 8 тактов используются для побайтовой передачи исходного текста в криптографический акселератор, 16 тактов используются для вычисления DES (один такт — один раунд), в оставшиеся 8 тактов шифротекст побайтово передается на выход. Эту информацию можно было прочитать в условиях конкурса, но также ее можно проверить с помощью методов второстепенных каналов, как это показано на следующем шаге.
Шаг 2. Проверка данных
Чтобы проверить связь между передаваемыми данными и потребленной энергией, мы объединим два метода, описанные в первых статьях. Во второй статье говорилось, что переключение бита потребляет энергию. Чем больше битов переключено, тем больше энергии потребляется. В первой статье был описан коэффициент корреляции Пирсона, который позволяет сравнить модель и реальные данные. Группируя все вместе, мы получим инструментарий для проверки нашей модели:
- На первом шаге посчитаем количество битов в последнем байте исходного текста. Получится вектор из 2000 элементов (так как у нас 2000 исходных текстов).
- Затем возьмем точку
i
(для первого разаi
) из каждой кривой напряжения — получится вектор из 2000 элементов (мы берем только одну точку из каждой кривой напряжения).= 1 - Посчитаем коэффициент корреляции Пирсона между моделью и вектором напряжения и сохраним его.
- Перейдем к следующей точке
i
и вернемся на шаг 2.
Шаги 2–4 будут выполнены 20 003 раза, то есть для всех моментов времени, когда происходило снятие напряжения. Код этого алгоритма приведен на рис. 3. Часть кода взята из Сети: функция popcount
возвращает количество битов каждого элемента вектора, передаваемого в качестве аргумента; а функция CheckDataTransmission
строит корреляцию нашей модели и реальных значений напряжения.
На рис. 4 приведен график для указанного кода, где красным цветом показано значение корреляции, а синим цветом — одна кривая напряжения. Для сравнения на рис. 5 показаны точно такие же графики, но уже для 7-го байта исходного текста, а на рис. 6 — для 6-го байта. На этих графиках мы видим некоторые всплески, обусловлены они тем, что наша модель хорошо согласуется с реальными значениями напряжения. Мы также видим, что пик корреляции смещается на один такт для каждого последующего байта исходного текста, — это лишний раз показывает, что данные передаются побайтово. Ты можешь построить точно такие же графики для байтов шифротекста и посмотреть, что с ними случится.
Шаг проверки данных важен по двум причинам. Во‑первых, за довольно короткое время мы смогли убедиться, что в данной реализации есть связь между бинарными данными и потребленной энергией. Во‑вторых, мы смогли посмотреть, когда передаются данные, то есть примерно определить момент начала работы алгоритма (а он не может начаться раньше, чем получен весь исходный текст). Определение времени работы алгоритма позволяет сократить окно исследуемых кривых напряжения, и это ускоряет время обработки.
Шаг 3. Разбираемся с реализацией алгоритма
Алгоритм DES, приведенный на рис. 7, должен быть тебе уже известен (если это не так, то поищи стандарт в Сети). Аппаратная реализация алгоритма DES, которую мы атакуем, приведена на рис. 8. Эта схема требует пояснения. Вначале опишем блоки:
- рег L — 32-битный регистр, который сохраняет левую часть состояния алгоритма после каждого раунда.
- рег R — 32-битный регистр, который сохраняет правую часть состояния алгоритма после каждого раунда.
- рег CD — 48-битный регистр, который сохраняет раундовый ключ.
- E, P, PC2 — таблицы перестановки битов, а на деле просто соединения.
- S — таблица замещения, выполненная полностью на логических блоках без памяти (в противном случае должен быть регистр, который бы хранил значения).
- LS2, LS, RS, RS2 — операции сдвига, которые логично выполнять в виде соединений.
Таким образом, мы видим, что на каждом раунде у нас происходит вычисление значений Li
и Ri
, которые перезаписывают предыдущие значения Li-1
и Ri-1
. Помимо этого, раундовый ключ тоже вычисляется заново и перезаписывает предыдущее значение регистра. Все эти перезаписи можно использовать для улучшения атаки, но мы сосредоточимся лишь на одном регистре L
, так как его проще всего моделировать.
Шаг 4. Разбираемся с моделью
Как и раньше, искать раундовый ключ алгоритма DES мы будем частями по шесть бит. Меньше не получится, так как мы не сможем построить достоверные модели из‑за таблицы замещения Sbox
, которая заменяет шесть входных бит четырьмя выходными. Так как это незащищенная реализация алгоритма DES, то значения Li
и Ri
перезаписывают предыдущие значения регистров. Таким образом, значение L1
перезапишется значением L2
на втором такте работы алгоритма. Эта перезапись вызовет всплеск напряжения, который должен быть пропорционален количеству перезаписанных битов, то есть расстоянию Хэмминга между двумя значениями HD(
.
Битовое расстояние Хэмминга HD(
равно весу Хэмминга от суммы по модулю двух этих переменных HW(
, другими словами: HD(
. Имея в распоряжении исходные тексты, мы можем найти значение этого выражения по формуле на рис. 8. В этой формуле все параметры, кроме ключа K1
, могут быть найдены из исходного текста, поэтому мы легко можем смоделировать количество переключений битов в регистре L
, когда L1
перезаписывает L2
.
Теоретически мы можем рассчитать любую другую перезапись регистров, например L4
и L5
, но в этом случае формула будет содержать значение ключей от K1
до K4
и нам будет очень сложно искать ключ по частям. С другой стороны, можно атаковать последние раунды и воспользоваться шифротекстами, как мы делали это для алгоритма AES, — нужно лишь написать обратные операции InvSbox
, InvP
, InvE
.
Формула на рис. 8 абсолютно правильная, но ужасно неудобная, потому что мы собираемся моделировать шесть бит ключа, которые подаются на вход Sbox
, а выход собирать нам придется из разных мест, так как перестановка P
перемешивает все выходы. На мой взгляд, это ужасно неудобно, но есть элегантное решение проблемы. Побитовая перестановка P
не изменяет вес Хэмминга, поэтому если мы применим обратную перестановку InvP
, то вес Хэмминга тоже не поменяется. Эта формула приведена на рис. 9.
Такая формула гораздо удобнее, так как позволяет единожды посчитать операцию InvP(
, а затем для каждого значения ключа просто брать выход Sbox
и считать переключение битов (индексы над переменными означают, какие биты нужно взять, чтобы составить модель для первых шести бит ключа). Если это преобразование тебе не очень ясно, то просто воспользуйся формулой на рис. 8, но не забудь правильно «словить» биты после перестановки P
.
Очевидно, ты уже понял, как искать ключ. Просто перебирая все значения шести бит ключа, вначале рассчитываешь модель потребления энергии. Затем считаешь корреляцию между моделью и реальными данными, и лучшее значение корреляции даст тебе верный ключ. С кодом нужно будет чуть‑чуть повозиться.
Шаг 5. Разбираемся с кодом
Чтобы упростить задачу, я воспользовался реализацией DES, выполненной Жуаном Франко (Joao Franco). Вначале мы рассчитаем все значения, которые не изменяются со сменой ключа, то есть значения E(
и InvP(
, обозначенные в коде (рис. 10) в качестве ER0
и InvL0R0
. Для расчета использовалась реализация Жуана Франко и дополнительная таблица обратной перестановки InvPFtable
. После вычисления промежуточных значений мы можем приступить к построению самих моделей, как это сделано в коде на рис. 11. После того как будет выполнен весь код (а это займет некоторое время), у тебя должен получиться график, приведенный на рис. 12. Среди всех графиков здесь есть один, у которого корреляция значительно отличается в момент 10-го такта с начала работы. Это говорит о том, что модель для одного из ключей хорошо согласуется с реальными данными и, скорее всего, ключ правильный. На это также косвенно указывает момент корреляции — если бы пик был в другой такт, то это бы противоречило нашей модели, которая опирается на перезапись регистра после второго раунда.
Немного изменяя код ,ты самостоятельно можешь построить корреляции для оставшихся частей ключа. Также ты можешь попытаться проатаковать последний раунд, для этого создай модель, когда R15
перезаписывается значением R16
. Если будут вопросы, то пиши на указанный в начале адрес.
Шаг 6. Подведем итог
В этот раз статья сфокусирована на том, чтобы позволить тебе самостоятельно «пощупать» реально измеренные данные. Если представленного файла тебе мало — на сайте DPA contest есть около 12 Гб различных данных, которые можно проанализировать. За три статьи мы разобрали основы атак по второстепенным каналам, и, начиная со следующей статьи, мы займемся атаками методом индуцированных сбоев. Затем, когда пройдет немного времени, мы вернемся к атакам по второстепенным каналам, но уже будем рассматривать сложные и еще более приближенные к реальности примеры. А пока у тебя есть возможность поиграться с данными и поискать дополнительную информацию в Сети или в статьях журнала «Хакер». Stay tuned!