Криптографическая библиотека для Arduino (шифр “МАГМА”, ГОСТ Р 34.12-2015)

Библиотека MagmaEncryptor для платформы Arduino позволяет использовать российский шифр “Магма” (стандартизован ГОСТ Р 34.12-2015) в целях защиты информации. Интерфейс библиотеки простой: две функции (или два метода) – lock() и unlock(), – соответственно, выполняют зашифрование и расшифрование, также имеется функция для загрузки ключей – setKeys(). Реализация сочетает в себе и шифрование, и аутентификацию передаваемых сообщений. То есть, помимо того, что данные будут зашифрованы, для них вычисляется код аутентификации (MAC, имитовставка), при помощи которого может быть проверена целостность и подлинность сообщения – проверяются функцией расшифрования.

Библиотека может быть использована как учебный материал или для самостоятельного программирования устройств “в быту”. Данная реализация заведомо не защищена от аппаратных атак (более того, ключи можно элементарно извлечь, получив устройство-носитель) и, из-за свойств шифра и микроконтроллера, от атак, основанных на анализе побочных наводок и излучений (этот момент, в основном, связан с использованием подстановок по таблице, возможно, я как-нибудь напишу отдельную записку по этой интересной теме). Очередной раз отмечу очевидное: несмотря на то, что я тщательно проверил код, в нём всё равно могут быть ошибки и уязвимости.

Внутренняя логика построения достаточно проста: шифр работает в режиме счётчика; после того, как открытый текст зашифрован, для шифротекста и значения nonce вычисляется код аутентификации, для его вычисления служит алгоритм CMAC, построенный на базе того же шифра (“Магма”). Полученный результат возвращается приложению. Для шифрования и получения MAC используются разные ключи (это важный момент). Исходный код библиотеки содержит достаточно подробные комментарии, поэтому с техническими деталями можно ознакомиться, заглянув внутрь.

Типичный сценарий использования библиотеки

1. Создаём экземпляр класса:

MagmaEncryptor M;

Если есть свободная память, то рекомендуется создавать два экземпляра, работающих с разными ключами – для отправки сообщений (записи) и их получения (чтения). Вот так:

MagmaEncryptor MWrite, MRead;

Дело в том, что корректное использование MAC подразумевает учёт случая, когда переданное сообщение копируется третьей стороной и позднее воспроизводится в сторону передавшего узла – соответственно, протокол должен подразумевать тот или иной контроль направления передачи.

2. Загружаем ключи:

M.setKeys(cipher_key, MAC_key);

Здесь cipher_key – это секретный ключ шифрования; MAC_key – секретный ключ для аутентификации. Оба ключа имеют разрядность 256 бит (соответствует разрядности ключа шифра), то есть, занимают 32 байта: byte cipher_key[32]; Ключи должны быть разными. Система симметричная, поэтому на всех узлах должны быть экземпляры ключей.

3. Для шифрования вызываем lock():

M.lock(nonce,plain_text,cipher_text,len,MAC);

Параметры: nonce – это начальное значение счётчика, оно должно использоваться только один раз (с конкретным ключом, при изменении ключа – можно повторно использовать); передаётся указатель на переменную – lock() записывает новое значение счётчика, это позволяет использовать nonce в последовательных вызовах lock(); plain_text – открытый текст (указатель на байтовый буфер); cipher_text – буфер для шифротекста; размеры буферов совпадают, и равны len (в байтах); MAC – буфер для записи кода аутентификации (64 бита, восемь байтов).

4. Для расшифрования вызываем unlock(), параметры совпадают с lock():

M.unlock(nonce,plain_text,cipher_text,len,MAC);

Функция unlock() должна получать значение nonce, соответствующее начальному значению в вызове lock(). Значение MAC – это значение кода аутентификации, полученное lock() для данного сообщения. Функция проверяет код аутентификации и расшифровывает сообщение, записывая результат в plain_text. Если значение MAC не совпало, то функция возвращает 0. Обратите внимание, что расшифрование производится в любом случае, однако если код аутентификации не корректен, использовать результат расшифрования нельзя.

5. Размеры блоков данных:

(Разрядность шифра и MAC – 64 бита; разрядность ключей – 256 бит.)

Ключи – 32 байта, byte key[32];
MAC – 8 байтов, byte MAC[8];
Nonce – 4 байта, uint32_t nonce;

