
В предыдущих статьях мы довольно подробно описали устройство и принципы работы криптомикросхемы ATSHA204A, теперь давайте разберём несколько практических примеров работы с ней.
В IoT большинство датчиков, актуаторов и контроллеров работают по беспроводным протоколам связи, поэтому наши практические примеры будут посвящены задаче аутентификации удалённых беспроводных частей системы (например, датчиков умного дома).
В качестве примеров будет рассмотрена работа двух популярных беспроводных систем — nRF24 и LoRa. Начнём с nRF24.
Постановка задачи
Нам нужно будет создать тестовую систему, состоящую из «базы» и «датчика», связанных по беспроводному nRF24 каналу. Задача будет состоять в том, что базе нужно будет связаться с датчиком и удостовериться, что это именно «легитимный» датчик нашей экосистемы, а не клонированный хакером (чужой) датчик.
Решаться эта задача будет при помощи микросхем ATSHA204A, которые установлены на базе и датчике и соответствующим образом запрограммированы.
Оборудование
В качестве контроллеров для нашего эксперимента (и для базы и для датчика) будут использоваться беспроводные контроллеры uniSensors nRF24, которые имеют на борту всё необходимое:
- Микроконтроллер ATmega328
- Беспроводной модуль nRF24L01
- Микросхему ATSHA204A
- и прочие необходимые компоненты
Для работы с uniSensors nRF24 и компиляции скетчей в Arduino IDE в настройках нужно выбирать контроллер Arduino Pro Mini, Atmega328 (3.3V, 8MHz).
Алгоритм работы
Алгоритм работы, обеспечивающий аутентификацию датчика, будет следующим: на базе микросхемой ATSHA204A генерируется (качественное) случайное число (RNG), затем этой же микросхемой при помощи команды MAC (на основе случайного числа и секретного ключа в Data зоне EEPROM памяти ATSHA204A) вычисляется SHA-256 хеш (дайджест). Этот дайджест запоминается для последующего сравнения с ответом датчика.
Затем ранее сгенерированное случайное число по беспроводному nRF24 каналу отсылается датчику, который на его основе (при помощи идентично запрограммированной микросхемы ATSHA204A) также вычисляет SHA-256 хеш и отсылает его обратно базе для проверки и сравнения с заранее вычисленным на базе хешем.
Тот факт, что сгенерировать правильный хеш в ответ на случайное число может только «легитимный» датчик, имеющий в своём составе «правильно» запрограммированную микросхему ATSHA204A с уникальным ключом, является гарантией верной аутентификации датчика базой.
Вот графическое представление работы алгоритма аутентификации:

