ESP32: ESP-NOW Encrypted Messages

In this guide, you’ll learn how to encrypt ESP-NOW messages exchanged between ESP32 boards. ESP-NOW uses the CCMP method for encryption using a Primary Master Key (PMK) and Local Master Keys (LMK).

ESP32 ESP-NOW Encrypted Messages Arduino IDE

If you’re new to ESP-NOW, we recommend reading the following getting started guide first to get familiar with ESP-NOW concepts and functions on the ESP32:

CCMP Security Protocol

CCMP means Counter Mode with Cipher Block Chaining Message Authentication Code Protocol. This is an encryption protocol designed for Wireless LAN. ESP-NOW can use the CCMP method to encrypt messages.

Accordingly to the documentation:

ESP-NOW use CCMP method which can be referenced in IEEE Std. 802.11-2012 to protect the vendor-specific action frame.”

The Wi-Fi device maintains a Primary Master Key (PMK) and several Local Master Keys (LMK). The length of the keys is 16 bytes.

Primary Master Key (PMK)

PMK is used to encrypt LMK with the AES-128 algorithm. To set the PMK key of the Wi-Fi device, you can use the esp_now_set_pmk() function to set PMK. If PMK is not set, a default PMK will be used.

Local Master Key (LMK)

You should set the LMK of the paired device to encrypt the vendor-specific action frame with CCMP method. The maximum number of different LMKs is six. The LMK is a property of the peer, esp_now_peer_info_t object, and can be set on the lmk property as we’ll see later.

ESP32: Getting Board MAC Address

To communicate via ESP-NOW, you need to know the MAC Addresses of the boards so that you can add each other as peers.

Each ESP32 has a unique MAC Address and that’s how we identify each board (learn how to Get and Change the ESP32 MAC Address).

To get your board’s MAC Address, upload the following code.

// Complete Instructions to Get and Change ESP MAC Address: https://RandomNerdTutorials.com/get-change-esp32-esp8266-mac-address-arduino/

#include "WiFi.h"
 
void setup(){
  Serial.begin(115200);
  WiFi.mode(WIFI_MODE_STA);
  Serial.println(WiFi.macAddress());
}
 
void loop(){

}

View raw code

After uploading the code, open the Serial Monitor at a baud rate of 115200 and press the ESP32 RST/EN button. The MAC address should be printed as follows:

ESP-NOW ESP32 Getting Board MAC Address

Save your board MAC address because you’ll need it in the next ESP-NOW examples.

Project Overview

The example we’ll show you is very simple so that you can understand how to encrypt your ESP-NOW messages. The sender will send a structure that contains two random numbers, x and y, and a counter variable (to keep track of the number of sent packets).

ESP32 ESP-NOW Encrypted Messages Project Overview

Here are the main steps:

  1. The sender sets its PMK;
  2. The sender adds the receiver as a peer and sets its LMK;
  3. The receiver sets its PMK (should be the same of the receiver);
  4. The receiver adds the sender as a peer and sets its LMK (should be the same as the one set on the sender board);
  5. The sender sends the following structure to the receiver board:
typedef struct struct_message {
    int counter;
    int x;
    int y;
} struct_message;
  1. The receiver gets the message.

ESP32 Sender Sketch (ESP-NOW Encrypted)

Here’s the code for the ESP32 Sender board. Copy the code to your Arduino IDE, but don’t upload it yet. You need to make a few modifications to make it work for you.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/?s=esp-now
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/

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

// REPLACE WITH THE RECEIVER'S MAC Address
uint8_t receiverAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

// PMK and LMK keys
static const char* PMK_KEY_STR = "REPLACE_WITH_PMK_KEY";
static const char* LMK_KEY_STR = "REPLACE_WITH_LMK_KEY";

// Structure example to send data
// Must match the receiver structure
typedef struct struct_message {
    int counter;
    int x;
    int y;
} struct_message;

// Create a struct_message called myData
struct_message myData;

// Counter variable to keep track of number of sent packets
int counter;

// callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  Serial.print("\r\nLast Packet Send Status:\t");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}
 
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;
  }
  
  // Set PMK key
  esp_now_set_pmk((uint8_t *)PMK_KEY_STR);
  
  // Register the receiver board as peer
  esp_now_peer_info_t peerInfo;
  memcpy(peerInfo.peer_addr, receiverAddress, 6);
  peerInfo.channel = 0;
  //Set the receiver device LMK key
  for (uint8_t i = 0; i < 16; i++) {
    peerInfo.lmk[i] = LMK_KEY_STR[i];
  }
  // Set encryption to true
  peerInfo.encrypt = true;
  
  // Add receiver as peer        
  if (esp_now_add_peer(&peerInfo) != ESP_OK){
    Serial.println("Failed to add peer");
    return;
  }

  // Once ESPNow is successfully Init, we will register for Send CB to
  // get the status of transmitted packet
  esp_now_register_send_cb(OnDataSent);
}
void loop() {
  static unsigned long lastEventTime = millis();
  static const unsigned long EVENT_INTERVAL_MS = 5000;
  if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) {
    lastEventTime = millis();
    
    // Set values to send
    myData.counter = counter++;
    myData.x = random(0,50);
    myData.y = random(0,50);
  
    // Send message via ESP-NOW
    esp_err_t result = esp_now_send(receiverAddress, (uint8_t *) &myData, sizeof(myData));
    if (result == ESP_OK) {
      Serial.println("Sent with success");
    }
    else {
      Serial.println("Error sending the data");
    }  
  }
}

View raw code

Don’t forget you need to add the receiver’s MAC address in the code. In my case, the receiver MAC address is 30:AE:A4:07:0D:64. So, it will look as follows on the code:

// REPLACE WITH THE RECEIVER'S MAC Address
uint8_t receiverAddress[] = {0x30, 0xAE, 0xA4, 0x07, 0x0D, 0x64};

Let’s take a look at the relevant parts of code that deal with encryption.

Create the PMK and LMK keys for this device on the following lines. It can be made of numbers and letters and the keys are 16 bytes (you can search online for “online byte counter” to check the length of your keys).

static const char* PMK_KEY_STR = "REPLACE_WITH_PMK_KEY";
static const char* LMK_KEY_STR = "REPLACE_WITH_LMK_KEY";

For example, the key can be something like this 00XXmkwei/lpPÇf.

The sender and receiver should have the same PMK and LMK keys.

Set the device PMK key using the esp_now_set_pmk() function as follows:

esp_now_set_pmk((uint8_t *)PMK_KEY_STR);

The LMK is a property of the peer device, so you must set it when you register a device as peer. You set the LMK as follows:

for (uint8_t i = 0; i < 16; i++) {
   peerInfo.lmk[i] = LMK_KEY_STR[i];
}

You also need to set the encrypt peer property as true.

peerInfo.encrypt = true;

And that’s it. This is all you need to do to encrypt ESP-NOW messages. Now, you can use the ESP-NOW function to exchange data and the messages will be encrypted.

ESP32 Receiver Sketch (ESP-NOW Encrypted Messages)

Here’s the code for the ESP32 Receiver board. Copy the code to your Arduino IDE, but don’t upload it yet. You need to make a few modifications to make it work for you.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/?s=esp-now
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/

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

// REPLACE WITH YOUR MASTER MAC Address
uint8_t masterMacAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

// PMK and LMK keys
static const char* PMK_KEY_STR = "REPLACE_WITH_PMK_KEY";
static const char* LMK_KEY_STR = "REPLACE_WITH_LMK_KEY";

// Structure example to send data
// Must match the sender structure
typedef struct struct_message {
    int counter; // must be unique for each sender board
    int x;
    int y;
} struct_message;

// Create a struct_message called myData
struct_message myData;

// Function to print MAC address on Serial Monitor
void printMAC(const uint8_t * mac_addr){
  char macStr[18];
  snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
           mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
  Serial.println(macStr);
}

