как узнать aes key
Аппаратный CTF. Легкий способ узнать ключ шифрования, когда у тебя под рукой осциллограф и ноутбук
Содержание статьи
В 2016 году компания Riscure проводила конкурс среди энтузиастов информационной безопасности RHme2. Задачи на реверс бинарников, цифровых протоколов, криптографию и эксплуатацию уязвимостей решали на единой аппаратной платформе — это была крохотная плата Arduino Nano с микроконтроллером AVR. Позднее, на конференции RSA 2018 в Сан-Франциско, один из организаторов конкурса сетовал на то, что лишь немногие участники пробовали свои силы в аппаратных атаках по второстепенным каналам (АВК) и атаках методом индуцированных сбоев (АМИС).
Остальные же посчитали подобные способы чересчур сложными, дорогими и нереалистичными и отказались от дальнейших попыток. Чтобы показать, что и такой вектор атаки стоит рассматривать в качестве потенциальной угрозы, Рафаэль Буа Карпи (Rafael Boix Carpi) из Riscure в ходе выступления успешно решил одну из задач на оборудовании стоимостью менее 60 долларов (не считая ноутбука). Так что стартовый набор для начинающего исследователя аппаратных уязвимостей сегодня доступен как никогда раньше!
В исполнении инструктора из Riscure атака кажется настолько простой и изящной, что хочется повторить ее прямо здесь и сейчас. Что я практически сразу же и сделал, с некоторыми изменениями и дополнениями.
WARNING
Информация в статье предназначена исключительно для образовательных целей. Редакция и автор не несут ответственности за любое противоправное применение и причиненный ущерб.
Современное железо
В первую очередь было решено выбрать в качестве цели более актуальное железо. Arduino и AVR — это прекрасно, это наше наследие, и к нему нужно относиться с подобающим уважением, а не ломать направо и налево. Но сегодня бал правят микроконтроллеры на ядре ARM (Cortex-M). И вероятнее всего, именно их ты обнаружишь, распотрошив ближайшее умное устройство — беспроводную розетку, лампочку с Wi-Fi и прочие прелести сомнительной автоматизации.
На первый взгляд, бюджетный микроконтроллер STM32F091 (Cortex-M0) на плате Nucleo-64 идеально подходил на роль подопытного кролика. По сравнению с Arduino Nano это решение выглядит выигрышно буквально по всем параметрам: разрядность 32 бит (против восьми), тактовая частота 48 МГц (против шестнадцати) и напряжение питания всего 3,3 В против пяти. Что касается цены, нужно учесть, что оригинальные итальянские платы Arduino стоят как минимум столько же, если не дороже.
Такой выбор на самом деле осложняет атаку — не всякий бюджетный осциллограф сможет угнаться за микроконтроллером на подобных скоростях. Использование Hantek DSO6022 из презентации было бы заранее обречено на провал, достаточно только взглянуть на его характеристики.
К счастью, у меня на столе стоял осциллограф RTC1002 компании Rohde & Schwarz. Если верить рекламным материалам, это модель начального уровня, но это обманчивое впечатление. Да, такой осциллограф стоит как флагманский смартфон, но у него масса полезных функций, и он максимально удобен.
Суть атаки
Остальные условия изначального конкурса я решил не менять. Мы по-прежнему будем анализировать трассы энергопотребления нашего устройства в момент шифрования данных и пытаться угадать хранящийся в памяти пароль. Шифр — AES в режиме ECB с длиной ключа в 128 бит.
Конечно, на практике в реальном устройстве ты, скорее всего, обнаружишь AES в режиме CBC, но я так и не придумал, как органично вписать вектор инициализации в условия задачи. Сам по себе IV без ключа шифрования не представляет особой ценности для злоумышленника и нередко передается в открытом виде (примером тому могут служить протоколы SSL/TLS, смотри RFC 5246).
Порой встречается ошибочное мнение, будто у алгоритма AES переменный размер блока. Это не совсем так, размер блока всегда фиксирован и составляет ровно 128 бит. А вот у симметричных шифров семейства «Рэндал» (к которому и относится AES) размер блока действительно различается и составляет от 128 до 256 бит (с шагом 32 бит). При этом размер ключа и количество раундов шифрования могут варьироваться. Так, существуют AES-128 (десять раундов), AES-192 (двенадцать раундов) и AES-256 (четырнадцать раундов).
Сам по себе алгоритм AES считается одним из лучших, используется практически повсеместно, и на сегодняшний день для него не известно ни одной реальной уязвимости. Что же может пойти не так и есть ли у нас вообще шансы на успех?
Все дело в том, что операции любого процессора на самом низком уровне сводятся к переключению транзисторов. На это расходуется энергия, потребление которой можно фиксировать. Зная алгоритм шифрования, входные данные и замеряя параметры питания, мы можем попытаться угадать двоичное представление ключа (количество транзисторов в активном состоянии). И это будет однозначно гораздо лучше, чем простой перебор! (Да что угодно лучше, чем простой перебор, если уж откровенно.) 🙂
Конечно, на практике все окажется не так легко: в любой современной схеме транзисторов неприлично много, существуют нелинейные процессы, внешние наводки почти всегда неизбежны, а сама попытка провести измерения неизбежно вносит свой негативный вклад в точность полученных данных.
К счастью, на нашей стороне будет статистика — если удастся собрать достаточное количество информации, полезный сигнал будет преобладать над шумами и мы сможем узнать ключ. В нашем случае речь идет о пяти тысячах осциллограмм, каждая по тридцать тысяч точек. Я бы назвал это «большими данными», но специалисты, вероятно, будут смеяться, поэтому обойдемся без громких заявлений.
Таким образом, принцип атаки следующий: мы посылаем с компьютера на вход исследуемого устройства случайный набор чисел, устройство их шифрует и отправляет обратно, а осциллограф внимательно следит за расходом электроэнергии (счетчикам ЖКХ такая точность и не снилась). После этого по команде с компьютера мы сохраняем осциллограмму и набор данных (вход и выход) и повторяем процесс.
В теории все выглядит соблазнительно просто, не так ли? Сколько раз я так ошибался.
Подготовка платы
Начнем с отладочной платы: ее предстоит немножко испортить, но тут уж ничего не поделаешь. В первую очередь удалим с текстолита все лишнее, что может как-то потреблять или накапливать энергию, — нам нужен максимально чистый сигнал с самого микроконтроллера, а не с конденсаторов из его обвязки.
Отламываем программатор ST-Link — широкие прорези в самой плате намекают на то, что он прекрасно будет работать и отдельно от основной части. Если опасаешься повредить плату и выдрать целый кусок, попробуй сперва подпилить соединения обычной ножовкой, сразу должно быть проще.
Теперь наступил черед конденсаторов в цепи питания. Микроконтроллер требует 3,3 В, но это совместимая с Arduino плата, и она рассчитана на внешний источник питания 5 В, за это отвечает линейный DC/DC-преобразователь LD39050PU33R. То, что он линейный, — это хорошо, от импульсного были бы лишние помехи.
Конденсаторы С20 и С21 на входе можно оставить, а вот С18 и С19 явно лишние, смело бери паяльник в руки и выпаивай. Да их тут и не было! Также совершенно необязательны конденсаторы С23, С24, С27 и C28. У них какие-то смешные емкости — 100 нФ — можно даже не обращать внимания. Понравилось? Все, стой, остальные пусть живут!
Теперь о грустном. Мы перед этим отломали программатор, но он нам все еще нужен. Ты ведь его не выбросил, верно? Это было очень предусмотрительно! И дело не в том, что программатор нам понадобится для того, чтобы прошить микроконтроллер. Нет, это, оказывается, еще и переходник с USB — UART, и с его помощью мы будем общаться с компьютером.
Проблема в том, что дорожки RX/TX интерфейса UART располагались как раз на перемычках, которые мы до этого перерезали. На программаторе эти сигналы доступны на разъеме CN3, но на самой плате с F091 контакты D0/D1 разъема CN9 не подсоединены к микроконтроллеру. За это отвечают перемычки SB62 и SB63 на обратной стороне платы, так что соедини их парой капель припоя.
Кстати, ты обратил внимание, что на плате отсутствует кварцевый резонатор на месте X3? Есть «часовой» кварц на 32,768 кГц на месте X2, но он предназначен для RTC. Оказывается, ST-Link, кроме того что выполняет свои основные функции, еще и генерирует опорную частоту в 8 МГц для основного микроконтроллера! Это линия МСО на схеме (вывод PF0).
В отличие от интерфейса UART, этот сигнал не выведен ни на какие контакты, и без него F091 работать точно откажется. Мне не пришло в голову ничего лучше, чем купить в ближайшем магазине радиодеталей кварцевый генератор на 8 МГц. Его следует подключить к линии 5 В. При этом меандр будет с амплитудой 5 В вместо положенных 3,3 В, но это не страшно, так как вывод PF0 обозначен в даташите на микроконтроллер как FT, то есть совместимый с пятивольтовой логикой.
Остается только отпаять перемычку SB50 и запаять SB55, чтобы вывести ножку PF0 на 29-й контакт разъема CN7. Вполне вероятно, сейчас тебя занимает только одна мысль: зачем разработчики в ST придумали такую сложную схему? Оказывается, на такой печатной плате основана вся модельная линейка отладок Nucleo-64, а это почти двадцать разных версий. Конкретная версия отличается сочетанием всех этих крошечных перемычек.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Описание основ криптопреобразования AES
Доброго времени суток, Хабр! Примерно 3 месяца назад проходил собеседование frontend разработчиком и самый первый вопрос, который мне задали: “Что такое AES?” Ну как бы аморфное представление я все же имел о симметрично блочном шифровании AES, было дело даже использовал в одном из проектов для шифрования персональных данных. Но чтобы знать алгоритм rijndael и еще уметь его реализовать на javascript, для меня на тот момент казалось задачей трудновыполнимой. Но! Мне был брошен вызов и я его принял.
Go в подкат!
За основу я взял спецификацию FIPS197 AES от 26 ноября 2011г.
В IT сфере одни из самых известных алгоритмов шифрования являются:
В свою очередь симметричные алгоритмы делятся на два типа:
Потоковые шифруют посимвольно.
Преимуществами для асимметричных алгоритмов является его безопасное хранение ключей. Асимметричные — те алгоритмы, которые имеют два ключа:
Недостатками асимметричных алгоритмов, является его относительно медленное выполнение шифрования.
Rijndael — симметричный алгоритм блочного шифрования с возможностью изменять размеры блоков и секретных ключей от 128 до 256 бит с разностью в 32 бита. Использует линейно-подстановочные преобразования и состоит из 10, 12 или 14 раундов в зависимости от длины ключа.
AES — rijndael с ключом в 128 бит и блоком данных в 16 байт.
Предположительно вы знакомы с теорией следующих терминов:
Математические понятия в rijndael:
a(x)=a₇x⁷ +a₆x⁶ +a₅x⁵ +a₄x⁴ +a₃x³ +a₂x² +a₁x +a₀x;
Байт А, состоящий из битов a₇, a₆, a₅, a₄, a₃, a₂, a₁, a₀;
Байт B, состоящий из битов b₇, b₆, b₅, b₄, b₃, b₂, b₁, b₀;
Произведение 87 x 131 будет выглядеть следующим образом:
(x⁶ + x⁴ + x² + x + 1)(x⁷ + x + 1) = x¹³ + x¹¹ + x⁹ + x⁸ + x⁷ + x⁷ + x⁵ + x³ + x² + x + x⁶ + x⁴ + x² + x + 1.
Раскрытие скобок происходит на основе элементарных математических правил. Далее сложение происходит по правилам суммирования указанных выше (п.4):
x⁷ + x⁷ = x⁷(1 + 1) = x⁷(1 ^ 1) = x⁷0 = 0;
(x⁶ + x⁴ + x² + x + 1)(x⁷ + x + 1) = x¹³ + x¹¹ + x⁹ + x⁸ + x⁶ + x⁵ + x⁴ + x³ + 1.
После производится деление на конкретно заданную функцию m(x) = x⁸ + x⁴ + x³ + x + 1 (неприводимый полином), которая по правилам rijndael гарантирует корректный результат, то есть на выходе мы получим двоичный полином степени меньше 8, а значит сможем представить его байтом для дальнейшего шифрования. Деление производится по модулю. Деление можно производить, как деление многочленов столбиком. Остаток от деления является результатом:
(x⁶ + x⁴ + x² + x + 1)(x⁷ + x + 1)/(x⁸ + x⁴ + x³ + x + 1) =
(x¹³ + x¹¹ + x⁹ + x⁸ + x⁶ + x⁵ + x⁴ + x³ + 1)/(x⁸ + x⁴ + x³ + x + 1) = |Result| = x⁷ + x⁶ + 1
Все вычислительные операции алгоритма проводятся с использованием вышеописанных правил
Функции алгоритма:
Шифрование в AES происходит в несколько этапов:
Rcon — постоянный массив для генерации ключей путем XOR. Иначе говоря функция keyExpansion() циклично XOR’ится с ключами фиксированного массива Rcon и возвращает массив ключей. Количество ключей составляет — 11. 10 из которых принадлежат раундам алгоритма.
Первый этап шифрования начинается с применения данной функции к state путем правила суммирования, указанного выше. Иначе говоря addRoundKey XOR’ится со state, точнее с каждым его байтом. Об’XOR’енный state попадает на следующий этап, а именно в систему раундов алгоритма:
В алгоритме есть 10 раундов, то есть 10 шагов криптопреобразования. Первые 9 раундов циклично выполняют 4 функции:
Данная функция трансформирует state путем замены своих байтов на другие способом подставления в готовую фиксированную таблицу S-box:
53h будет равен edh
Данная функция производит циклическое смещение 3-х последних строк влево следующим образом:
Вычислительно самое сложное из функций. Тут происходит умножение на постоянную функцию a(x) = <03>x³ + <01>x² + <01>x + <02>. То есть произведение по указанному выше правилу конкретного столбца из state на функцию a(x). За исключением правила умножения алгоритма данный способ эквивалентен матричному умножению:
Дешифрование в AES также происходит в несколько этапов:
В алгоритме есть 10 раундов, то есть шаг криптопреобразования.
Первые 9 раундов циклично выполняют 4 функции, порядок обратный порядку шифрования, то есть:
Функция invMixColumns выполняет мультипликативно обратную операцию умножения по правилам умножения алгоритма на постоянную функцию a⁻¹(x) конкретного столбца state:
Обратная трансформация shiftRows() — циклическое смещение вправо:
Инверсия subBytes() — обратная замена байт state заведомо представленную в hex в соответствии фиксированной таблицы inverse S-box:
Список литературы:
Как устроен AES
О чём эта статья
Долгое время я считал, что криптографические алгоритмы шифрования и хеширования, вроде AES и MD5, устроены очень сложно и написать их совсем не просто, даже имея под рукой полную документацию. Запутанные реализации этих алгоритмов на разных языках программирования только укрепляли это мнение. Но недавно у меня появилось много свободного времени и я решил разобраться в этих алгоритмах и написать их. Оказалось, что они очень просто устроены и для их реализации нужно совсем немного времени.
В этой статье я напишу как устроен алгоритм шифрования AES (которого иногда называют Rijndael) и напишу его на JavaScript. Почему на JavaScript? Чтобы запустить программу на этом языке, нужен только браузер в котором вы читаете эту статью. Чтобы запустить программу, скажем, на C, нужен компилятор и найдётся совсем мало желающих, готовых потратить время на компиляцию кода из какой то статьи. В конце есть ссылка по которой можно скачать архив с html страницей и несколькими js файлами — это пример реализации AES на JavaScript.
Как применить AES
Этот алгоритм преобразует один 128-битный блок в другой, используя секретный ключ который нужен для такого преобразования. Для расшифровки полученного 128-битного блока используют второе преобразование с тем же секретным ключом. Выглядит это так:
Размер блока всегда равен 128 бит. Размер ключа также имеет фиксированный размер. Чтобы зашифровать произвольный текст любым паролем можно поступить так:
Это можно записать так:
Чтобы расшифровать массив блоков cipher нужно применить к каждому блоку decrypt:
Конечно, длина текста может быть не кратна 128 битам. В таких случаях можно дополнить текст нулями до нужной длины, а в зашифрованные данные добавить несколько байт с зашифрованным размером оригинального текста. Функции aes.encrypt и aes.decrypt в файле aes.js в примере используют этот подход.
Поле GF(2 8 )
AES активно использует так называемое конечное поле GF(2 8 ). Чтобы написать AES на JavaScript не обязательно знать, что это за поле, но если вы хотите лучше понять AES, прочтите этот раздел.
Поле GF(2 8 ) это числа 0..255 для которых определили особое умножение и особое сложение. Возмём какое нибудь число из этого поля и представим его в виде восьми битов: a = a7a6a5a4a3a2a1a0. Точно также представим число b. Сложение a и b это известная побитовая операция xor:
У сложения есть простые свойства:
Умножение определяется сложнее. Запишем многочлены с коэффициентами из битов этих чисел:
Теперь перемножим эти два многочлена и найдём остаток от деления на m:
m = x 8 + x 4 + x 3 + x + 1
r = pq mod (m)
Почему выбран именно такой m? У этого многочлена есть только два делителя-многочлена на которых он делится без остатка: единица и он сам. По аналогии с простыми числами, многочлен m «простой». Находить остаток от деления можно также как для обычных чисел: для этого достаточно уметь умножать, складывать и вычитать многочлены, причём сложение и вычитание производят по правилам GF(2 8 ), т.е. сложение и вычитание многочленов это xor между каждой парой коэффициентов. Вот два примера:
Многочлен r представим в виде
Его 8 коэффициентов представляют собой 8-битовое число из поля GF(2 8 ) и это число называется произведением a•b. В отличие от сложения, умножение нельзя найти парой простых побитовых операций. Однако умножение на произвольный многочлен в поле GF(2 8 ) можно свести к умножению на многочлен x, а умножить на x можно несколькими побитовыми операциями, о чём пойдёт речь ниже.
Для обозначения многочленов в GF(2 8 ) используют 16-ричные цифры. Например
m = x 8 + x 4 + x 3 + x + 1 = 100011011 = 0x011b = <01><1b>
Умножить на многочлен x = <02>в поле GF(2 8 ) очень просто. Рассмотрим произведение:
Теперь нужно найти остаток от деления на m. Если бит a7 = 1, то нужно один раз вычесть m. Если a7 = 0 то вычитать ничего не нужно. Итак:
Умножение на x можно записать такой функцией:
Зная как умножать на x можно умножить на любой другой многочлен. Для примера найдём a•b где a = <3c>, b =
Таблица SBox
Эта таблица представляет собой 256-байтый массив и используется для замены одного байта другим. Не обязательно понимать как она получается, потому что в код можно просто скопировать этот массив. Чтобы узнать чему равен элемент SBox[b] нужно три действия:
В сумме эти три действия дают афинное преобразование:
Несложно понять как построена эта матрица из битов. Для умножения битов нужно применять «and», для сложения — «xor». Например:
Функцию sbox я написал так:
Построенная таблица выглядит так:
63 7c 77 7b f2 6b 6f c5 30 01 67 2b fe d7 ab 76
ca 82 c9 7d fa 59 47 f0 ad d4 a2 af 9c a4 72 c0
b7 fd 93 26 36 3f f7 cc 34 a5 e5 f1 71 d8 31 15
04 c7 23 c3 18 96 05 9a 07 12 80 e2 eb 27 b2 75
09 83 2c 1a 1b 6e 5a a0 52 3b d6 b3 29 e3 2f 84
53 d1 00 ed 20 fc b1 5b 6a cb be 39 4a 4c 58 cf
d0 ef aa fb 43 4d 33 85 45 f9 02 7f 50 3c 9f a8
51 a3 40 8f 92 9d 38 f5 bc b6 da 21 10 ff f3 d2
cd 0c 13 ec 5f 97 44 17 c4 a7 7e 3d 64 5d 19 73
60 81 4f dc 22 2a 90 88 46 ee b8 14 de 5e 0b db
e0 32 3a 0a 49 06 24 5c c2 d3 ac 62 91 95 e4 79
e7 c8 37 6d 8d d5 4e a9 6c 56 f4 ea 65 7a ae 08
ba 78 25 2e 1c a6 b4 c6 e8 dd 74 1f 4b bd 8b 8a
70 3e b5 66 48 03 f6 0e 61 35 57 b9 86 c1 1d 9e
e1 f8 98 11 69 d9 8e 94 9b 1e 87 e9 ce 55 28 df
8c a1 89 0d bf e6 42 68 41 99 2d 0f b0 54 bb 16
Её можно просто скопировать в код, как часто делают, а можно вычислять функцией sbox по мере надобности.
Таблица InvSBox
Для дешифрования текста AES использует таблицу обратную к SBox. Таблица InvSBox обладает одним свойством: InvSBox[SBox[i]] = i. InvSBox выглядит так:
52 09 6a d5 30 36 a5 38 bf 40 a3 9e 81 f3 d7 fb
7c e3 39 82 9b 2f ff 87 34 8e 43 44 c4 de e9 cb
54 7b 94 32 a6 c2 23 3d ee 4c 95 0b 42 fa c3 4e
08 2e a1 66 28 d9 24 b2 76 5b a2 49 6d 8b d1 25
72 f8 f6 64 86 68 98 16 d4 a4 5c cc 5d 65 b6 92
6c 70 48 50 fd ed b9 da 5e 15 46 57 a7 8d 9d 84
90 d8 ab 00 8c bc d3 0a f7 e4 58 05 b8 b3 45 06
d0 2c 1e 8f ca 3f 0f 02 c1 af bd 03 01 13 8a 6b
3a 91 11 41 4f 67 dc ea 97 f2 cf ce f0 b4 e6 73
96 ac 74 22 e7 ad 35 85 e2 f9 37 e8 1c 75 df 6e
47 f1 1a 71 1d 29 c5 89 6f b7 62 0e aa 18 be 1b
fc 56 3e 4b c6 d2 79 20 9a db c0 fe 78 cd 5a f4
1f dd a8 33 88 07 c7 31 b1 12 10 59 27 80 ec 5f
60 51 7f a9 19 b5 4a 0d 2d e5 7a 9f 93 c9 9c ef
a0 e0 3b 4d ae 2a f5 b0 c8 eb bb 3c 83 53 99 61
17 2b 04 7e ba 77 d6 26 e1 69 14 63 55 21 0c 7d
Виды AES
Алгоритм AES преобразует блок длиной 128 битов в другой блок той же длины. Для преобразования применяется расписание ключей w получаемое из ключа. 128-битный блок в AES представляется в виде матрицы 4×Nb. Стандарт допускает только одно значение Nb = 4, поэтому длина блока всегда 128 бит, хотя алгоритм может работать с любым Nb. Длина ключа равна 4Nk байт. Алгоритм шифрования блока состоит из Nr раундов — применений одной и той же группы преобразований к 128-битному блоку данных. Стандарт допускает следующие комбинации этих трёх параметров:
Nk | Nb | Nr | |
AES-128 | 4 | 4 | 10 |
AES-192 | 6 | 4 | 12 |
AES-256 | 8 | 4 | 14 |
Преобразование KeyExpansion
Для шифрования текста AES применяет не пароль или хеш от пароля, а так называемое «расписание ключей» получаемое из ключа. Это расписание можно представить как Nr + 1 матриц размера 4×Nb. Алгоритм шифрования делает Nr + 1 шагов и на каждом шаге он, помимо других действий, берёт одну матрицу 4×Nb из «расписания» и поэлементно добавляет её к блоку данных.
Шифрование блока данных
Алгоритм шифрования получает на вход 128-битный блок данных input и расписание ключей w, которое получается после KeyExpansion. 16-байтый input он записывает в виде матрицы s размера 4×Nb, которая называется состоянием AES, и затем Nr раз применяет к этой матрице 4 преобразования. В конце он записывает матрицу в виде массива и подаёт его на выход — это зашифрованный блок. Каждое из четырёх преобразований очень простое.
Для шифрования используют [a b c d] = [ <02><03><01><01>]. Можно проверить, что преобразование обратное к MixColumns[ <02><03><01><01>] это MixColumns[ <0e><0b><0d><09>].
Схематично шифрование можно изобразить так:
Расшифровка
Как видно, для шифрования блока данных AES последовательно применяет к нему много обратимых преобразований. Для расшифровки нужно применить обратные преобразования в обратном порядке.
Немного оптимизации
Функция sbox имеет всего 256 возможных входных значений и 256 возможных выходных значений. Чтобы не вычислять много раз sbox для одного аргумента, нужно кешировать результаты. На JavaScript это несложно сделать даже не меняя код написанный ранее. Для этого нужно всего лишь дописать ниже вот это:
Этот код заменяет sbox функцией которая кеширует результаты sbox. Тоже самое можно сделать для любой функции, например для invsbox и rcon. Этот же приём можно применить для функции gf.mul которая умножает два байта в поле GF(2 8 ), но в этом случае размер кеша будет равен 256×256 элементов, что довольно много.
Ссылки
Документация к AES на английском называется FIPS 197.