Более подробно об этом алгоритме и реализации его работы микросхемой ATSHA204A вы можете прочитать в предыдущих статьях этого цикла.
Базовый блок
В этой статье мы подробно рассмотрим работу базы как с криптографической частью, так и работу базы с беспроводной системой nRF24. Работу датчика мы разберём в следующей статье.
Как следует из условий задачи, базовый блок должен сгенерировать случайное число и отослать его датчику, затем сравнить вычисленный самостоятельно и полученный от датчика SHA-256 хеши. Это основной функционал базы, теперь подробнее разберём создание и работу скетча.
Код криптоалгоритма базы
Ниже приведён код криптоалгоритма базы из одной из предыдущих статей (там же вы можете ознакомиться с подробным описанием его работы). Здесь наша задача будет состоять в интеграции этого кода со скетчем беспроводной nRF24 связи.
/*
ATSHA204A Project (Base)
*/
#include <sha204_library.h>
const int sha204Pin = A3;
byte challenge[MAC_CHALLENGE_SIZE];
byte hash[MAC_CHALLENGE_SIZE];
byte retCode = 0;
atsha204Class sha204(sha204Pin);
void setup() {
Serial.begin(115200);
Serial.println("ATSHA204 Project (Base) start...");
makeRandom();
Serial.println("Random:");
printArray(challenge, 32);
Serial.println();
macChallenge();
Serial.println("Hash:");
printArray(hash, 32);
Serial.println();
}
// Print
void printArray(byte arr[], byte len) {
for (byte i = 0; i < len; i++) {
if (arr[i] < 16) {Serial.print('0');}
Serial.print(arr[i], HEX); Serial.print(' ');
}
}
// Random
void makeRandom() {
uint8_t response[RANDOM_RSP_SIZE];
uint8_t tx_buffer[12] = {0};
retCode = sha204.sha204m_random(tx_buffer, response, RANDOM_NO_SEED_UPDATE);
for (byte i = 0; i < 32; i++) {
challenge[i] = response[i];
}
}
// MAC
void macChallenge() {
uint8_t command[MAC_COUNT_LONG];
uint8_t response[MAC_RSP_SIZE];
retCode = sha204.sha204m_execute(SHA204_MAC,
0, 0,
MAC_CHALLENGE_SIZE, (uint8_t *) challenge,
0, NULL,
0, NULL,
sizeof(command), &command[0],
sizeof(response), &response[0]);
for (byte i = 0; i < 32; i++) {
hash[i] = response[i];
}
}
void loop() {
}
Подсистема nRF24
Работа с беспроводной системой nRF24 является сама по себе большой и сложной темой и выходит за рамки этой статьи, поэтому здесь мы только приведём готовый nRF24 скетч базы (без крипто-функций), а затем создадим финальный код скетча, который будет сочетать в себе и крипто-функционал (базы) и возможности беспроводной nRF24 связи.
Для работы этого скетча и вообще для работы с nRF24 подсистемой вам понадобится библиотека nRF24.
/*
nrf24 Base
*/
#include <SPI.h>
#include "RF24.h"
#define RF_PIN A1
RF24 radio(6, 7); // CE, CSN
byte addresses[][6] = {"1Node", "2Node"};
#define PACKET_MAX_BYTES 32
byte buffRx[PACKET_MAX_BYTES];
byte buffTx[PACKET_MAX_BYTES] = {
0x23, 0x47, 0x3B, 0x1D, 0x89, 0xEA, 0x07, 0x80,
0x07, 0x5A, 0xEE, 0xD9, 0xEC, 0x8D, 0xF5, 0x68,
0x88, 0xAC, 0x3B, 0x36, 0x50, 0xF4, 0x03, 0xA4,
0xDF, 0xB3, 0x38, 0xA7, 0x16, 0xBA, 0x4A, 0xF5
};
void setup() {
Serial.begin(115200);
Serial.println(F("RF24 Base start..."));
pinMode(RF_PIN, OUTPUT);
digitalWrite(RF_PIN, LOW);
delay(500);
radio.begin();
radio.setPALevel(RF24_PA_MIN);
radio.openWritingPipe( addresses[1]);
radio.openReadingPipe(1, addresses[0]);
}
void printArray(byte arr[], byte len) {
for (byte i = 0; i < len; i++) {
if (arr[i] < 16) {Serial.print('0');}
Serial.print(arr[i], HEX); Serial.print(' ');
}
}
bool match(byte arr1[], byte arr2[], byte len) {
bool res = true;
for (byte i = 0; i < len; i++) {
if (arr1[i] != arr2[i]) {
res = false;
break;
}
}
return res;
}
void loop() {
radio.stopListening();
Serial.print(F("Sending..."));
unsigned long start_time = micros();
if (!radio.write(&buffTx, PACKET_MAX_BYTES)) {
Serial.println(F("failed"));
}
radio.startListening();
unsigned long started_waiting_at = micros();
boolean timeout = false;
while (!radio.available()) {
if (micros() - started_waiting_at > 200000 ) { // 200ms
timeout = true;
break;
}
}
if (timeout) {
Serial.println(F("Failed response."));
} else {
radio.read(&buffRx, PACKET_MAX_BYTES);
Serial.print(F("Response: ")); printArray(buffRx, 32); Serial.print(F(" "));
if (match(buffTx, buffRx, 32)) {Serial.println(F("Match!"));}
else {Serial.println(F("Not match!"));}
}
delay(3000);
}
Этот скетч выполняет всего несколько простых функций: отсылает тестовый 32-байтовый массив buffTx на датчик и принимает от датчика ответный, тоже 32-байтовый массив. Затем сравнивает эти два массива и выносит вердикт «совпадает» (Match!) или «не совпадает» (Not match!).
Вначале мы подключаем необходимые библиотеки для работы с nRF24 частью.
#include <SPI.h> #include "RF24.h"
Затем определяем пин включения питания радиомодуля на контроллерах uniSensors nRF24.
#define RF_PIN A1
Создаём объект для управления модулем nRF24L01 и указываем номера GPIO для подключения его CE и CSN выводов.
RF24 radio(6, 7); // CE, CSN
Задаём адреса для связи базы и датчика.
byte addresses[][6] = {"1Node", "2Node"};
И тестовый 32-байтовый массив для отсылки датчику.
byte buffTx[PACKET_MAX_BYTES] = {
0x23, 0x47, 0x3B, 0x1D, 0x89, 0xEA, 0x07, 0x80,
0x07, 0x5A, 0xEE, 0xD9, 0xEC, 0x8D, 0xF5, 0x68,
0x88, 0xAC, 0x3B, 0x36, 0x50, 0xF4, 0x03, 0xA4,
0xDF, 0xB3, 0x38, 0xA7, 0x16, 0xBA, 0x4A, 0xF5
};
Затем, в функции setup(), подаём питание на радиомодуль uniSensors nRF24.
pinMode(RF_PIN, OUTPUT); digitalWrite(RF_PIN, LOW); delay(500);
Включаем радиомодуль.
radio.begin();
Устанавливаем минимальную мощность nRF24 передатчика.
radio.setPALevel(RF24_PA_MIN);
И настраиваем адреса для приёма и передачи данных.
radio.openWritingPipe( addresses[1]); radio.openReadingPipe(1, addresses[0]);
Далее в цикле loop() посылаем тестовый массив (в эфир) на датчик.
void loop() {
radio.stopListening();
Serial.print(F("Sending..."));
unsigned long start_time = micros();
if (!radio.write(&buffTx, PACKET_MAX_BYTES)) {
Serial.println(F("failed"));
}
Принимаем данные от датчика, выводим на печать в Serial и сравниваем исходный и полученный массивы и сообщаем об их совпадении или не совпадении.
radio.read(&buffRx, PACKET_MAX_BYTES);
Serial.print(F("Response: ")); printArray(buffRx, 32); Serial.print(F(" "));
if (match(buffTx, buffRx, 32)) {Serial.println(F("Match!"));}
else {Serial.println(F("Not match!"));}
Код:
radio.startListening();
unsigned long started_waiting_at = micros();
boolean timeout = false;
while (!radio.available()) {
if (micros() - started_waiting_at > 200000 ) { // 200ms
timeout = true;
break;
}
}
отвечает за включение (в цикле) прослушивания эфира и прерывание этого прослушивания по таймауту (если датчик по какой-то причине не отвечает).
Скетч рассчитан на работу с «ответной» частью (скетчем датчика), который мы рассмотрим в следующей статье.
Далее мы создадим из этих двух (криптографического ATSHA204A и беспроводного nRF24) объединённый финальный скетч базы и подробно разберём его работу.
Скетч nRF24 ATSHA204A Base
Ниже представлен полный код скетча nRF24 ATSHA204A Base, объединяющий в себе криптографическую, беспроводную и функциональную части.
/*
nRF24 ATSHA204A Base
*/
#include <SPI.h>
#include "RF24.h"
#include <sha204_library.h>
// nRF24
#define RF_PIN A1
RF24 radio(6, 7); // CE, CSN
byte addresses[][6] = {"1Node", "2Node"};
#define PACKET_MAX_BYTES 32
byte buffRx[PACKET_MAX_BYTES];
// ATSHA204A
const int sha204Pin = A3;
byte challenge[MAC_CHALLENGE_SIZE];
byte hash[MAC_CHALLENGE_SIZE];
byte retCode = 0;
atsha204Class sha204(sha204Pin);
void setup() {
Serial.begin(115200);
Serial.println("nRF24 ATSHA204 (Base) start...");
// ATSHA204A
makeRandom();
Serial.print(F("Random: ")); printArray(challenge, 32); Serial.println();
macChallenge();
Serial.print(F("Hash: ")); printArray(hash, 32); Serial.println();
Serial.println();
// nRF24
pinMode(RF_PIN, OUTPUT);
digitalWrite(RF_PIN, LOW);
delay(500);
radio.begin();
radio.setPALevel(RF24_PA_MIN);
radio.openWritingPipe( addresses[1]);
radio.openReadingPipe(1, addresses[0]);
}
// Print
void printArray(byte arr[], byte len) {
for (byte i = 0; i < len; i++) {
if (arr[i] < 16) {Serial.print('0');}
Serial.print(arr[i], HEX); Serial.print(' ');
}
}
// Compare
bool match(byte arr1[], byte arr2[], byte len) {
bool res = true;
for (byte i = 0; i < len; i++) {
if (arr1[i] != arr2[i]) {
res = false;
break;
}
}
return res;
}
// Random
void makeRandom() {
uint8_t response[RANDOM_RSP_SIZE];
uint8_t tx_buffer[12] = {0};
retCode = sha204.sha204m_random(tx_buffer, response, RANDOM_NO_SEED_UPDATE);
for (byte i = 0; i < 32; i++) {
challenge[i] = response[i + 1];
}
}
// MAC
void macChallenge() {
uint8_t command[MAC_COUNT_LONG];
uint8_t response[MAC_RSP_SIZE];
retCode = sha204.sha204m_execute(SHA204_MAC,
0, 0,
MAC_CHALLENGE_SIZE, (uint8_t *) challenge,
0, NULL,
0, NULL,
sizeof(command), &command[0],
sizeof(response), &response[0]);
for (byte i = 0; i < 32; i++) {
hash[i] = response[i + 1];
}
}
void loop() {
radio.stopListening();
Serial.print(F("Sending: "));
printArray(challenge, 32);
unsigned long start_time = micros();
if (!radio.write(&challenge, PACKET_MAX_BYTES)) {
Serial.print(F("failed"));
}
Serial.println();
radio.startListening();
unsigned long started_waiting_at = micros();
boolean timeout = false;
while (!radio.available()) {
if (micros() - started_waiting_at > 200000 ) { // 200ms
timeout = true;
break;
}
}
if (timeout) {
Serial.println(F("Failed response."));
} else {
radio.read(&buffRx, PACKET_MAX_BYTES);
Serial.print(F("Response: ")); printArray(buffRx, 32); Serial.println();
Serial.print(F("Hash: ")); printArray(hash, 32); Serial.println();
if (match(hash, buffRx, 32)) {Serial.println(F("Match!"));}
else {Serial.println(F("Not match!"));}
Serial.println();
}
delay(3000);
}
Теперь разберём работу тех частей скетча, которые мы ещё не осветили (в этой или предыдущих статьях).
В финальной версии скетча мы объявляем 32-байтовый массив для приёма хеша (дайджеста) от датчика нашей IoT экосистемы.
#define PACKET_MAX_BYTES 32 byte buffRx[PACKET_MAX_BYTES];
Объявляем 2 массива для сгенерированного случайного числа (challenge) и вычисленного на базе хеша SHA-256 (hash).
byte challenge[MAC_CHALLENGE_SIZE]; byte hash[MAC_CHALLENGE_SIZE];
Генерируем (при помощи ATSHA204A) и выводим на печать случайное 32-байтовое число.
// ATSHA204A
makeRandom();
Serial.print(F("Random: ")); printArray(challenge, 32); Serial.println();
Вычисляем (при помощи ATSHA204A) и выводим на печать 32-байтовый SHA-256 хеш (дайджест).
macChallenge();
Serial.print(F("Hash: ")); printArray(hash, 32); Serial.println();
Serial.println();
В цикле посылаем сгенерированное случайное число на датчик.
void loop() {
radio.stopListening();
Serial.print(F("Sending: "));
printArray(challenge, 32);
unsigned long start_time = micros();
if (!radio.write(&challenge, PACKET_MAX_BYTES)) {
Serial.print(F("failed"));
}
Serial.println();
Принимаем и выводим на печать присланный датчиком хеш.
radio.read(&buffRx, PACKET_MAX_BYTES);
Serial.print(F("Response: ")); printArray(buffRx, 32); Serial.println();
Для наглядности также выводим вычисленный базой хеш.
Serial.print(F("Hash: ")); printArray(hash, 32); Serial.println();
И, наконец, сравниваем сгенерированный базой и полученный от датчика SHA-256 хеши и выносим вердикт о «легитимности» беспроводного датчика.
if (match(hash, buffRx, 32)) {Serial.println(F("Match!"));}
else {Serial.println(F("Not match!"));}
Вот результат работы нашего финального скетча:

Примечание: в этом эксперименте использовались микросхемы ATSHA204A (установленные на платах uniSensors) без дополнительной прошивки и блокировки зон памяти, поэтому вместо реальных случайных чисел вы видите тестовые последовательности FF FF 00 00 FF FF 00 00, выдаваемые микросхемой ATSHA204A в этом режиме. Если вы воспользуетесь методикой программирования микросхем ATSHA204A, описанной в статьях этого цикла, то сможете записать любой секретный ключ в нулевую (0) ячейку Data зоны вашей микросхемы и (без каких-либо переделок этого скетча) получить полнофункциональную аутентификацию при помощи ATSHA204A в вашей беспроводной nRF24 сети.
Заключение
Здесь мы рассмотрели только первую часть примера криптографической аутентификации при помощи микросхемы ATSHA204A в беспроводной nRF24 сети (работу базы), в следующей статье мы разберём работу ответной части — беспроводного nRF24 датчика.
Ссылки по теме
ATSHA204 - Библиотека и примеры
ATSHA204A - Чтение зоны конфигурации 1
ATSHA204A - Чтение зоны конфигурации 2
ATSHA204A - Чтение зоны конфигурации 3
ATSHA204A - Запись конфигурации 1
ATSHA204A - Запись конфигурации 2
ATSHA204A - Запись конфигурации 3
ATSHA204A - Запись конфигурации 4
ATSHA204A - Работа в режиме Config Lock
ATSHA204A - Работа с зонами памяти
ATSHA204A - Чтение Data и OTP зон памяти
ATSHA204A - Аутентификация. Базовый блок
ATSHA204A - Криптография и команды
ATSHA204A - nRF24 аутентификация. База
ATSHA204A - nRF24 аутентификация. Датчик



