Embedded HandbookПриложения
Все разделы › III. Периферия
STM32F103, STM32F407, STM32F437, STM32L4A6 · уровень продвинутый · 15 мин чтения
Открыть в интерактивном справочнике

43. Аппаратная криптография: CRC, RNG, HASH, CRYP

Четыре блока, которые отвечают на четыре разных вопроса: не испортились ли данные (CRC), откуда взять число, которое никто не угадает (RNG), те ли это данные, что были (HASH), и как их прочитать только тому, кому положено (CRYP). Первые два есть почти у всех STM32, вторые два — у части семейств, и там, где их нет, ту же работу делает библиотека.

Раздел опирается на пять камней: F103 и F407 — привычные, F437 — как F407 с блоками HASH и CRYP, L4A6 — с компактным AES, G474 — с CRC, у которого можно задать полином.

Как это называется в документации

Оригинал Перевод
cyclic redundancy check (CRC) циклический избыточный код, «контрольная сумма»
generator polynomial образующий многочлен, полином
initial value / final XOR начальное значение / завершающее исключающее ИЛИ
reflected input / output отражённый (обратный порядок бит) вход / выход
true random number generator (TRNG) истинный генератор случайных чисел
seed error / clock error ошибка начального значения / тактирования
hash, digest хеш, свёртка
collision коллизия: два сообщения с одним хешем
pre-image resistance стойкость к восстановлению прообраза
HMAC хеш с ключом для подтверждения подлинности
block cipher блочный шифр
ECB / CBC / CTR / GCM режимы работы блочного шифра
initialization vector (IV), nonce вектор инициализации, одноразовое число
authentication tag метка подлинности
key derivation подготовка (раскрытие) ключа

43.1 CRC: не сумма, а остаток от деления

Слово «контрольная сумма» прижилось, но CRC — не сумма. Это остаток от деления сообщения на заданное число, причём деление ведётся над битами по правилам, где сложение — это исключающее ИЛИ. Отсюда главное свойство: каждый бит сообщения влияет на результат, и один перевёрнутый бит меняет CRC.

Делитель записывают многочленом: x⁸ + x² + x + 1. Степени — это позиции единиц в числе; старшая степень подразумевается и опускается, поэтому многочлен восьмой степени записывают восемью битами: 0x07. Для шестнадцатой — 0x8005 означает x¹⁶ + x¹⁵ + x² + 1.

CRC-8 с полиномом 0x07 для байта 0x31 по шагам: сдвиг и XOR, когда выпадает единица

Название Полином Где встречается
CRC-8 0x07 датчики с однопроводным интерфейсом, короткие кадры
CRC-16/MODBUS 0x8005, отражённый, начальное 0xFFFF Modbus RTU (раздел 16)
CRC-16/XMODEM 0x1021, начальное 0x0000 XMODEM, SD-карты (раздел 41а)
CRC-16/CCITT-FALSE 0x1021, начальное 0xFFFF многие протоколы
CRC-32 0x04C11DB7, отражённый, начальное и завершающее 0xFFFFFFFF Ethernet, ZIP, PNG
CRC-32/MPEG-2 0x04C11DB7, без отражений и завершающего XOR аппаратный блок STM32

⚠️ Полином — только один из шести параметров. Ширина, полином, начальное значение, завершающее XOR, отражение входа и отражение выхода. Два CRC с одинаковым полиномом 0x04C11DB7 дают для строки 123456789 0xCBF43926 и 0x0376E6E7 — потому что остальные пять параметров различаются. «CRC не сошёлся с компьютером» почти всегда означает именно это, а не ошибку в данных.

Вкладка «CRC» в калькуляторах приложения считает все пять вариантов из таблицы для одних и тех же байтов — сразу видно, какой из них совпал с другой стороной, и что даёт аппаратный блок после дополнения до целых слов.

Проверка на приёме: к сообщению приписывают его CRC и считают CRC заново. У вариантов без завершающего XOR получается ноль — если CRC приписан в нужном порядке байтов: у отражённых (MODBUS) младшим байтом вперёд, у прямых (MPEG-2) старшим. У вариантов с завершающим XOR выходит не ноль, а постоянное число: у CRC-32 — 0x2144DF1C. Проще и нагляднее другое: посчитать CRC по данным без приписанного и сравнить с принятым.

Аппаратный блок

У F1 и F4 блок фиксирован: полином 0x04C11DB7, начальное значение 0xFFFFFFFF, никаких отражений, данные — словами по 32 бита.

RCC->AHBENR |= RCC_AHBENR_CRCEN;
CRC->CR = CRC_CR_RESET;            /* к начальному значению */
for (i = 0; i < words; i++) {
    CRC->DR = buf[i];              /* по слову */
}
result = CRC->DR;

