Continuing with the tutorials about the ESP-Now protocol, today I bring you one about how to add security to ESP-Now communications. This is very important to give security to your project and prevent someone from spying on your communications, or even flooding it with data.

Types of security keys

The first thing we should know is that there are two different types of keys:

  • Master Key or PMK (Private Master Key)
  • Local Key or LMK (Local Master Key)

Master or PMK

The master key or PMK is the key that will be used to encrypt the LMK key with AES128 type encryption. In the case of not indicating this password, a default password will be used. Since we are going to add security, his will be to change it, for which we can use the esp_now_set_pmk () function. This key is composed of 16 bytes, and is a global key that will be used in all communications.

Local or LMK

The local key LMK of the paired device is used to encrypt the action frames with the CCMP method. This configuration is done at the device level, so you can have several ESP clients with their particular keys. The only limitation is that you can only have 6 keys at most. If this key is not indicated, the action frame will not be encrypted. Like the PMK key, it is made up of 16 bytes.

How to configure security keys

Enough theory! Let’s move on to the interesting part: learning to encrypt communications, which, as you will see, is not difficult at all. We are going to start with the master, which will be the one that sends the encrypted data.

Master ESP-Now with encryption

The example code that we will use for encrypted communications will be the following:

/*
  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);
}

The important parts of this code that we must look at are the following:

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

These are the keys that we will use to encrypt the messages, so it is important to adjust them with random letters and numbers, and that they are not both the same.

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

In this line, as we commented previously, we execute the function that sets the PMK key. It is very important to execute it after executing the esp_now_init () function, or else the default key will be used.

  // 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;

In these lines you can see the normal method to add a slave board, with the difference that in this case we set the encrypt option to true and set the lmk key using a for loop.

And just like that, we will already have our master board configured to send encrypted data, adding security to our communication. Now we will go on to configure the slave board, which is configured in exactly the same way.

ESP-Now slave with encryption

Now that we have the master board configured, we need someone to receive the messages and for this we will configure a slave board. The configuration you will see that it is the same as in the master card except for the exclusive configurations such as the callback of when a packet is received. The code that we will use for the slave is the following:

/*
  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() {
}

And in this code we will see that the important parts are the same as in the master, we simply change the name of the variables.

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

These are the keys used to encrypt the messages, and it has to be the same in both.

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

We configure the PMK key as in the 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;

And with this you could already communicate safely between your ESP boards with the ESP-Now protocol.

As always, I hope it helps you and do not hesitate to comment and share. All the best!

Daniel Carrasco

DevOps with several years of experience, and cloud architect with experience in Google Cloud Platform and Amazon Web Services. In his spare time experimenting with Arduino and electronics.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.