// Callback function executed when data is received
void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) {
 
  Serial.print("Packet received from: ");
  printMAC(mac_addr);
  
  memcpy(&myData, incomingData, sizeof(myData));
  Serial.print("Bytes received: ");
  Serial.println(len);
  Serial.print("Packet number: ");
  Serial.println(myData.counter);
  Serial.print("X: ");
  Serial.println(myData.x);
  Serial.print("Y: ");
  Serial.println(myData.y);
}
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;
  }
  
  // Set the PMK key
  esp_now_set_pmk((uint8_t *)PMK_KEY_STR);
  
  // Register the master as peer
  esp_now_peer_info_t peerInfo;
  memcpy(peerInfo.peer_addr, masterMacAddress, 6);
  peerInfo.channel = 0;
  // Setting the master device LMK key
  for (uint8_t i = 0; i < 16; i++) {
    peerInfo.lmk[i] = LMK_KEY_STR[i];
  }
  // Set encryption to true
  peerInfo.encrypt = true;
  
  // Add master as peer       
  if (esp_now_add_peer(&peerInfo) != ESP_OK){
    Serial.println("Failed to add peer");
    return;
  }
  
  // Once ESPNow is successfully Init, we will register for recv CB to
  // get recv packer info
  esp_now_register_recv_cb(OnDataRecv);
}
void loop() {
  
}

View raw code

You need to add the sender board as a peer. So, you need to know its MAC address. Add the sender MAC address in the following line:

uint8_t masterMacAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

Set the PMK and LMK keys. Should be the same as the other board.

static const char* PMK_KEY_STR = "REPLACE_WITH_PMK_KEY";
static const char* LMK_KEY_STR = "REPLACE_WITH_LMK_KEY";

Set the device PMK key using the esp_now_set_pmk() function as follows:

esp_now_set_pmk((uint8_t *)PMK_KEY_STR);

The LMK is a property of the peer device, so you must set it when you register a device as peer. You set the LMK as follows:

for (uint8_t i = 0; i < 16; i++) {
  peerInfo.lmk[i] = LMK_KEY_STR[i];}

You also need to set the encrypt peer property as true.

peerInfo.encrypt = true;

And that’s it, now the receiver board can receiver and decrypt the encrypted messages sent by the sender.

Demonstration

Upload the codes to the corresponding boards.

Open the Serial Monitor to check what’s going on. You can use PuTTY to be able to see the messages on both boards simultaneously.

This is what you should get on the receiver board:

ESP-NOW Receiver Encrypted Messages PUTTY

On the sender board, you should get “Delivery Success” messages.

ESP-NOW Sender Encrypted Messages Delivery Success Serial Monitor

Wrapping Up

In this tutorial, you learned how to encrypt ESP-NOW messages using PMK and LMK keys.

I tested the encryption in different scenarios and here are the results:

  • Sender and receiver encrypted with same keys: receiver board receives the messages successfully;
  • The sender sends encrypted messages, but the receiver doesn’t have the keys or has different keys: the receiver doesn’t get the messages;
  • The receiver has the code for encryption but the sender doesn’t: the receiver gets the messages anyway. I don’t think this is the behavior we expected. Since we add the encryption code on both boards, one would expect that if the receiver board got a message that is not encrypted, it would ignore it. But that’s not what happens. It receives all messages, encrypted and not encrypted. At the moment, there isn’t a way to know if the received message is encrypted or not, which seems like a limitation at the moment.

We hope you found this tutorial useful. We have more ESP-NOW examples you may like:

If you would like to learn more about the ESP32 board and IoT, make sure you take a look at our resources:

Thanks for reading.



Build Web Server projects with the ESP32 and ESP8266 boards to control outputs and monitor sensors remotely. Learn HTML, CSS, JavaScript and client-server communication protocols DOWNLOAD »

Build Web Server projects with the ESP32 and ESP8266 boards to control outputs and monitor sensors remotely. Learn HTML, CSS, JavaScript and client-server communication protocols DOWNLOAD »

Recommended Resources

Build a Home Automation System from Scratch » With Raspberry Pi, ESP8266, Arduino, and Node-RED.

Home Automation using ESP8266 eBook and video course » Build IoT and home automation projects.

Arduino Step-by-Step Projects » Build 25 Arduino projects with our course, even with no prior experience!

What to Read Next…


Enjoyed this project? Stay updated by subscribing our newsletter!

1 thought on “ESP32: ESP-NOW Encrypted Messages”

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.