Два следствия. Первое: результат совпадает с CRC-32/MPEG-2, а не с тем, что считает архиватор, — сравнивать с компьютером надо тем же вариантом. Второе: блок берёт слово со старшего бита, а слово в памяти Cortex‑M лежит младшим байтом вперёд (раздел 3а). Байты '1', '2', '3', '4' приходят в блок как 0x34333231, и первым он считает '4': для строки 12345678 выходит 0xFEFC54F9 — CRC-32/MPEG-2 строки 43218765. Чтобы получить CRC по потоку байтов, как на компьютере (0x49E3C2FB), каждое слово переворачивают: CRC->DR = __REV(buf[i]);. Длина буфера при этом — кратна четырём байтам.

Байты 1, 2, 3, 4 в памяти и то же слово в регистре CRC: без переворота блок первым считает байт 4 и выдаёт CRC-32/MPEG-2 строки 43218765, с командой REV — байт 1 и CRC строки 12345678, как на компьютере

У G4 и L4 блок программируемый: POL задаёт полином, INIT — начальное значение, POLYSIZE — ширину 7/8/16/32, REV_IN и REV_OUT — отражения, а данные можно подавать и байтами: сколько байтов уходит в блок, решает ширина записи в DR — 32-битная запись отправляет четыре, 8-битная один. Так на нём считают и CRC-16/MODBUS:

CRC->POL  = 0x8005u;
CRC->INIT = 0xFFFFu;
CRC->CR   = (1u << CRC_CR_POLYSIZE_Pos)     /* 16 бит */
          | (1u << CRC_CR_REV_IN_Pos)       /* отражение по байтам */
          | CRC_CR_REV_OUT
          | CRC_CR_RESET;
for (i = 0; i < n; i++)
    *(volatile uint8_t *)&CRC->DR = data[i];   /* запись байта, а не слова */
crc16 = (uint16_t)CRC->DR;                     /* результат — младшие 16 бит */

Обычная запись CRC->DR = data[i]; — 32-битная: вместе с байтом в блок уйдут три нулевых, и для строки 123456789 вместо 0x4B37 выйдет 0xB1C1.

Вместе с DMA блок считает CRC буфера почти без участия ядра — это штатный способ проверять прошивку при старте (раздел 48).


43.2 RNG: откуда берётся случайность

Псевдослучайная последовательность из формулы (rand, LFSR) хороша для заставки и никуда не годится для ключа: узнав начальное значение, её повторит любой. Криптографии нужен источник, который нельзя воспроизвести, — физический шум.

У STM32 это несколько кольцевых генераторов, чей аналоговый шум складывается и снимается сдвиговым регистром. Блок тактируется от той же ветки 48 МГц, что и USB (раздел 20), и выдаёт одно 32-разрядное слово примерно за 40 тактов этого сигнала — около микросекунды, до 1,2 млн слов в секунду.

RCC->AHB2ENR |= RCC_AHB2ENR_RNGEN;
RNG->CR |= RNG_CR_RNGEN;

if (RNG->SR & (RNG_SR_CECS | RNG_SR_SECS)) {
    /* ошибка: число использовать нельзя, перезапустить блок */
}
while (!(RNG->SR & RNG_SR_DRDY)) { }
value = RNG->DR;

⚠️ Два флага ошибок читают до данных, а не после. CECS — тактовая блока ниже допустимой: обычно не настроена ветка 48 МГц. SECS — сбой источника шума: блок увидел в нём больше 64 одинаковых бит подряд или больше 32 чередований 0 и 1 подряд, и всё, что он выдал до перезапуска, случайным считать нельзя. Восстановление: сбросить SEIS, выключить и снова включить RNGEN, накопленные слова выбросить. Код, который ждёт только DRDY, получит числа, выглядящие случайными, — и это худший из отказов, потому что он незаметен.

Совпадение двух слов подряд блок сам не ловит. Если изделию нужно соответствие требованиям сертификации, это делает программа: первое слово после включения не используют, а каждое следующее сравнивают с предыдущим и совпадение считают отказом. Второе правило — не выбрасывать «некрасивые» значения: отбрасывание нулей или повторяющихся байт делает распределение неслучайным.

Где брать случайность там, где блока нет (F103): шум младших разрядов АЦП с висящего входа, дрожание фазы между двумя независимыми генераторами (HSI против LSI), время до нажатия кнопки. Каждый источник по отдельности слаб; их смешивают хешем — и это уже следующий блок.

Когда криптостойкость не нужна — разброс пауз при повторе (раздел 186), мигание «вразнобой», тестовые данные, — хватит формулы. Но не любой.

