Continuando con los tutoriales acerca del protocolo ESP-Now, hoy os traigo uno acerca de como añadir seguridad a las comunicaciones ESP-Now. Esto es muy importante para darle seguridad a tu proyecto y evitar que alguien pueda espiar tus comunicaciones, o incluso hacer flooding de datos en él.

Tipos de claves de seguridad

Lo primero que debemos de saber es que existen dos tipos de claves distintas:

  • Clave maestra o PMK (Private Master Key)
  • Clave local o LMK (Local Master Key)

Maestra o PMK

La clave maestra o PMK es la clave que se usará para encriptar la clave LMK con un cifrado del tipo AES128. En el caso de no indicar esta clave, una clave por defecto será usada. Ya que vamos a añadir seguridad lo suyo será cambiarla, para lo cual podremos usar la función esp_now_set_pmk(). Esta clave está compuesta por 16 bytes, y es una clave global que será usada en todas las comunicaciones.

Local o LMK

La clave local LMK del dispositivo emparejado se utiliza para encriptar los action frames con el método CCMP. Esta configuración se hace a nivel de dispositivo, por lo que puedes tener varios clientes ESP con sus claves particulares. La única limitación es que sólo puedes tener 6 claves como máximo. Si no se le indica esta clave, el action frame no será encriptado. Al igual que la clave PMK, está compuesta por 16 bytes.

Cómo configurar las claves de seguridad

¡Basta ya de teoría!, vamos a pasar a la parte interesante: aprender a encriptar las comunicaciones, lo cual como verás, no es nada difícil. Vamos a empezar por el master, que será el que envíe los datos encriptados.

Master ESP-Now con encriptación

El código de ejemplo que usaremos para las comunicaciones encriptadas será el siguiente:

/*
  Daniel Carrasco
  This and more tutorials at https://www.electrosoftcloud.com/
*/

#include <esp_now.h>
#include <WiFi.h>

// Set the SLAVE MAC Address
uint8_t slaveAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

// PMK and LMK keys
static const char* PMK_KEY_STR = "PLEASE_CHANGE_ME";
static const char* LMK_KEY_STR = "DONT_BE_LAZY_OK?";

// Structure to keep the temperature and humidity data from a DHT sensor
typedef struct temp_humidity {
  float temperature;
  float humidity;
};

// Create a struct_message called myData
temp_humidity dhtData;

// Callback to have a track of sent messages
void OnSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  Serial.print("\r\nSend message status:\t");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Sent Successfully" : "Sent Failed");
}
 
void setup() {
  // Init Serial Monitor
  Serial.begin(115200);
 
  // Set device as a Wi-Fi Station
  WiFi.mode(WIFI_STA);

  // Init ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("There was an error initializing ESP-NOW");
    return;
  }

  // Setting the PMK key
  esp_now_set_pmk((uint8_t *)PMK_KEY_STR);
  
  // Register the slave
  esp_now_peer_info_t slaveInfo;
  memcpy(slaveInfo.peer_addr, slaveAddress, 6);
  slaveInfo.channel = 0;
  // Setting the master device LMK key
  for (uint8_t i = 0; i < 16; i++) {
    slaveInfo.lmk[i] = LMK_KEY_STR[i];
  }
  slaveInfo.encrypt = true;
  
  // Add slave        
  if (esp_now_add_peer(&slaveInfo) != ESP_OK){
    Serial.println("There was an error registering the slave");
    return;
  }

  // We will register the callback function to respond to the event
  esp_now_register_send_cb(OnSent);
}

void loop() {
  // Set values to send
  // To simplify the code, we will just set two floats and I'll send it 
  dhtData.temperature = 12.5;
  dhtData.humidity = 58.9;

  // Is time to send the messsage via ESP-NOW
  esp_err_t result = esp_now_send(slaveAddress, (uint8_t *) &dhtData, sizeof(dhtData));
   
  if (result == ESP_OK) {
    Serial.println("The message was sent sucessfully.");
  }
  else {
    Serial.println("There was an error sending the message.");
  }
  delay(2000);
}

De este código las partes importantes en las que debemos fijarnos son las siguiente:

// PMK and LMK keys
static const char* PMK_KEY_STR = "PLEASE_CHANGE_ME";
static const char* LMK_KEY_STR = "DONT_BE_LAZY_OK?";

Estas son las claves que usaremos para encriptar los mensajes, por lo que es importante ajustarlas con letras y números aleatorios, y que no sean ambas iguales.

  // Setting the PMK key
  esp_now_set_pmk((uint8_t *)PMK_KEY_STR);

