С одной стороны мне все равно, что в многоэтажном доме кто-то сможет «подслушать» температуру на кухне (а то и позвонить с вопросом «Что готовишь?»), но с другой стороны не хотелось бы, чтобы кто-то смог (хоть бы и теоретически) управлять силовыми нагрузками (включить любимую кофе-машину, например). Хочется быть чуть более уверенным, что комманда исходит от моего управляющего устройства, а не подставного (в криптографии, это известно как «проверка подлинности»).
Реализовать такой подход сравнительно просто…
Схема взаимодействия управляющего и исполнительного устройства построена на «подписи» случайной фразы внутренним ключом, известным обоим устройствам. Наглядней это можно продемонстрировать так:
Реализовать можно с помощью трех функций. Одна для генерации случайной фразы:
byte alphabet[] = { 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a }; // a, b, c, d, etc
void getPhrase(byte *phrase) {
for (int i = 0; i < 8; i++) {
phrase[i] = alphabet[random(0, 26)];
phrase[i + 1] = '\0';
}
}
Вторая собственно создание MD5-хеша (MD5 library for the Arduino):
#include <MD5.h>
byte sessionPhrase[9];
byte salt[] = { 0x4c, 0x39, 0x78, 0x36, 0x73, 0x4c, 0x39, 0x78 }; // L9x6sL9x - внутренний ключ
void signPhrase(byte *phrase, char *signedPhrase) {
byte localPhrase[17];
for (int i = 0; i < 16; i++) {
if (i < 8) localPhrase[i] = salt[i];
else localPhrase[i] = sessionPhrase[i - 8];
localPhrase[i + 1] = '\0';
}
unsigned char* hash = MD5::make_hash((char *)localPhrase);
char *md5str = MD5::make_digest(hash, 16);
for (int i = 0; i < 16; i++) {
signedPhrase[i] = md5str[i];
signedPhrase[i + 1] = '\0';
}
free(hash);
free(md5str);
}
Третья для сверки «подписанной» фразы инициатора с «самоподписанной» фразой исполнителя:
bool compareSignedPhrases(char *signedPhrase) {
if (strcmp(signedPhrase, (char *)sessionMD5Phrase) == 0) {
return true;
}
return false;
}
#include <SPI.h>
#include <MD5.h>
#define DEBUG 1
byte sessionPhrase[9];
char sessionMD5Phrase[17];
byte salt[] = { 0x4c, 0x39, 0x78, 0x36, 0x73, 0x4c, 0x39, 0x78 }; // L9x6sL9x — внутренний ключ
byte alphabet[] = { 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a }; // a, b, c, d, etc
void setup() {
Serial.begin(115200);
}
void loop() {
/* Генерируем случайную фразу */
getPhrase(sessionPhrase);
signPhrase(sessionPhrase, sessionMD5Phrase); // Запоминаем для дальнейшего сравнения
/* Подписываем */
char signedPhrase[17];
signPhrase(sessionPhrase, signedPhrase);
#ifdef DEBUG
Serial.print(«signedPhrase: „);
Serial.println((char *)signedPhrase);
Serial.print(“copmpare: „);
Serial.println(compareSignedPhrases(signedPhrase));
Serial.println(“»);
#endif
delay(1000);
}
void getPhrase(byte *phrase) {
for (int i = 0; i < 8; i++) {
phrase[i] = alphabet[random(0, 26)];
phrase[i + 1] = '\0';
}
#ifdef DEBUG
Serial.print(«sessionPhrase: „);
Serial.println((char *)sessionPhrase);
#endif
}
void signPhrase(byte *phrase, char *signedPhrase) {
byte catPhrase[17];
for (int i = 0; i < 16; i++) {
if (i < 8) catPhrase[i] = salt[i];
else catPhrase[i] = sessionPhrase[i — 8];
catPhrase[i + 1] = '\0';
}
#ifdef DEBUG
Serial.print(“catPhrase: „);
Serial.println((char *)catPhrase);
#endif
unsigned char* hash = MD5::make_hash((char *)catPhrase);
char *md5str = MD5::make_digest(hash, 16);
for (int i = 0; i < 16; i++) {
signedPhrase[i] = md5str[i];
signedPhrase[i + 1] = '\0';
}
free(hash);
free(md5str);
}
bool compareSignedPhrases(char *signedPhrase) {
if (strcmp(signedPhrase, (char *)sessionMD5Phrase) == 0) {
return true;
}
return false;
}
Такой подход можно исполнять как в своих “велосипедах», так и в других фреймворках (таких как MySensors) и дает дополнительный плюсик к безопасности Вашего Интернета вещей.
Кстати, аутентификация будет в MySensors, на текущий момент она реализована в development-версии.
P.S.: Если будет интересно сообществу — сделаю update заметки на примере того же MySensors с примерами листингов для Serial Gateway и Relay…
Комментарии (10)
Ivan_83
08.06.2015 19:09+5Ну кто же так пишет!?
" for (int i = 0; i < 16; i++) {
if (i < 8) localPhrase[i] = salt[i];
else localPhrase[i] = sessionPhrase[i — 8];
localPhrase[i + 1] = '\0';
}" — это memcpy два раза.
«localPhrase[i + 1] = '\0';» — выносится за цикл, те два memcpy.
memcpy(localPhrase, salt, 8);
memcpy((localPhrase + 8), sessionPhrase, 8);
localPhrase[16] = 0;
«for (int i = 0; i < 16; i++) {
signedPhrase[i] = md5str[i];
signedPhrase[i + 1] = '\0';
}» — опять такое же порно: memcpy и зануление после него.
«if (strcmp(signedPhrase, (char *)sessionMD5Phrase) == 0)» — МЕГАПОРНО!
1. с чего вы взяли что там будет ноль на конце?
2. нужно было хотя бы memcmp() заюзать с фиксированным размером, тайминг атака осталось бы, зато оно бы не падало.
3. нужно защищатся от тайминг атак: cryptocoding.net/index.php/Coding_rules#Compare_secret_strings_in_constant_time
По крипте.
Я не большой спец, но дописывать одно к другому не очень хорошая идея, примерно так работают атаки на расширение.
Чтобы не парится придумали hmac поверх хэш функций.
По протоколу.
Порно!
Не нужно рассчитывать что кто то будет присылать строки, про строковые функции вообще нужно забыть.
Правильный вариант будет: подключаемся, сразу от сервера=исполнительного девайса прилетает рандом байт на 16-64 и хэш.
Мы загоняем в hmac и убеждаемся что это реально наш сервер/девайс.
Берём нашу команду и этот рандом (как данные), загоняем их в hmac, и этому же hmac скармливаем наш секретный общий ключ.
Получившееся (команду + хэш) отправляем на сервер.
Сервер получает команду и хэш. К команду+рандом и секретный ключ скармливает в hmac и получает хэш, сравнивает его с тем что в пакете.
Тут лежат мои md5, sha1, sha2 (и chacha20, ECDSA+ГОСТ Р34.10-2001, ГОСТ Р34.10-2012): netlab.linkpc.net/download/software/SDK/core/include
там и hmac варианты и селфтесты есть.
Достаточно один раз заинклюдить чтобы начать пользоваться.
А заметке надо делать не апдейт а переписывать с нуля :)zazhim103 Автор
08.06.2015 20:01Спасибо за дельные советы! Многим нравится именно обсуждения. Это и есть формат Хабра!
Не нужно рассчитывать что кто то будет присылать строки
Возможно Иван не совсем правильно понял, но общение между Инициатором и Исполнителем происходит по Радио-каналу именно по строковым данным (в заметке упоминается не один раз фраймворк MySensors), именно в связи с этим такой алгоритм действий, который можно применить и к другим системам.
К тому же на каждое обращение генерируется новая «случайная» фраза, что позволит обойти «перебор», описанный в комментарии выше…areht
10.06.2015 01:51> К тому же на каждое обращение генерируется новая «случайная» фраза, что позволит обойти «перебор»
Это всё замечательно, только у вас защиты от подмены сообщений нет. Достаточно «включи свет в ванной» на «убей всех человеков» заменить и дальше само всё подпишется.
Вообще, криптография — это не просто пару хешей посчитать. Писать самому… С тем же эффектом (и гораздо проще) прогнать XORом сообщение по ключу
zazhim103 Автор
10.06.2015 11:15Достаточно «включи свет в ванной» на «убей всех человеков» заменить...
Естественно, исполнитель имеет свой ограниченный функционал и игнорирует команды, которые в него не заложены. Например в том же MySensors можно проверять тип входящего сообщения (message.type == V_LIGHT) и адресность конкретного сенсора (message.sensor == PRIMARY_CHILD_ID)…areht
10.06.2015 13:58> и игнорирует команды, которые в него не заложены
естественно я не рассчитываю, что лампочка будет варить кофе
Vendict
10.06.2015 06:38+1Но тут ещё одна проблема. К примеру, на Arduino, использование простейшего md5 добавляет 13К к коду, плюс столько же библиотека взаимодействия с устройством и в итоге нам останется всего 10К для нашей программы. При реализации взаимодействия с Arduino через EtherCard я думал об использовании простейшей подписи, но когда увидел размеры кода для подписи — отказался от данного решения. Нужно придумывать свою упрощённую реализацию со скидкой на «Неуловимого Джо».
zazhim103 Автор
10.06.2015 11:25Да, конечно, здесь по большей части обозначен «вектор движения». Можно экпериментировать, например с DES and 3DES Encryption Library.
vedenin1980
Но md5 для целей криптографии не рекомендуют использовать уже давным давно, так как он достаточно легко вскрывается перебором. Я бы на вашем месте лучше попробовал прикрутить библиотеку с SHA-2 или чем-то вроде того.
zazhim103 Автор
Воспримите это как некий вектор движения начинающего в сторону уменьшения паранои. Да и в плейграунде проекта Arduino одной из первых указана именно эта библиотека.
akirsanov
md5 плох для хранения паролей, в случае с подписью, при достаточной длине ключа, — вполне уместен.