Линейный конгруэнтный генератор x = x × 1664525 + 1013904223 по модулю 2³² выглядит случайным только в старших битах. Его младший бит строго чередуется — в опыте 010101010101, — второй бит повторяется через 4 шага, третий через 8, четвёртый через 16. Взять x % 2 для «орла или решки» — значит получить чередование; брать нужно старшие биты, x >> 16.

Младшие биты линейного конгруэнтного генератора повторяются через 2, 4, 8 и 16 шагов

xorshift32 — три сдвига и три «исключающих или»:

x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;

Он обходит все ненулевые 32-битные значения по разу: в опыте от единицы до возврата в неё прошло 4 294 967 295 шагов, то есть 2³² − 1. Ноль — ловушка: из нуля генератор не выходит, поэтому начальное значение нулём быть не должно. На Cortex-M4 шаг занимает три команды EOR — сдвиг встроен в саму команду.


43.3 HASH: отпечаток данных

Хеш-функция превращает сообщение любой длины в строку фиксированной длины так, что обратно не вернуться, а найти два сообщения с одинаковым хешем практически невозможно. Изменение одного бита на входе меняет примерно половину бит на выходе — это называют лавинным эффектом.

Алгоритм Длина результата Состояние
MD5 16 байт сломан: коллизии находятся за секунды; только контроль целостности
SHA-1 20 байт сломан для подписей с 2017 года
SHA-256 32 байта рабочий стандарт
SHA-512 64 байта быстрее на 64-разрядных, на Cortex‑M — медленнее

Три вещи, для которых хеш нужен в приборе:

Целостность прошивки. Загрузчик считает SHA-256 образа и сравнивает с записанным — это точнее CRC: CRC ловит случайные искажения, а подобрать другой образ с тем же SHA-256 практически невозможно. Но от подмены хеш сам по себе не спасает: кто подменил образ, запишет рядом и его хеш — как и CRC (раздел 48.4). Защищает только эталон, которому можно верить: подпись (раздел 48а), HMAC с секретным ключом или хеш в области, закрытой от записи.

Хранение секрета без секрета. Пароль доступа хранят не сам, а его хеш: проверить можно, восстановить — нет. С оговоркой: короткий пароль подберут по словарю хешей, поэтому к нему добавляют случайную «соль» и хешируют много раз.

Подтверждение подлинности. HMAC — хеш, в который подмешан ключ: сообщение с верным HMAC мог составить только тот, кто ключ знает. Это защита от подделки команд прибору, дешевле шифрования.

HMAC доказывает, кто составил сообщение, но не когда: перехваченную команду можно отправить ещё раз, и метка у неё верная. Поэтому в подписанную часть кладут счётчик команд — прибор принимает только номер больше последнего — или одноразовое число, выданное самим прибором. А метку сравнивают за постоянное время, все байты до конца: memcmp вправе выйти на первом несовпадении, и по времени ответа метку можно подобрать байт за байтом:

uint8_t diff = 0;
for (i = 0; i < 32; i++)
    diff |= tag[i] ^ expect[i];    /* все 32 байта, без выхода по дороге */
ok = (diff == 0);

Аппаратный блок у F437/L4A6 считает SHA-1, SHA-256 и MD5, поддерживает HMAC и принимает данные через DMA. Данные подаются словами; регистр DATATYPE говорит, как переставить байты: строка abc в памяти лежит как 61 62 63, словом это 0x00636261, и без правильного DATATYPE хеш выйдет от другой последовательности байт. Это второй по частоте источник вопроса «почему хеш не совпадает с компьютером». Третий — длина: последнее слово заполнено не целиком, и число значащих бит в нём пишут в поле NBLW регистра HASH_STR (для abc — 24). Без этого к данным добавится лишний нулевой байт.


43.4 CRYP: шифрование

Блочный шифр берёт блок фиксированной длины и ключ и выдаёт блок той же длины. AES — стандарт: блок 16 байт, ключ 16, 24 или 32 байта (128/192/256 бит). DES и тройной DES остались только для совместимости.

Ключевой вопрос — не «какой шифр», а «в каком режиме»:

Режим Что делает Брать?
ECB каждый блок отдельно нет: одинаковые блоки дают одинаковый шифр, картинка просвечивает
CBC блок сцеплен с предыдущим через IV для файлов; требует дополнения до 16 байт
CTR шифр превращён в поток: длина любая для потоков данных
GCM CTR плюс метка подлинности да, по умолчанию

GCM даёт то, чего нет у остальных: подменённый байт шифротекста не расшифруется «в мусор», а откажет проверкой метки в 16 байт. Прибор, принимающий команды, обязан знать, что команда не подделана, — шифрование без подлинности этого не даёт.

