2022年6月16日木曜日

ESP32 secureなWiFiリモートスイッチ

 せっかくSHAとAESのハードウェアアクセラレータが使えるようになったので、WiFiを使ったリモートスイッチを暗号化して、セキュアなWiFiリモートスイッチを作ってみました。クライアント側のスケッチ、サーバ側のスケッチを2つ用意し、それぞれのフォルダに共通ライブラリをコピー&ペーストしてコンパイルしてください。

接続

クライアント側の25Pinにスイッチ、26PinにLEDと抵抗をつけます。LEDと抵抗は通信エラーが起きたときのインジケータなので、なくても大丈夫です。

サーバ側の25PinにLEDと抵抗をつけます。


クライアント側のスケッチ

/*
secure WiFi remote control Client
Challenge-Handshake Authentication Protocol
https://en.wikipedia.org/wiki/Replay_attack
*/
#include "ESP32E_AES_Hardware_Accelerator.h"
#include <WiFi.h>
const char* ssid = "your ssid";
const char* password = "your password";
char hostIP[] = "192.168.11.21";
int hostPort = 4000;
uint32_t cipher_key[] = {0x2b7e1516, 0x28aed2a6, 0xabf71588, 0x09cf4f3c};
RTC_DATA_ATTR int bootCount = 0;
WiFiClient client;
const int LED_PIN = 26;
AES_HardwareAccelerator AES;
void setup() {
uint8_t input[256];
bootCount++;
pinMode(GPIO_NUM_25, INPUT_PULLUP);
Serial.begin(115200);
esp_sleep_wakeup_cause_t wakeup_reason;
wakeup_reason = esp_sleep_get_wakeup_cause();
//Power on Reset or unknown wakeup reason
if (( bootCount == 0 ) || (wakeup_reason != ESP_SLEEP_WAKEUP_EXT0)) {
delay(1000);
Serial.println("initial boot");
deepsleep();
}
// WiFi connection
int timeout_count = 0;
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
timeout_count++;
delay(100);
if (timeout_count == 50) {
Serial.println("WiFi Connect failed.");
connectionfailed();
deepsleep();
}
}
Serial.println("WiFi Connected.");
Serial.printf("IP Address : ");
Serial.println(WiFi.localIP());
Serial.println("Connect to server");
uint32_t msg_LINK[] = {0x4C494E4B, 0x00000000, 0x00000000, 0x00000000}; //LINK
uint32_t msg_CHAL[] = {0x4348404C, 0x4C454E47, 0x00000000, 0x00000000}; //CHAL LENG
uint32_t msg_TGL[] = {0x4C454454, 0x474C0000, 0x00000000, 0x00000000}; //LEDTGL
uint32_t msg_ACK[] = {0x41434B00, 0x00000000, 0x00000000, 0x00000000}; //ACK
uint32_t msg_NACK[] = {0x4E41434B, 0x00000000, 0x00000000, 0x00000000}; //NACK
uint32_t declipt_msg[4];
uint8_t senddata[16]; //output buffer for WiFi packet
uint8_t recvdata[16]; //input buffer for WiFi packet
//connect to server
int returncode = client.connect(hostIP, hostPort, 1000);
if (returncode != 1) {
Serial.print("connection failed");
Serial.print(returncode);
connectionfailed();
deepsleep();
}
client.setTimeout(1000);
//1 : to Server, Link Request
encrypt_byte(cipher_key, msg_LINK, senddata);
client.write(senddata,sizeof(senddata));
//2: from Server, get challange code from server
while (client.available() == 0) { }
client.read(recvdata, sizeof(recvdata));
decrypt_byte(cipher_key, recvdata, declipt_msg);
if ((declipt_msg[0] != 0x4348404C) || (declipt_msg[1] != 0x4C454E47)) {
Serial.println("response error");
connectionfailed();
client.stop();
deepsleep();
}
//3: to Server, send "LED msg_TGL" command + challenge code
msg_TGL[3] = declipt_msg[3];
encrypt_byte(cipher_key, msg_TGL, senddata);
client.write(senddata, sizeof(senddata));
//4: from Server, get "ACK"
while (client.available() == 0) { }
client.read(recvdata, sizeof(recvdata));
decrypt_byte(cipher_key, recvdata, declipt_msg);
if (declipt_msg[0] == 0x41434B00) { //ACK
Serial.println("SUCCESS");
} else {
Serial.println("FAIL");
}
client.stop();
deepsleep();
}
void loop() {
}
void deepsleep() {
WiFi.disconnect(true);//disconnect WiFi as it's no longer needed
WiFi.mode(WIFI_OFF);
while (digitalRead(25) == LOW) { }
esp_sleep_enable_ext0_wakeup(GPIO_NUM_25, LOW);
Serial.println("Going to sleep now");
esp_deep_sleep_start();
}
void connectionfailed() {
Serial.println("Connection failed.");
for (int i = 0; i < 5; i++) {
digitalWrite(LED_PIN, HIGH);
delay(200);
digitalWrite(LED_PIN, LOW);
delay(200);
}
}
void encrypt_byte(uint32_t * cipher_key, uint32_t * text, uint8_t * c_text) {
AES.enable();
AES.setMode_AES128Encrypt();
AES.setCipherKey(cipher_key);
AES.setTextReg(text);
AES.start();
AES.getTextReg(c_text);
AES.disable();
}
void decrypt_byte(uint32_t * cipher_key, uint8_t * c_text, uint32_t * text) {
AES.enable();
AES.setMode_AES128Decrypt();
AES.setCipherKey(cipher_key);
AES.setTextReg(c_text);
AES.start();
AES.getTextReg(text);
AES.disable();
}