Обратите внимание на то, что библиотека не следит за использованием ключей и nonce – предполагается, что это делает само приложение, вызывающее библиотеку.

Ограничения по объёму данных

Отдельную задачу представляет оценка максимального числа блоков, которые можно защитить данной библиотекой (для одного набора ключей), при условии идеальной реализации и отсутствия утечек. Попробуем оценить это число приблизительно. Очевидно, что строгий верхний предел ставит разрядность счётчика – 32 бита (или 232 блоков). Но отталкиваться нужно от стойкости MAC. Традиционная консервативная оценка для 64-разрядного CMAC – 221 блоков, при корректном использовании. Полагаю, что для “бытовых применений” вполне можно остановиться на оценке 220 блоков, что примерно соответствует восьми мегабайтам переданных данных (кто всё это будет записывать?). Или нескольким сотням тысяч команд и ответов устройств. Не менее разумным будет просто остановиться на разрядности двухбайтового счётчика (его проще вести на 8-битном контроллере) – 216. После того, как число блоков приблизилось к установленному максимальному значению, следует перейти на другой набор ключей – для этого в библиотеке предусмотрена функция KDF(), генерирующая новые ключи на основе заданных.

Установка библиотеки

Библиотеку можно скачать в виде сжатого архива и импортировать в Arduino IDE штатными средствами – Sketch -> Include Library -> Add .ZIP library. (Можно просто скопировать содержимое архива в директорию libraries IDE.) В составе библиотеки находится небольшой пример кода, который также служит для тестирования корректности сборки в конкретном окружении (после установки библиотеки пример будет доступен в разделе File -> Examples -> MagmaEncryptor).

Дополнение (03/07/2020): в современных версиях Arduino IDE, при попытке сборки библиотеки, может выдаваться ошибка компиляции (относящаяся к ассемблеру), это связано с тем, что “из коробки” в IDE для компилятора указан неподходящий уровень оптимизации (флаг -O). Шифр реализован на ассемблере, поэтому, чтобы использовать библиотеку,  необходимо правильно настроить уровень оптимизации. Глобально (для Arduino IDE), это делается в файле настроек параметров сборки platform.txt, который, обычно, находится в директории hardware/arduino/avr (это поддиректория установки Arduino IDE). Для успешной компиляции нужно установить параметр -O1 (или выше). По умолчанию – устанавливается значение -Os. Соответствующая строка в файле настроек platform.txt, в которой можно поменять уровень оптимизации, начинается с compiler.cpp.flags. Это только один из способов (см. про второй способ ниже), но он работает; по крайней мере, для версии Arduino IDE 1.8.13 под Linux (Fedora 32). Возможно, вы используете какие-то другие, особые настройки компиляции. В таком случае – вот основная идея: нужно установить уровень оптимизации (и прочие параметры) так, чтобы компилятор смог использовать конструкции inline-ассемблера (asm).

Соответственно, аналогичного эффекта можно достичь, используя директивы компилятора pragma в исходном коде, либо добавив директивы __attribute__((optimize(“-O1”))) в объявления прототипов функций, которые используют inline-ассемблер (такие определения добавляются в файле MagmaEncryptor.h). Менять в файле platform.txt, в этом случае, ничего не нужно. Я внёс нужные дополнения в исходный код и, чтобы не ломать ссылки и рабочий код предыдущей версии, выложил дополнительный архив с изменениями.

Библиотека (версия с исправлениями): MagmaEncryptor1.zip
SHA-256: d3f87b5f40415c25f2c74ca29a658bd5595a0058dc5adf21a1128f565162fdef

Библиотека (предыдущая версия): MagmaEncryptor.zip.
SHA-256: 12b214d719bd36c3dd2e1374cc7d184945498f8cb4040e0ba1417db5162550a1

Исходный код (копия, в архиве библиотеки – эти же файлы): MagmaEncryptor_01beta.h, MagmaEncryptor_01beta.cpp.

(Проверено на Arduino Uno и Nano, все с ATmega328.)

Вопросы, поправки, пожелания – весьма приветствуются (по электронной почте).

Ссылки по теме:

Симметричные блочные шифры на примере ГОСТ Р 34.12-2015 “Магма”;
Интернет вещей: шифр “Магма” (ГОСТ Р 34.12-2015) для Arduino