En esta línea tal y como comentamos anteriormente, ejecutamos la función que setea la clave PMK. Es muy importante ejecutarlo tras haber ejecutado la función esp_now_init(), o de lo contrario la clave por defecto será usada.

  // Register the slave
  esp_now_peer_info_t slaveInfo;
  memcpy(slaveInfo.peer_addr, slaveAddress, 6);
  slaveInfo.channel = 0;
  // Setting the master device LMK key
  for (uint8_t i = 0; i < 16; i++) {
    slaveInfo.lmk[i] = LMK_KEY_STR[i];
  }
  slaveInfo.encrypt = true;

En estas líneas puedes ver el método normal para añadir una placa esclava, con la diferencia de que en este caso ponemos la opción encrypt a true y seteamos la clave lmk usando un bucle for.

Y así de fácil, ya tendremos configurada nuestra placa maestra para enviar los datos encriptados, añadiendo seguridad a nuestra comunicación. Ahora pasaremos a configurar la placa esclava, la cual se configura exactamente de la misma manera.

Esclavo ESP-Now con encriptación

Ahora que ya tenemos configurada la placa maestra, nos falta alguien que reciba los mensajes y para ello configuraremos una placa esclava. La configuración verás que es la misma que en la placa maestra salvo por las configuraciones exclusivas como por ejemplo el callback de cuando se recibe un paquete. El código que usaremos para el esclavo es el siguiente:

/*
  Daniel Carrasco
  This and more tutorials at https://www.electrosoftcloud.com/
*/

#include <esp_now.h>
#include <WiFi.h>

// Set the SLAVE MAC Address
uint8_t masterAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

// PMK and LMK keys
static const char* PMK_KEY_STR = "PLEASE_CHANGE_ME";
static const char* LMK_KEY_STR = "DONT_BE_LAZY_OK?";

// Structure to keep the temperature and humidity data
// Is also required in the client to be able to save the data directly
typedef struct temp_humidity {
  float temperature;
  float humidity;
};

// Create a struct_message called myData
temp_humidity dhtData;

// callback function executed when data is received
void OnRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
  memcpy(&dhtData, incomingData, sizeof(dhtData));
  Serial.print("Bytes received: ");
  Serial.println(len);
  Serial.print("Temperature: ");
  Serial.println(dhtData.temperature);
  Serial.print("Humidity: ");
  Serial.println(dhtData.humidity);
}

void setup() {
  // Initialize Serial Monitor
  Serial.begin(115200);
  
  // Set device as a Wi-Fi Station
  WiFi.mode(WIFI_STA);

  // Init ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("There was an error initializing ESP-NOW");
    return;
  }

  // Setting the PMK key
  esp_now_set_pmk((uint8_t *)PMK_KEY_STR);

  // Register the master
  esp_now_peer_info_t masterInfo;
  memcpy(masterInfo.peer_addr, masterAddress, 6);
  masterInfo.channel = 0;
  // Setting the master device LMK key
  for (uint8_t i = 0; i < 16; i++) {
    masterInfo.lmk[i] = LMK_KEY_STR[i];
  }
  masterInfo.encrypt = true;
  
  // Add master        
  if (esp_now_add_peer(&masterInfo) != ESP_OK){
    Serial.println("There was an error registering the master");
    return;
  }
  
  // Once the ESP-Now protocol is initialized, we will register the callback function
  // to be able to react when a package arrives in near to real time without pooling every loop.
  esp_now_register_recv_cb(OnRecv);
}

void loop() {
}

Y en este código veremos que las partes importantes son las mismas que en el maestro, simplemente cambiamos el nombre de las variables.

// PMK and LMK keys
static const char* PMK_KEY_STR = "PLEASE_CHANGE_ME";
static const char* LMK_KEY_STR = "DONT_BE_LAZY_OK?";

Estas son las claves usadas para encriptar los mensajes, y tiene que ser las mismas en ambos.

  // Setting the PMK key
  esp_now_set_pmk((uint8_t *)PMK_KEY_STR);

Configuramos la clave PMK al igual que en el master.

  // Register the master
  esp_now_peer_info_t masterInfo;
  memcpy(masterInfo.peer_addr, slaveAddress, 6);
  masterInfo.channel = 0;
  // Setting the master device LMK key
  for (uint8_t i = 0; i < 16; i++) {
    masterInfo.lmk[i] = LMK_KEY_STR[i];
  }
  masterInfo.encrypt = true;

Y con esto ya podrías comunicarte de forma segura entre tus placas ESP con el protocolo ESP-Now.

Como siempre, espero que os sirva y no dudéis en comentar y compartir. ¡Un saludo!

Daniel Carrasco

DevOps con varios años de experiencia, y arquitecto cloud con experiencia en Google Cloud Platform y Amazon Web Services. En sus ratos libres experimenta con Arduino y electrónica.

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.