サーバ側のスケッチ

/*
secure WiFi remote control Server
Challenge-Handshake Authentication Protocol
https://en.wikipedia.org/wiki/Replay_attack
*/
#include "ESP32E_AES_Hardware_Accelerator.h"
#include <WiFi.h>
const char* ssid = "your ssid";
const char* password = "your password";
int hostPort = 4000;
WiFiServer server(hostPort);
const int LED_PIN = 25;
uint32_t cipher_key[] = {0x2b7e1516, 0x28aed2a6, 0xabf71588, 0x09cf4f3c};
AES_HardwareAccelerator AES;
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
delay(500);
Serial.println();
WiFi.begin(ssid, password);
Serial.print("connecting WiFi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(100);
}
Serial.println();
Serial.println("WiFi Connected.");
Serial.printf("IP Address : ");
Serial.println(WiFi.localIP());
server.begin();
Serial.println("start server");
randomSeed(100);
}
void loop() {
uint32_t msg_LINK[] = {0x4C494E4B, 0x00000000, 0x00000000, 0x00000000}; //LINK
uint32_t msg_CHAL[] = {0x4348404C, 0x4C454E47, 0x00000000, 0x00000000}; //CHAL LENG
uint32_t msg_TGL[] = {0x4C454454, 0x474C0000, 0x00000000, 0x00000000}; //LEDTGL
uint32_t msg_ACK[] = {0x41434B00, 0x00000000, 0x00000000, 0x00000000}; //ACK
uint32_t msg_NACK[] = {0x4E41434B, 0x00000000, 0x00000000, 0x00000000}; //NACK
uint32_t declipt_msg[4];
uint8_t senddata[16]; //output buffer for WiFi packet
uint8_t recvdata[16]; //input buffer for WiFi packet
WiFiClient client = server.available(); // listen for incoming clients
if (client) {
Serial.println("New Client connected.");
while (client.connected()) { // loop while the client's connected
client.setTimeout(1000);
while (client.available() == 0) { }
//1: from client, Link Request
client.read(recvdata, sizeof(recvdata));
decrypt_byte(cipher_key, recvdata, declipt_msg);
if (declipt_msg[0] != msg_LINK[0]) {
client.println("NACK");
client.stop();
Serial.println("client disconnected.");
break;
}
//2: to client, generate charrange code and send
uint32_t challenge = random(0xFFFFFFFF);
msg_CHAL[3] = challenge;
encrypt_byte(cipher_key, msg_CHAL, senddata);
client.write(senddata, sizeof(senddata));
//3: from client, command and challenge code
while (client.available() == 0) { }
client.read(recvdata, sizeof(recvdata));
decrypt_byte(cipher_key, recvdata, declipt_msg);
//4 to client, ACK /NACK
if (declipt_msg[3] == challenge) {
if ((declipt_msg[0] == 0x4C454454) && (declipt_msg[1] == 0x474C0000)) { //LEDTGL
led_toggle();
msg_ACK[3] = challenge;
encrypt_byte(cipher_key, msg_ACK, senddata);
}
} else {
msg_NACK[3] = challenge;
encrypt_byte(cipher_key, msg_NACK, senddata);
}
client.write(senddata, sizeof(senddata));
client.stop();
Serial.println("client disconnected.");
}
}
delay(1);
}
void encrypt_byte(uint32_t * cipher_key, uint32_t * text, uint8_t * c_text) {
AES.enable();
AES.setMode_AES128Encrypt();
AES.setCipherKey(cipher_key);
AES.setTextReg(text);
AES.start();
AES.getTextReg(c_text);
AES.disable();
}
void decrypt_byte(uint32_t * cipher_key, uint8_t * c_text, uint32_t * text) {
AES.enable();
AES.setMode_AES128Decrypt();
AES.setCipherKey(cipher_key);
AES.setTextReg(c_text);
AES.start();
AES.getTextReg(text);
AES.disable();
}
void led_toggle() {
if (digitalRead(LED_PIN) == HIGH) digitalWrite(LED_PIN, LOW);
else digitalWrite(LED_PIN, HIGH);
}