Комментарии читателей блога: 10

  • 1. 19th February 2017, 11:48 // Читатель Юрий написал:

    Прекрасная работа! Спасибо!

    Очень хотелось бы почитать ваше сравнение с IoT шифрами от АНБ. Кто круче так сказать ;)

  • 2. 6th March 2017, 12:36 // Читатель Nik написал:

    Не получается запустить пример, при компиляции ошибка, не могу разобраться в чем дело ((
    error: can’t find a register in class ‘POINTER_Y_REGS’ while reloading ‘asm’

  • 3. 6th March 2017, 13:10 // Александр Венедюхин:

    Какую версию компилятора (avr-gcc) используете? Я проверял с avr-gcc (GCC) 4.8.1.
    Причина может быть в старой версии.

  • 4. 6th March 2017, 16:07 // Читатель Nik написал:

    Все разобрался, поменял уровень оптимизации с Os на O1, и скетч скомпилировался
    В папке с arduino ide лежит avr-gcc-4.9.2, думаю эта версия…

  • 5. 6th March 2017, 18:55 // Александр Венедюхин:

    Да, 4.9 – подходящая версия. Уровень оптимизации как раз был вторым кандидатом на роль источника проблемы: -Os в gcc, похоже, не со всеми ассемблерными конструкциями уживается.

  • 6. 23rd November 2017, 06:51 // Читатель Максим написал:

    Есть ли реализация шифрования строки?
    Буду премного благодарен, если поделитесь
    abc6181@yandex.ru

  • 7. 23rd November 2017, 11:55 // Александр Венедюхин:

    > Есть ли реализация шифрования строки?

    Готовой функции, которая получала бы на вход строку, а возвращала бы зашифрованную строку и MAC, нет. Всё ж, это базовая библиотека. Однако подобную функцию несложно реализовать на базе lock(), там всё готово, нужно только вычислить длину строки и выделить память под буферы:

    M.lock(nonce,plain_text,cipher_text,len,MAC);

    – подготавливаем буферы, вычисляя размер на основе длины строки; исходную строку – под указатель plain_text, длину строки в байтах – в переменную len и т.д. Результат получаем в буфере, на который указывает cipher_text. Необходимо прикрепить к строке код аутентификации, который функция lock() возвращает в переменной MAC.

  • 8. 23rd November 2017, 17:21 // Читатель Максим написал:

    Благодарю!
    Буду пробовать

  • 9. 16th March 2018, 09:23 // Читатель Руслан написал:

    Здравствуйте. Ошибка компиляции для Arduino IDE 1,8,5

    C:\Arduino\libraries\MagmaEncryptor\src\MagmaEncryptor.cpp: In function ‘DoCipher.constprop’:

    C:\Arduino\libraries\MagmaEncryptor\src\MagmaEncryptor.cpp:649:4: error: can’t find a register in class ‘POINTER_Y_REGS’ while reloading ‘asm’

    );

    ^

    C:\Arduino\libraries\MagmaEncryptor\src\MagmaEncryptor.cpp:649:4: error: ‘asm’ operand has impossible constraints

    lto-wrapper: C:\Users\Vlad\Desktop\Хобби\arduino-1.8.5\hardware\tools\avr/bin/avr-gcc returned 1 exit status

    c:/arduino-1.8.5/hardware/tools/avr/bin/../lib/gcc/avr/4.9.2/../../../../avr/bin/ld.exe: error: lto-wrapper failed

    collect2.exe: error: ld returned 1 exit status

    Используем библиотеку MagmaEncryptor версии 0.1 из папки: C:\Users\Vlad\Documents\Arduino\libraries\MagmaEncryptor
    exit status 1
    Ошибка компиляции для платы Arduino Pro or Pro Mini.

    В Arduino IDE 1,6,1 нормально все. Спасибо

  • 10. 27th December 2019, 03:46 // Читатель Deface написал:

    Привет, отличный блог. Долго искал материал по tls. Лучше чем у тебя в Рунете нет! Спасибо!!!

Написать комментарий

Ваш комментарий:

Введите ключевое слово "9GD6D" латиницей СПРАВА НАЛЕВО (<--) без кавычек: (это необходимо для защиты от спама).

Если видите "капчу", то решите её. Это необходимо для отправки комментария ("капча" не применяется для зарегистрированных пользователей). Обычно, комментарии поступают на премодерацию, которая нередко занимает продолжительное время.