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!