共通ライブラリ

/*
* FILE: ESP32E_AES_Hardware_Accelerator.h
* AUTHOR: PiT
* PURPOSE: Arduino library for ESP32E AES Hardware Accelerator
* VERSION: 1.0.0
*/
#define DPORT_PERI_CLK_EN_REG *((volatile uint32_t *)0x3FF0001C)
#define DPORT_PERI_RST_EN_REG *((volatile uint32_t *)0x3FF00020)
#define AES_MODE_REG *((volatile uint32_t *)0x3FF01008)
#define AES_ENDIAN_REG *((volatile uint32_t *)0x3FF01040)
#define AES_KEY_0_REG *((volatile uint32_t *)0x3FF01010)
#define AES_KEY_1_REG *((volatile uint32_t *)0x3FF01014)
#define AES_KEY_2_REG *((volatile uint32_t *)0x3FF01018)
#define AES_KEY_3_REG *((volatile uint32_t *)0x3FF0101C)
#define AES_KEY_4_REG *((volatile uint32_t *)0x3FF01020)
#define AES_KEY_5_REG *((volatile uint32_t *)0x3FF01024)
#define AES_KEY_6_REG *((volatile uint32_t *)0x3FF01028)
#define AES_KEY_7_REG *((volatile uint32_t *)0x3FF0102C)
#define AES_TEXT_0_REG *((volatile uint32_t *)0x3FF01030)
#define AES_TEXT_1_REG *((volatile uint32_t *)0x3FF01034)
#define AES_TEXT_2_REG *((volatile uint32_t *)0x3FF01038)
#define AES_TEXT_3_REG *((volatile uint32_t *)0x3FF0103C)
#define AES_START_REG *((volatile uint32_t *)0x3FF01000)
#define AES_IDLE_REG *((volatile uint32_t *)0x3FF01004)
#define AES_MODE_AES128ENCRYPTION 0x00000000
#define AES_MODE_AES192ENCRYPTION 0x00000001
#define AES_MODE_AES256ENCRYPTION 0x00000002
#define AES_MODE_AES128DECRYPTION 0x00000004
#define AES_MODE_AES192DECRYPTION 0x00000005
#define AES_MODE_AES256DECRYPTION 0x00000006
#define AES_KEY_ENDIAN0 0x00000000
#define AES_KEY_ENDIAN1 0x00000001
#define AES_KEY_ENDIAN2 0x00000002
#define AES_KEY_ENDIAN3 0x00000003
#define AES_TEXT_ENDIAN0 0x00000000
#define AES_TEXT_ENDIAN1 0x00000014
#define AES_TEXT_ENDIAN2 0x00000028
#define AES_TEXT_ENDIAN3 0x0000003C
class AES_HardwareAccelerator {
public:
void enable();
void setMode_AES128Encrypt();
void setMode_AES128Decrypt();
void start();
void setCipherKey(uint32_t * cipher_key);
void setTextReg(uint32_t * textblock);
void setTextReg(uint8_t * text);
void getTextReg(uint32_t * textblock);
void getTextReg(uint8_t * text);
void disable();
};