⚠️ В CTR и GCM одноразовое число не повторяют никогда. Два сообщения с одним ключом и одним IV дают XOR своих открытых текстов, а в GCM ещё и позволяют подделать метку. IV берут либо из счётчика, который хранится во Flash и переживает перезагрузку, либо 96 бит из RNG. Счётчик в ОЗУ, начинающийся после сброса с нуля, — готовая уязвимость. Ключ меняют раньше, чем счётчик может переполниться.

Сообщение в 60 байт в CBC займёт 64 (четыре блока с дополнением), в CTR и GCM — ровно 60, плюс метка в GCM.

Аппаратный блок ускоряет операцию — на F437 это CRYP с DES/TDES/AES и FIFO на восемь слов, на L4A6 — компактный AES только с AES-128/256 и режимами ECB/CBC/CTR/GCM, у обоих вход через DMA. Тонкость L4: расшифровка в ECB/CBC требует подготовленного ключа — отдельный режим MODE перед расшифровкой; в CTR и GCM этого нет, потому что там шифр работает только в прямую сторону, — ещё один довод за них.

⚠️ Блок — не библиотека. Регистры делают одно преобразование блока. Режимы сцепления, дополнение, метка GCM, обмен ключами, подписи — код поверх. Писать его самому — верный способ получить уязвимость: берут готовую библиотеку, а блок она использует сама, если он есть.

Библиотека производителя покрывает все семейства, включая те, где аппаратных блоков нет вовсе: AES во всех режимах, SHA-1…SHA-512 с HMAC, программный генератор случайных чисел, RSA, эллиптические кривые и подписи ECDSA. Поставляется собранной под Keil, IAR и GCC; подключение — путь к заголовкам и имя библиотеки в компоновщике (раздел 4).

⚠️ Секрет в исходниках — не секрет. Ключ шифрования в массиве const uint8_t key[16] читается из прошивки за минуту. Ключи хранят в защищённой области с включённой защитой чтения (раздел 11) или получают из обмена при первом подключении; в исходниках — только в тестовых примерах, и с этим знанием.


43.5 Что чем защищать

Задача Средство Блок
кадр по UART/CAN не исказился при передаче CRC-16 программный или аппаратный CRC
прошивка во флеш-памяти цела CRC-32 при старте CRC + DMA
прошивка не подменена SHA-256 + подпись HASH или библиотека
команда прибору — от своего сервера HMAC-SHA-256 HASH или библиотека
данные не прочитает посторонний AES-GCM CRYP / AES или библиотека
ключ сеанса, одноразовое число RNG RNG
пароль доступа хранится безопасно соль + многократный SHA-256 HASH или библиотека

Правило выбора: CRC — от помех, хеш — от незаметной порчи, подпись или HMAC — от подмены и подделки, шифр — от чтения. Каждый следующий не заменяет предыдущего, а решает другую задачу.


43.6 Грабли

CRC не совпадает с компьютером. Разные параметры при одном полиноме; у аппаратного блока F1/F4 — вариант MPEG-2, слова по 32 бита, без отражений, и байты слова он берёт со старшего: без __REV CRC выйдет от переставленных байтов (43.1).

Хеш не совпадает с компьютером. DATATYPE не переключён на байты; блок хеширует переставленные байты слов.

RNG выдаёт одинаковые числа. Совпадений блок сам не ловит: SECS говорит о сбое источника шума, а не о повторе слова. Проверять SECS и CECS до чтения и ждать DRDY; при SECS — сбросить SEIS и перезапустить блок. Где нужен непрерывный тест, слово с предыдущим сравнивает программа.

RNG не готов никогда. CECS: нет тактовой 48 МГц — не настроена ветка PLL, как для USB.

Расшифровка даёт мусор. Не тот ключ, не тот IV, не тот режим; в ECB/CBC на L4 — не сделана подготовка ключа перед расшифровкой.

GCM отказывает при верном ключе. Подменён или искажён хотя бы байт шифротекста, заголовка или метки — это не ошибка, а работа режима.

Чужая команда прошла проверку GCM. IV повторился с тем же ключом — например, счётчик в ОЗУ после сброса начался с нуля: по двум сообщениям с одним IV метку подделывают без ключа.

ECB «работает», но шифр повторяется. Одинаковые блоки — одинаковый шифротекст; менять режим на GCM.

Ключ виден в прошивке. Массив в исходниках. Защита чтения и хранение вне кода.

Библиотека не компонуется. Взят вариант не для того ядра или без аппаратного блока при его наличии; в архиве — разные сборки.


43.7 Итоги

Дальше: раздел 43а «Откуда TLS берёт случайность: аппаратный RNG, свой источник энтропии и проверка качества» — как прошивка засевает генератор mbedTLS: драйвер RNG F407 с обработкой ошибок, свой источник на F103 и тесты источника на ходу.

Открыть в интерактивном справочнике