
Статья посвящена криптографически защищённому сопряжению и взаимодействию двух плат ESP32 через обычный светодиод по протоколу LX.25 или через ESP-NOW и LoRa.
Согласование ключей происходит с использованием протокола Диффи-Хеллмана на эллиптических кривых (X25519), а сообщения шифруются с помощью алгоритма AES-256-GCM. Библиотека SecurePair поддерживает любые средства передачи. Она работает через ESP-NOW, LoRa и любой другой канал связи, способный пересылать пакеты. А с помощью PacketLED можно реализовать обмен сообщениями даже через обычные светодиоды.

Для сопряжения потребуется наличие на каждой плате кнопки и пользователь, который будет сравнивать короткий код. Весь процесс обходится без приложения, QR-кода, пароля и жёстко зашитых в прошивку ключей.
Передача ключей через свет. При использовании PacketLED один и тот же светодиод отправляет, получает и показывает код. Никакой радиосигнал в этом случае не отправляется, и чтобы вклиниться в этот процесс, атакующему придётся прямо у вас на виду как-то направить свет на оба светодиода. Но даже если представить, что ему это удалось, его вмешательство выдаст несовпадение кода.
Проверка человеком. Обе платы с помощью 20 коротких/длинных миганий передают один и тот же код. Внедрившееся в канал связи устройство не сможет повторить такой код — вероятность составляет один из миллиона.
Множество плат. Каждая плата имеет собственный постоянный идентификатор и запоминает до 8 сопряжённых устройств — каждое со своим ключом. Повторное сопряжение с уже знакомой платой ведёт к замене этого ключа, но только после согласования обеими платами.
Любое средство передачи. После сопряжения сообщения передаются в зашифрованном виде с помощью тех же светодиодов, через ESP-NOW (до десятков метров), LoRa (до нескольких километров) или через ваш собственный канал. Если нет доступных светодиодов, сопряжение можно реализовать и по WiFi.
Стандартная криптография. Используются X25519, HKDF и AES-256-GCM — базовые компоненты TLS 1.3 с защитой от атаки через воспроизведение. При перезагрузке платы ключи остаются. Их также можно сохранять в зашифрованном виде с помощью мастер-ключа, зашитого в саму микросхему.
Минимум кода. Достаточно вызвать
provision()в функцииsetup(), а затемsend()иreceive()для передачи/получения сообщений. В циклеloop()ничего для сопряжения прописывать не нужно.
Статус: beta (0.5).
На чём тестировалось: на платах ESP32, ESP32-C3 и ESP32-C6. Сопряжение выполнялось с помощью светодиодов (на расстоянии до 2 м) и через ESP-NOW. Передача сообщений производилась через светодиоды и ESP-NOW. Есть поддержка повторного сопряжения через удержание кнопки во время запуска, а также возможность хранения ключей в зашифрованном виде.
На чём не тестировалось: LoRa.
Что останется неизменным: основной принцип — согласование ключа, который один раз утверждается человеком и затем используется через любое средство передачи.
Планируется добавить: поддержку различных каналов передачи и способы отображения данных. Буду признателен за сторонние отчёты о тестировании и другую помощь проекту.
Содержание:
Функциональность
Чтобы наладить связь между двумя платами, для них нужно создать общий секретный ключ. Без него любой, кто находится в диапазоне доступа, сможет читать передаваемые сообщения или посылать в канал свои. Вот только безопасно доставить этот ключ на плату не так просто. Если его записать в прошивку, он окажется одинаковый на всех устройствах, а значит, будет у всех обладателей такой платы. Если же его вводить, то для этого потребуется клавиатура и экран.
SecurePair решает эту задачу, начиная с установки сопряжения и заканчивая передачей сообщений:
Платы согласуют между собой новый ключ, который пользователь утверждает, сравнивая короткие, сгенерированные платами коды.
Ключ сохраняется вместе с идентификатором платы и при её перезагрузке не аннулируется.
Сообщения шифруются, аутентифицируются и защищаются от атак воспроизведением. При этом передавать их можно как тем же способом, который использовался для сопряжения, так и любым другим.
Почему свет?
SecurePair неважно, как именно передаются пакеты. Здесь средой передачи выступает небольшой интерфейс, а в комплекте с библиотекой уже идут модули PacketLED, ESP-NOW и LoRa. Тем не менее она затачивалась под передачу данных с помощью света, поэтому именно работа через PacketLED позволит раскрыть её потенциал в полной мере.
Свет сложно перехватить. Он не проходит сквозь стены, а PacketLED работает между двумя направленными друг на друга светодиодами, расстояние между которыми во время сопряжения составляет всего несколько сантиметров. Прослушивание этого канала злоумышленнику ничего не даст, так как обмен ключами надёжно защищён. А для вклинивания в этот процесс ему потребовалось бы прямо на глазах у пользователя разместить в зоне действия светодиодов собственный источник света и датчик. В то же время сопряжение по радиоканалу уязвимо для атаки в радиусе десятков метров. Но в любом случае внедрение несогласованного устройства можно обнаружить по коду, который сравнивает пользователь. А использование именно световой коммуникации дополнительно усложняет организацию атаки типа MITM (человек посредине).
Никакого радиоизлучения. В процессе согласования ключей отсутствует радиосигнал, который можно было бы перехватить или заглушить.
Проверочный код показывается тем же светодиодом, который передавал ключи. Обе платы синхронно делают 20 коротких/длинных миганий через PacketLED. Пользователь наблюдает за их светодиодами и легко заметит любые расхождения в этом мигании. Ничего читать или вводить не нужно.
На каждой плате используется по одному светодиоду и резистору — никаких дополнительных микросхем. Этот же светодиод отвечает за отображение состояния платы.
Именно сравнение кодов обеспечивает безопасность любого сопряжения вне зависимости от используемого средства передачи. Световая индикация добавляет к этой схеме ещё один уровень защиты — необходимость физической близости — а также упрощает само сравнение. После сопряжения сообщения можно передавать через те же светодиоды (на небольшие расстояния и до 35 байт каждое) либо через радиоканал.
Сценарии использования
Сенсорные узлы и общий шлюз. Каждый узел сопрягается со шлюзом прямо на рабочем столе, после чего монтируется на место. Показания сенсоров передаются в зашифрованном виде через ESP-NOW или LoRa, что исключает возможность внедрения данных подставным узлом.
Удалённое управление и актуаторы: реле, приводы ворот, замки. Передаваемые команды аутентифицируются и принимаются один раз, поэтому перехватить и воспроизвести их позднее не выйдет.
Устройства без экрана или клавиатуры. Светодиода и кнопки достаточно для генерации ключа — нужно просто поднести устройство к контроллеру.
Замена платы в полевых условиях. Новая плата включает режим сопряжения с хабом автоматически при первом запуске. А плата, в памяти которой уже есть сохранённые устройства, будет сопрягаться с ними при старте с зажатой кнопкой или в любой другой момент, если это позволяет прошивка. В случае разрыва сопряжения ключ сохраняется.
Один хаб с несколькими устройствами. Каждая плата запоминает до 8 подключенных устройств — каждое со своим ключом.
Обучение. Это небольшая и вполне понятная реализация согласования ключей, коротких строк аутентификации (SAS) и шифрования с возможностью тестирования на ПК.
Что видит пользователь
Для начала нажмите кнопку на обеих платах, не превышая интервал в несколько секунд.
Светодиоды мигнут дважды, затем последует отсчёт из трёх миганий: расположите светодиоды рядом.
Светодиоды будут мигать, пока платы обмениваются ключами, потом выдержат паузу и начнут мигать уже синхронно, демонстрируя один и тот же код в виде 20 коротких/длинных вспышек.
Если код одинаковый, нажмите кнопку на обеих платах для подтверждения. Если разный, нажмите кнопку на одной из них.
Три коротких мигания и одно длинное — данные сопряжения сохранены. Быстрое мигание — сопряжение не удалось, никаких изменений не произошло.
Чтобы повторить сопряжение позже — для генерации нового ключа или подключения другой платы — удерживайте кнопку нажатой на каждой плате во время её включения или перезагрузки. Интервал между запуском плат не должен превышать 20 секунд. После этого сопряжение начнётся с пункта 2. Если кнопку не зажать, плата сразу запускается в обычном режиме.
Попытка подтвердить код коротким нажатием кнопки до завершения его мигания не сработает, так как в этот момент пользователь ещё не видел всю последовательность. Длинное нажатие приведёт к отмене сопряжения сразу после завершения мигания кода. В случае RGB-светодиода у каждой вспышки также будет свой свет, поэтому разница в коде отразится в нескольких миганиях подряд. В системе без светодиодов код можно выводить в виде шести цифр.
Функциональные возможности
Каналы связи |
PacketLED (один светодиод), ESP-NOW, LoRa (SX1276/78, ещё не тестировалось) |
Отображение кода |
Обычным светодиодом, RGB-светодиодом WS2812 или в виде шести цифр через любой канал вывода (Print) |
Отображение состояния |
Обычным светодиодом или RGB-светодиодом WS2812 |
Ввод |
Кнопкой (короткие и длинные нажатия) |
Хранение данных |
ESP32 NVS, в открытом виде или в зашифрованном с помощью зашитого в чипе ключа |
До нескольких сопряжённых устройств на плату. Каждая плата может хранить в памяти до 8 подключенных устройств (можно перенастроить на 16) и распознавать те, с которыми уже была сопряжена. В таком случае при повторном сопряжении просто заменяется ключ без добавления записи.
Безопасная замена ключей. Старый ключ продолжает действовать, пока новый не будет сохранён на обеих платах, а при очередном сопряжении автоматически устраняются несоответствия, возникшие при прошлой неудачной попытке. К примеру, при обрыве подключения из-за потери пакетов или отключения питания платы продолжат взаимодействовать по старому или новому ключу. Каждый возможный случай описан в PROTOCOL.md.
Отсутствие фоновой активности. После сопряжения SecurePair не выполняет никакого собственного кода, и
loop()ничего не вызывает. Для повторного сопряжения удерживайте кнопку при включении платы или запускайте процесс из приложения.Система полностью построена на интерфейсах. Любые компоненты — каналы передачи, механизмы отображения, способ ввода и средство хранения — можно заменить. Подробно об этом сказано в инструкции EXTENDING.md.
Криптография
Только стандартные, хорошо изученные примитивы:
Задача |
Алгоритм |
Стандарт |
Согласование ключей |
X25519 (Curve25519 Diffie-Hellman) |
RFC 7748 |
Хэширование, схема обязательств, запись сессии |
SHA-256 |
FIPS 180-4 |
Аутентификация сообщений при сопряжении |
HMAC-SHA256 |
RFC 2104 |
Формирование ключей |
HKDF-SHA256 |
RFC 5869 |
Шифрование и аутентификация сообщений |
AES-256-GCM |
FIPS 197, NIST SP 800-38D |
Реализации X25519 и AES-GCM подтягиваются из поставляемой с ESP32 библиотеки Mbed TLS и задействуют механизм AES самой платы. А вот SHA-256, HMAC и HKDF реализованы прямо внутри SecurePair и проверяются по эталонным значениям — как на ПК, так и на самом микроконтроллере через функцию CryptoSelfTest.
Как всё это совмещается:
Сопряжение. При каждом сопряжении генерируются новые ключи X25519. Одна из плат фиксирует свой ключ с помощью хэша до того, как увидит ключ второй платы. Это исключает возможность подобрать ключ постфактум, чтобы обманным путём обеспечить совпадение проверочных кодов. Именно этот механизм позволяет ограничиться 20-битным кодом (при каждой попытке шанс успешного взлома составляет 1 из 1 048 576). Аналогичный принцип реализован в механизме числового сравнения протокола Bluetooth.
Идентификация. У каждой платы также есть постоянный ключ-идентификатор. Во время сопряжения эти идентификаторы передаются в зашифрованном виде, и на их основе выполняется второй раунд шифрования по методу Диффи-Хеллмана, результат которого интегрируется в итоговый сохраняемый ключ. Таким образом каждая плата доказывает владение собственным идентификатором.
Запись сессии. Все передаваемые данные, включая версию протокола, хэшируются и подмешиваются в ключи с помощью HKDF. Любое изменение сообщения при передаче или понижение версии протокола приведут к генерации разных ключей, и сопряжение провалится.
Подтверждение сопряжения. Операции подтверждения на обеих платах защищены криптографической подписью (HMAC). Плата сохраняет новый ключ только после подтверждения пользователем, и удаляет старый, только после получения нового от другой платы.
Сообщения. Для каждого направления передачи используется собственный ключ AES-256-GCM, сформированный из основного, который был сохранён. В качестве 96-битного одноразового случайного числа (nonce) используется значение счётчика загрузок из флеш-памяти вместе со значением счётчика сообщений. Это полностью исключает его повторное использование, в том числе после перезагрузки платы. Каждое сообщение содержит 128-битный проверочный тег. Принимающая плата обрабатывает каждое сообщение один раз, а после перезагрузки с помощью механизма «запрос-ответ» (challenge-response) убеждается, что входящее сообщение не является повтором.
Защита сохранённых ключей. После того, как в микросхему зашивается eFuse-ключ, механизм NvsPairStorage начинает шифровать каждую сохраняемую запись с помощью AES-256-GCM. Созданный ключ хранилища с помощью HKDF формируется из хэша HMAC, который вычисляется периферийным блоком на основе eFuse-ключа. Такую запись нельзя прочесть извне, изменить, переместить в другую ячейку памяти или на другую плату, а при удалении плата о ней просто забывает.
Криптографическая гигиена. Проверочные теги сравниваются за фиксированное время, слабые ключи сразу отбраковываются, а временные секреты удаляются из памяти сразу после использования. Случайные числа берутся из встроенного в ESP32 генератора, а для надёжности к ним через SHA-256 подмешивается естественный световой шум, пойманный светодиодом.
От чего защиты нет:
От наивного пользователя, который подтверждает сопряжение не глядя.
От непосредственного считывания флеш-памяти. Если в чипе не будет выжжен мастер-ключ, злоумышленник сможет получить ключи сопряжения. Если же мастер-ключ присутствует, то ничего полезного он не получит — исключением станет сценарий, в котором у него будет возможность выполнить на этом чипе собственный код (читайте раздел «Шифрование сохраняемых ключей»).
От прочтения сообщений, перехваченных злоумышленником ранее, так как механизма прямой секретности (forward secrecy) здесь нет. Рабочий ключ сообщений сохраняется, поэтому любой, кто его получит, сможет расшифровать все сообщения, которые были записаны ранее. Чтобы заменить ключ, нужно повторить сопряжение.
Этот протокол ещё не проходил ревью независимым криптографом. Для удобства проведения такого ревью я в разделе PROTOCOL.md дал полное описание, включая каждый возможный сценарий отказа. Если по результатам анализа вы решите сообщить об уязвимости, сделайте это в соответствии с инструкцией из SECURITY.md.
Что потребуется
Плата ESP32 с ядром Arduino-ESP32 версии 3.x. В версии 3.3.12 код успешно компилируется под ESP32, ESP32-C3 и ESP32-C6. Учтите, что функция шифрования ключей в памяти заработает только на чипах со встроенным модулем HMAC: ESP32-S2, S3, C3, C5, C6, H2 и P4.
Кнопка или любой другой способ ответить системе «да» или «нет». Важно: не вешайте её на конфигурационный пин загрузки вроде BOOT. Если привязать кнопку конкретно к нему, то при её удержании во время включения плата будет уходить в режим ожидания загрузки прошивки, а не запускать ваше ПО.
Для сопряжения с использованием светодиодов потребуется PacketLED версии 1.1.0 или новее, а также по одному светодиоду и резистору на каждую плату. Для использования LoRa нужна библиотека LoRa от Sandeep Mistry.
Установка
В Arduino IDE откройте Library Manager, найдите SecurePair и кликните Install. IDE предложит попутно установить PacketLED; при желании также добавьте LoRa.
При использовании PlatformIO добавьте SecurePair в файл platformio.ini (PacketLED подтянется автоматически):
lib_deps = toggio/SecurePair
Также можете скачать репозиторий в виде ZIP-файла и добавить его через меню: Sketch > Include Library > Add .ZIP Library.
Использование
Сопряжение с помощью светодиодов и передача сообщений через ESP-NOW
Потребуется две платы ESP32 — каждая с кнопкой и светодиодом, подключенным к выводам, закреплённым за PacketLED. Больше ничего:
#include <PacketLED.h> #include <SecurePairLed.h> #include <transport/EspNowTransport.h> ArduinoLedPhy phy(32, 33); // Анод светодиода (контакт ADC1), катод; для ESP32-C3/C6: 0, 1 PacketLED led(phy); PacketLedTransport optical(led); // Сопряжение с помощью светодиодов. EspNowTransport radio; // Передача сообщений через ESP-NOW. LedDisplay display(optical); // Отображение кода и статуса платы одним и тем же светодиодом. NvsPairStorage storage; // При перезагрузке ключи сохраняются. SecurePair pairing(optical, storage, display, display); SecureLink channel(pairing, radio); // Канал для передачи зашифрованных сообщений. ButtonInput button(13); // Кнопка между GPIO13 и GND; для ESP32-C3/C6 это пин 3 void setup() { button.begin(); led.begin(); pairing.begin(); pairing.provision(button); // При первом запуске или удержании кнопки во время старта происходит сопряжение. radio.begin(); // Радиомодуль запускается после сопряжения, в противном случае WiFi-сигнал создавал бы помехи для светодиода. channel.sync(); } void loop() { // Ваш код. В качестве примера короткое нажатие отправляет «hello» каждой подключенной плате... if (button.read() == PairButton::Short) { PeerInfo peer; for (size_t i = 0; pairing.peerInfo(i, peer); ++i) channel.send(peer.id, "hello"); } // ...а этот блок отвечает за приём сообщений: uint8_t buf[256]; PeerId from; int n = channel.receive(buf, sizeof(buf), from); if (n > 0) { // С платы from принято n байт, которые были расшифрованы и проверены. } }
Вот и весь код сопряжения:
Метод
provision()выполняет сопряжение при первом запуске и всякий раз, когда плата стартует при зажатой кнопке. В этом случае генерируется новый ключ как для известной платы, так и для подключаемой впервые. В противном случае функция сразу завершается.loop()с сопряжением никак не связан, здесь можете использовать кнопку на своё усмотрение.radio.begin()идёт строго после сопряжения, так как PacketLED отсчитывает время миганий вплоть до микросекунд, и помехи со стороны WiFi могут сбивать этот тайминг.Чтобы активировать сопряжение прямо в процессе работы, вызовите
handle()вloop(). Если долго удерживать кнопку в первые 10 с после запускаbegin(), в фоновом режиме запустится сопряжение, иhandle()будет регистрировать нажатия. А чтобы запускать подключение вообще в любой момент — например, из меню — установите параметрPairConfig::policyнаAlwaysи вызовитеrequestPairing().
Более подробно всё описано в API.md. В качестве примера LedPairEspNowLink как раз использован этот скетч — только с дополнительной световой индикацией на принимающей плате, которая срабатывает при каждом нажатии кнопки на передатчике.
Использование других каналов передачи
С помощью той же библиотеки можно реализовать сопряжение через ESP-NOW с передачей сообщений методом LoRa без дополнительных светодиодов. Промигать код в этом случае можно собственными светодиодами платы либо просто вывести в текстовом виде:
#include <LoRa.h> #include <SecureLink.h> #include <SecurePair.h> #include <storage/NvsPairStorage.h> #include <transport/EspNowTransport.h> #include <transport/LoRaTransport.h> #include <ui/ButtonInput.h> #include <ui/GpioLedSink.h> #include <ui/LedDisplay.h> using namespace securepair; EspNowTransport radio; // Для сопряжения. LoRaTransport lora; // Для передачи сообщений. GpioLedSink statusLed(LED_BUILTIN); // Либо укажите вывод внешнего светодиода. LedDisplay display(statusLed); // Отображение кода и статуса платы её собственным светодиодом. NvsPairStorage storage; SecurePair pairing(radio, storage, display, display); SecureLink channel(pairing, lora); ButtonInput button(13); void setup() { button.begin(); statusLed.begin(); LoRa.begin(868E6); // Укажите собственную частоту и выводы модуля LoRa. lora.begin(); radio.begin(); pairing.begin(PairConfig::forRadio()); // Паузы короче, чем при использовании светодиодов. pairing.provision(button); channel.sync(); } void loop() { // Здесь прописываем channel.send() и channel.receive(), как в примере выше. }
При использовании радиоканала дополнительного фактора защиты в виде обязательной физической близости уже нет. Любой злоумышленник в радиусе приёма сигнала может попытаться вклиниться в канал связи, и остановит его только необходимость сопоставления кодов. Поэтому выводите проверочные коды так, чтобы пользователю было удобно их сравнить.
Шифрование сохраняемых ключей
Модуль NvsPairStorage хранит ключи сопряжения в открытом виде, пока чип не получит собственный мастер-ключ. При хранении ключей в таком виде любой, кто получит прямой доступ к плате, сможет считать их через USB с помощью esptool. К тому же, полученный дамп флеш-памяти будет работать и на другой плате. На чипах с аппаратным блоком HMAC (ESP32-S2, S3, C3, C5, C6, H2 и P4) ключ чипа предотвращает обе эти угрозы следующим образом:
Загрузите пример ChipKeySetup на каждую плату, введите команду
burnи подтвердите действие. Так вы запишете случайный 256-битный ключ в один из eFuse-блоков (в блок KEY5, если он не занят). Ни одна программа и ни один инструмент не смогут его считать — использовать этот ключ способен только блок HMAC самого чипа. Операция записи в eFuse-блоки является необратимой.Больше ничего в скетчах менять не надо. Начиная со следующего запуска, модуль
NvsPairStorageбудет запечатывать каждую запись по алгоритму AES-256-GCM с помощью ключа, созданного на основе ключа чипа. После этого ключи сопряжения перестанут сохраняться открыто в памяти, и вытащить их из дампа этой памяти на другой плате тоже не выйдет. В приведённых мной примерах скетчей при запуске выводится предупреждение, если ключи хранятся в открытом виде.
NvsPairStorage storage; // Шифрование включается с момента создания мастер-ключа. NvsPairStorage storage(Encryption::Required); // Без шифрования работа блокируется (для готовых продуктов).
При установке Encryption::Required плата без собственного ключа вообще откажется выполнять сопряжение. В любом случае, если плата не сможет расшифровать зашифрованные данные (из-за повреждения или скопированные с другого чипа), она просто заблокирует дальнейшую работу, но уже точно не откатится к хранению ключей в открытом виде.
Записывайте мастер-ключ до первого сопряжения устройств. Если плата до этого подключалась, процесс придётся повторить. В этом случае все данные, которые были сохранены в открытом виде, не импортируются, а удаляются. Так сделано потому, что любой, кто имел доступ к записи во флеш-память, мог эти ключи подменить. Но при этом механизм NVS будет физически хранить старые копии данных в памяти до тех пор, пока ему не потребуется перезаписать эту область. Чтобы окончательно их стереть, выполните однократную очистку флеш-памяти: установите параметр Tools > Erase All Flash Before Sketch Upload в положение Enabled, залейте скетч, а затем переключите обратно в Disabled.
Имейте в виду, что описанный механизм не оградит вас от злоумышленника, у которого есть возможность запустить на чипе собственный код — будь то через USB-интерфейс или путём прошивки другой программы. Загруженный им код сможет использовать мастер-ключ точно так же. Чтобы защититься от этой уязвимости, настройте режим Secure Boot через среду ESP-IDF, также отключив режим загрузки (download mode) и отладочный интерфейс JTAG.
Обратную запись старого дампа флеш-памяти SecurePair тоже не обнаруживает.
А на оригинальных чипах ESP32, не имеющих блока HMAC, необходимо использовать шифрование флеш-памяти вместе с шифрованием NVS. ↩
Каналы передачи
Канал передачи используется для пересылки пакетов данных. SecurePair использует один канал для сопряжения, и каждое соединение SecureLink использует по одному для передачи сообщений: по умолчанию тот же самый, но можно установить другой.
Канал передачи |
Радиус действия |
Размер сообщений |
Примечание |
PacketLedTransport |
От нескольких см до 2 м, в пределах прямой видимости (зависит от светодиодов). |
35 байт |
По одному светодиоду на плату; при сопряжении должны находиться в нескольких сантиметрах друг от друга. |
EspNowTransport |
Десятки метров. |
217 байт |
Нужен WiFi-канал, к которому будут подключены все платы. |
LoRaTransport |
Километры. |
216 байт |
На железе ещё не тестировался, и здесь нужно учитывать рабочий цикл (duty cycle). |
В список можно добавить абсолютно любой канал передачи, способный пересылать пакеты размером от 58 байт — будь то Bluetooth, проводной интерфейс UART или другой радиомодуль. В EXTENDING.md подробно объясняется, какие функции должен выполнять транспорт и с чего следует начинать его реализацию (то же самое расписано для экранов, кнопок и памяти). Внутреннее же устройство самой библиотеки описано в разделе ARCHITECTURE.md.
Примеры
Пример |
Сопряжение |
Сообщения |
Оборудование |
Provisioning |
LED |
Минимум: только сопряжение. |
|
LedOnly |
LED |
LED |
По одному светодиоду и кнопке на плату. |
LedPairEspNowLink |
LED |
ESP-NOW |
По светодиоду и кнопке на каждую плату; при нажатии кнопки светодиод на другой плате реагирует миганием. |
LedPairLoRaLink |
LED |
LoRa |
Дополнительно модуль SX1276/78. |
EspNowOnly |
ESP-NOW |
ESP-NOW |
Только кнопки. |
EspNowPairLoRaLink |
ESP-NOW |
LoRa |
Модуль SX1276/78 и кнопка. |
WaveshareC3Zero |
LED |
LED |
Плата Waveshare ESP32-C3-Zero: проверочный код и статус отображаются её собственным RGB-светодиодом. |
ChipKeySetup |
Записывает в чип мастер-ключ — для каждой платы это делается только один раз; с этого момента ключи сопряжения сохраняются в зашифрованном виде. |
||
CryptoSelfTest |
Проверяет корректность работы криптографических алгоритмов платы. |
Тесты
В каталоге extras/test лежит тест, который запускает две платы на ПК в виде потоков в симулированной среде передачи с искусственной потерей пакетов и подтверждений приёма. Тест охватывает следующие сценарии: сопряжение и повторное сопряжение; таймауты; стороннее изменение сообщений и их воспроизведение; сбои хранения данных; восстановление связи после незавершённого повторного сопряжения и двух неудачных повторных сопряжений подряд; внезапную перезагрузку одной из плат; искусственную задержку ответов на проверочные запросы и их дублирование; одновременное использование разных каналов передачи; работу программного слоя подтверждения, используемого при передаче через ESP-NOW и LoRa; отправку радиопакетов сверх допустимого размера; нажатие кнопки в процессе индикации кода и тайминги её удержания; а также работу хранилища с открытыми и зашифрованными данными, включая случаи повреждения, подмены, копирования и утери записей.
Корректность криптографических алгоритмов проверялась на соответствие значениям, вычисленным с помощью пакета Python cryptography.
Для запуска тестов потребуется Python и компилятор Zig языка C++. Сам процесс занимает около двух минут. Для Windows:
python -m pip install ziglang powershell extras/test/run_tests.ps1
В случае Linux или macOS, если у вас установлена PowerShell, выполните команду pwsh extras/test/run_tests.ps1. Либо скомпилируйте те же файлы напрямую:
python -m ziglang c++ -std=c++17 -O2 -I src -I extras/test extras/test/test_securepair.cpp src/*.cpp src/internal/*.cpp src/storage/*.cpp -o securepair_test && ./securepair_test
Устранение неполадок
Предложенные примеры выводят информацию о происходящем в Serial Monitor со скоростью 115 200 бод: при возникновении проблем проверяйте его в первую очередь.
Pairing: failed (no peer)/ Сбой сопряжения: подключаемый узел не найден. Запустите процесс сопряжения на обеих платах с интервалом не более 20 секунд. При использовании PacketLED держите светодиоды направленными друг на друга на расстоянии нескольких сантиметров и оградите их от прямых солнечных лучей или света мощных ламп. При работе через ESP-NOW обе платы должны быть подключены к одному каналу WiFi.
Платы показывают разные коды. Отклоните сопряжение длинным нажатием кнопки. Либо в процесс обмена кто-то вмешался, либо рядом происходит сопряжение других плат. Повторите попытку.
Pairing: failed (protocol error)/Сбой сопряжения из-за ошибки протокола. Обычно причина в том, что рядом происходит подключение другой пары плат. Переместитесь подальше и повторите попытку.Serial Monitor пуст, хотя плата (ESP32-C3 или C6) подключена через собственный USB-порт. Включите в настройках пункт Tools > USB CDC On Boot.
При запуске с зажатой кнопкой программа не загружается. Кнопка подключена к загрузочному выводу — например, BOOT. Подключите её к другому.
Keys in clear (see the ChipKeySetup example)/Ключи хранятся в открытом виде. Это нормально, пока вы не создадите для чипа мастер-ключ.Saved keys do not open/Сохранённые ключи не открываются. Содержимое флеш-памяти было скопировано с другой платы или повреждено. Полностью очистите её с помощью командыNvsPairStorage::factoryReset()или через опцию Erase All Flash Before Sketch Upload, после чего повторите сопряжение.После перезагрузки платы перестали принимать сообщения. Проверьте — в самом начале нужно вызывать функцию
SecureLink::sync(), а затем на обеих платах периодически вызывать методreceive(), который также выдаёт ответы на проверочные запросы от другой платы.
Ограничения
Пока поддерживаются только платы ESP32.
Если не установить мастер-ключ, ключи сопряжения сохраняются в открытом виде. В оригинальной версии ESP32 модуль HMAC отсутствует, поэтому на ней мастер-ключ создать нельзя. В таком случае включайте шифрование NVS и флеш-памяти.
После перезагрузки платы первое сообщение от каждого из подключенных устройств, которые не перезагружались, отклоняется, и в ответ им отправляется проверочный запрос. Эту процедуру выполняет функция
SecureLink::sync(), чтобы исключить потерю данных.В среде широкого вещания вроде ESP-NOW или LoRa о приёме сигнала сообщает любая плата в зоне действия. То есть, если в этой зоне находится больше двух плат, сообщение «доставлено» означает только, что его «кто-то получил».
Если плату сбросить до заводских настроек, сопряжённая плата будет хранить свою старую запись до выполнения
forget().Сопряжение с помощью светодиодов выполняется при выключенном радиомодуле, поэтому встроенный в ESP32 генератор случайных чисел не может выступать источником абсолютной случайности. Чтобы это компенсировать, библиотека с помощью алгоритма SHA-256 подмешивает шумы от светодиода и таймингов нажатия кнопки, но качество этих шумов не измерялось.
Сопутствующие работы
Идея сравнения коротких кодов, генерируемых двумя устройствами, не нова и уже освещалась в ряде публикаций:
S. Vaudenay, Secure Communications over Insecure Channels Based on Short Authenticated Strings, CRYPTO 2005: почему короткого кода будет достаточно, если обе стороны зафиксировали свои ключи.
J. M. McCune, A. Perrig, M. K. Reiter, Seeing-Is-Believing: Using Camera Phones for Human-Verifiable Authentication, IEEE Symposium on Security and Privacy 2005: использование визуального канала для аутентификации двух устройств.
R. Prasad, N. Saxena, Efficient Device Pairing Using "Human-Comparable" Synchronized Audiovisual Patterns, ACNS 2008: синхронное отображение устройствами одного и того же паттерна мигания и его сравнение пользователем.
SecurePair привносит в этот процесс комбинирование: один и тот же светодиод служит для передачи ключей и промигивания кода, каждая плата хранит постоянный идентификатор и несколько подключенных устройств, а сообщения могут передаваться через любой канал связи.
Помощь проекту
Проект SecurePair нацелен на развитие и будет признателен вам за возможный вклад в виде:
Отчётов о тестировании, особенно по использованию LoRa на реальных модулях.
Экспертного ревью протокола.
Реализации альтернативных каналов передачи с помощью модулей RadioLib, Bluetooth, UART.
Добавления поддержки экранов (OLED, e-paper) и адаптации библиотеки для других микроконтроллеров.
Если захотите помочь, открывайте Issue или присылайте пул-реквест.