/*
* FILE: ESP32E_AES_Hardware_Accelerator.cpp
* AUTHOR: PiT
* PURPOSE: Arduino library for ESP32E AES Hardware Accelerator
* VERSION: 1.0.0
*/
#include <Arduino.h>
#include "ESP32E_AES_Hardware_Accelerator.h"
void AES_HardwareAccelerator::enable() {
DPORT_PERI_CLK_EN_REG = DPORT_PERI_CLK_EN_REG | 0x00000001; // peripheral clock enable
DPORT_PERI_RST_EN_REG = DPORT_PERI_RST_EN_REG & (~(0x00000001 | 0x00000008 | 0x00000010)); // peripheral reset
}
void AES_HardwareAccelerator::setMode_AES128Encrypt() {
AES_MODE_REG = AES_MODE_AES128ENCRYPTION;
AES_ENDIAN_REG = AES_KEY_ENDIAN2 | AES_TEXT_ENDIAN2;
}
void AES_HardwareAccelerator::setMode_AES128Decrypt() {
AES_MODE_REG = AES_MODE_AES128DECRYPTION;
AES_ENDIAN_REG = AES_KEY_ENDIAN2 | AES_TEXT_ENDIAN2;
}
void AES_HardwareAccelerator::start() {
AES_START_REG = 0x00000001;
while (AES_IDLE_REG) {}
}
void AES_HardwareAccelerator::setCipherKey(uint32_t * cipher_key) {
AES_KEY_0_REG = cipher_key[0];
AES_KEY_1_REG = cipher_key[1];
AES_KEY_2_REG = cipher_key[2];
AES_KEY_3_REG = cipher_key[3];
}
void AES_HardwareAccelerator::setTextReg(uint32_t * text) {
AES_TEXT_0_REG = text[0];
AES_TEXT_1_REG = text[1];
AES_TEXT_2_REG = text[2];
AES_TEXT_3_REG = text[3];
}
void AES_HardwareAccelerator::setTextReg(uint8_t * text) {
uint32_t temp;
temp = text[0];
temp = (temp << 8) + text[1];
temp = (temp << 8) + text[2];
temp = (temp << 8) + text[3];
AES_TEXT_0_REG = temp;
temp = text[4];
temp = (temp << 8) + text[5];
temp = (temp << 8) + text[6];
temp = (temp << 8) + text[7];
AES_TEXT_1_REG = temp;
temp = text[8];
temp = (temp << 8) + text[9];
temp = (temp << 8) + text[10];
temp = (temp << 8) + text[11];
AES_TEXT_2_REG = temp;
temp = text[12];
temp = (temp << 8) + text[13];
temp = (temp << 8) + text[14];
temp = (temp << 8) + text[15];
AES_TEXT_3_REG = temp;
}
void AES_HardwareAccelerator::getTextReg(uint32_t * text) {
text[0] = AES_TEXT_0_REG;
text[1] = AES_TEXT_1_REG;
text[2] = AES_TEXT_2_REG;
text[3] = AES_TEXT_3_REG;
}
void AES_HardwareAccelerator::getTextReg(uint8_t * text) {
uint32_t temp;
temp = AES_TEXT_0_REG;
text[0] = temp >> 24;
text[1] = temp >> 16;
text[2] = temp >> 8;
text[3] = temp;
temp = AES_TEXT_1_REG;
text[4] = temp >> 24;
text[5] = temp >> 16;
text[6] = temp >> 8;
text[7] = temp;
temp = AES_TEXT_2_REG;
text[8] = temp >> 24;
text[9] = temp >> 16;
text[10] = temp >> 8;
text[11] = temp;
temp = AES_TEXT_3_REG;
text[12] = temp >> 24;
text[13] = temp >> 16;
text[14] = temp >> 8;
text[15] = temp;
}
void AES_HardwareAccelerator::disable() {
DPORT_PERI_CLK_EN_REG = 0x00000000; // peripheral clock enable
DPORT_PERI_RST_EN_REG = 0x0000000F; // peripheral reset
}

普通のWiFiリモコンとの違い

通信の内容をAES128を使って暗号化しています。

LEDを制御するコマンドがLEDTGLのみなので、通信するデータに乱数(Salt)をのせて、暗号文が通信毎に変わるようにしています。ただし、通信1発目のリンクリクエストについては、Saltを加えてもサーバは反応するしかないため、何もしていません。

サーバ側からワンタイムパスワード(Challenge)を送信し、クライアント側からはコマンドにワンタイムパスワードを加えた文字列を暗号化してサーバに送信します。サーバ側でワンタイムパスワードを比較することで、リプレイアタックされてもコマンドを受け付けないようにしています。

より詳しく知りたい方は、

リプレイアタック、チャレンジーハンドシェイク、CHAP

などを検索してください。



0 件のコメント:

コメントを投稿