Chapter 72: ESP-NOW Security Implementation
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand the common security risks associated with wireless device-to-device communication.
- Learn about ESP-NOW’s built-in encryption mechanism (AES-128 CCMP).
- Understand the role, characteristics, and management of Local Master Keys (LMKs) in ESP-NOW.
- Configure and implement encrypted unicast communication between ESP32 devices using ESP-NOW.
- Discuss various strategies and challenges for provisioning LMKs to peer devices.
- Recognize the limitations of ESP-NOW security and best practices for its application.
- Evaluate the impact of encryption on ESP-NOW performance and resource usage.
Introduction
ESP-NOW provides a convenient and efficient way for ESP32 devices to communicate directly. However, like any wireless communication, ESP-NOW transmissions can be susceptible to eavesdropping, message tampering, or unauthorized device interactions if not properly secured. For many applications, especially those handling sensitive data or controlling critical functions, ensuring the confidentiality and integrity of these direct communications is paramount.
Fortunately, ESP-NOW includes a mechanism for encrypting unicast messages, adding a crucial layer of security. This feature helps protect data in transit between trusted peer devices. While ESP-NOW’s security is focused on link-layer encryption between known peers, it’s a vital component in building more robust and trustworthy systems.
This chapter will delve into the security features of ESP-NOW. We will explore how encryption is implemented, the importance of key management, and practical steps to secure your ESP-NOW links. Understanding these concepts will allow you to make informed decisions about when and how to apply security to your ESP32-based direct communication projects.
Theory
Security Threats in Wireless Communication
Before discussing ESP-NOW’s security features, it’s helpful to understand common threats in wireless environments:
Threat | Description | How ESP-NOW Addresses It (Unicast) |
---|---|---|
Eavesdropping (Sniffing) | Unauthorized interception of data transmitted over the air. | Addressed by AES-128 CCMP encryption for unicast messages, making data unreadable to unauthorized parties. |
Message Tampering (Integrity Attack) | Malicious modification of data in transit. | Addressed by CCMP, which includes a Message Integrity Code (MIC) to detect if the message has been altered. |
Spoofing (Impersonation) | An unauthorized device masquerading as a legitimate peer. | Partially addressed. If LMKs are kept secret, an imposter cannot establish an encrypted session. However, MAC addresses can be spoofed; reliance on LMK secrecy is key. |
Replay Attack | Capturing legitimate messages and retransmitting them later. | CCMP includes a packet sequence number to help detect replays. However, robust application-level replay protection might still be needed depending on the ESP-NOW implementation specifics and application sensitivity. |
ESP-NOW’s security features primarily address eavesdropping and, to some extent, message integrity for unicast communication through encryption.
ESP-NOW Encryption Overview
ESP-NOW provides an optional encryption feature for unicast (peer-to-peer) messages. It does not support encryption for broadcast messages.
- Encryption Algorithm: ESP-NOW uses AES-128 CCMP (Counter Mode Cipher Block Chaining Message Authentication Code Protocol) for encryption and message integrity.
- AES (Advanced Encryption Standard): A symmetric block cipher widely adopted for its strength. “Symmetric” means the same key is used for both encryption and decryption.
- CCMP: An encryption protocol designed for wireless LAN products that provides both data confidentiality (encryption) and data integrity (message authentication code – MIC). The MIC helps ensure that the message has not been tampered with during transit.
- Local Master Key (LMK):
- The core of ESP-NOW encryption is the Local Master Key (LMK).
- It is a 16-byte (128-bit) secret key.
- Crucially, for any two ESP-NOW peers to communicate securely, they must both be configured with the exact same LMK for that specific peer-to-peer relationship.
- The LMK is not exchanged over the air by the ESP-NOW protocol itself. It must be provisioned or pre-shared through some other secure mechanism.
Component | Description | Role in ESP-NOW Security |
---|---|---|
AES (Advanced Encryption Standard) | A strong symmetric block cipher. ESP-NOW uses AES with a 128-bit key. | Provides confidentiality by encrypting the data payload of unicast messages. |
CCMP (Counter Mode Cipher Block Chaining Message Authentication Code Protocol) | An encryption protocol based on AES, designed for wireless networks. | Provides both confidentiality (via AES encryption) and data integrity & authenticity (via MIC). |
MIC (Message Integrity Code) | A cryptographic checksum generated by CCMP. It’s calculated over the message payload and parts of the header. | Ensures that the message has not been tampered with during transit. If the MIC calculated by the receiver does not match the one sent, the packet is considered corrupted or tampered. |
Peer Management with Encryption
When adding or modifying a peer using esp_now_add_peer()
or esp_now_mod_peer()
, the esp_now_peer_info_t
structure has two fields relevant to security:
bool encrypt
: Set totrue
to enable encrypted communication with this specific peer. Iffalse
, communication with this peer will be unencrypted.uint8_t lmk[ESP_NOW_KEY_LEN]
: An array to hold the 16-byte LMK to be used for communication with this peer ifencrypt
istrue
.ESP_NOW_KEY_LEN
is typically 16.
sequenceDiagram participant DeviceA as Device A (Sender) participant ESPNOWStackA as ESP-NOW Stack (A) participant Air as Wireless Medium participant ESPNOWStackB as ESP-NOW Stack (B) participant DeviceB as Device B (Receiver) DeviceA->>ESPNOWStackA: <b>esp_now_add_peer()</b><br/>Peer: MAC_B<br/>Encrypt: true<br/>LMK: SHARED_LMK activate ESPNOWStackA ESPNOWStackA-->>DeviceA: Peer B added (encrypted) deactivate ESPNOWStackA note right of DeviceA: Device A now configured to encrypt for MAC_B DeviceB->>ESPNOWStackB: <b>esp_now_add_peer()</b> (Optional for receive, mandatory if B sends back encrypted)<br/>Peer: MAC_A<br/>Encrypt: true<br/>LMK: SHARED_LMK activate ESPNOWStackB ESPNOWStackB-->>DeviceB: Peer A added (encrypted) deactivate ESPNOWStackB note left of DeviceB: Device B now configured with LMK for MAC_A DeviceA->>ESPNOWStackA: <b>esp_now_send()</b><br/>To: MAC_B<br/>Data: "Hello Secure World" activate ESPNOWStackA ESPNOWStackA->>ESPNOWStackA: Encrypt data with SHARED_LMK (AES-CCMP) ESPNOWStackA->>Air: Transmit Encrypted Packet deactivate ESPNOWStackA Air->>ESPNOWStackB: Receive Encrypted Packet (From: MAC_A) activate ESPNOWStackB ESPNOWStackB->>ESPNOWStackB: Identify sender MAC_A<br/>Use SHARED_LMK to decrypt (AES-CCMP)<br/>Verify MIC alt Decryption & Integrity OK ESPNOWStackB->>DeviceB: <b>recv_cb()</b><br/>From: MAC_A<br/>Data: "Hello Secure World" (decrypted) else Decryption/Integrity Fails ESPNOWStackB->>ESPNOWStackB: Discard packet or error ESPNOWStackB-->>DeviceB: No callback or error indication end deactivate ESPNOWStackB
How it works:
- Sender Side: When
esp_now_send()
is called for a peer marked as encrypted, the ESP-NOW stack uses the LMK associated with that peer to encrypt the data payload using AES-128 CCMP before transmission. - Receiver Side: When an encrypted packet is received, the ESP-NOW stack identifies the sender by its MAC address. If this sender is registered as an encrypted peer (meaning the receiver has also been configured with an LMK for this sender), it uses that LMK to decrypt the payload and verify its integrity. If decryption or integrity check fails (e.g., wrong LMK), the packet is typically discarded, and the receive callback might not be invoked or might receive an error.
LMK Provisioning: The Main Challenge
The security of ESP-NOW encryption hinges entirely on the secrecy and correct distribution of LMKs. ESP-NOW itself does not provide a mechanism for LMK exchange or negotiation. This means you, the developer, are responsible for ensuring that paired devices share the correct LMK.
Common LMK provisioning strategies include:
- Pre-programming (Hardcoding): LMKs are embedded directly into the firmware of all devices that need to communicate.
- Pros: Simple to implement for a fixed set of devices.
- Cons: Inflexible. If an LMK is compromised, all devices need reprogramming. Not suitable for dynamically adding new devices. Security relies on firmware secrecy.
- Manual Configuration: Users manually enter the LMK into devices via a serial interface, web interface, or mobile app during setup.
- Pros: More flexible than hardcoding.
- Cons: Can be cumbersome for users, prone to entry errors. Requires a secure method for displaying/transferring the LMK to the user.
- One-Time Secure Provisioning Channel: Use a temporary secure channel during initial device setup to exchange or generate LMKs. Examples:
- BLE Pairing: Devices pair over BLE using LE Secure Connections, exchange/generate an LMK, then store it for future ESP-NOW communication.
- Temporary SoftAP: One ESP32 acts as a temporary, secured (WPA2-PSK) SoftAP. Other devices connect, and LMKs are exchanged over this encrypted Wi-Fi link.
- Physical Connection: UART, NFC, or even QR codes displayed on one device and scanned by another.
- Pros: Can be quite secure if the provisioning channel itself is robust. Allows for dynamic pairing.
- Cons: Adds complexity to the initial setup/pairing process.
- Cloud-Assisted Provisioning: Devices register with a cloud service, which then distributes LMKs to authorized peers.
- Pros: Scalable, centralized management.
- Cons: Requires internet connectivity, introduces cloud dependency and its own set of security considerations.
Strategy | Description | Pros | Cons |
---|---|---|---|
Pre-programming (Hardcoding) | LMKs embedded directly into device firmware. | Simple for fixed device sets; no user interaction needed post-manufacturing. | Inflexible; LMK compromise requires reprogramming all devices; firmware secrecy critical. Not for dynamic pairing. |
Manual Configuration | Users enter LMKs via serial, web UI, app, etc. | More flexible than hardcoding; LMKs can be changed. | Cumbersome for users; error-prone; requires secure LMK display/transfer to user. |
One-Time Secure Provisioning Channel | Use a temporary secure channel (BLE, temporary SoftAP, physical connection like UART/NFC, QR codes) during setup to exchange/generate LMKs. | Can be very secure if provisioning channel is robust; allows dynamic pairing; LMKs not hardcoded long-term. | Adds complexity to setup/pairing process; requires additional hardware/software for the provisioning channel. |
Cloud-Assisted Provisioning | Devices register with a cloud service, which distributes LMKs to authorized peers. | Scalable; centralized management and revocation; potentially user-friendly. | Requires internet connectivity; cloud dependency; introduces cloud security considerations; subscription costs. |
The choice of LMK provisioning method depends heavily on the application’s security requirements, usability constraints, and deployment scenario.
graph TD subgraph "Device A (Provisioner - e.g., Hub)" A1["Start Provisioning Mode"]:::primary A2["Create Temporary<br>Secured SoftAP (WPA2-PSK)"]:::process A3["Generate/Hold LMK_AB for Device B"]:::process A4["Wait for Device B to Connect"]:::decision A5["Device B Connected"]:::check A6["Securely Transmit LMK_AB<br>to Device B over SoftAP link"]:::process A7["Store LMK_AB for Peer B"]:::process A8["Tear Down SoftAP"]:::process A9["Provisioning Complete for B"]:::success end subgraph "Device B (Client - e.g., Sensor)" B1["Start Pairing Mode"]:::primary B2["Scan for Provisioning SoftAP"]:::process B3{"Found SoftAP_Provisioner?"}:::decision B4["Connect to SoftAP_Provisioner<br>using Pre-shared Key (for SoftAP)"]:::process B5["Receive LMK_AB from Device A"]:::process B6["Store LMK_AB for Peer A"]:::process B7["Disconnect from SoftAP"]:::process B8["Pairing Complete"]:::success end A1 --> A2 A2 --> A3 A3 --> A4 B1 --> B2 B2 --> B3 B3 -- Yes --> B4 B3 -- No --> B2 B4 --> A5 A4 -- "Device B Connects" --> A5 A5 --> A6 A6 --> B5 B5 --> B6 B6 --> B7 A6 -- "LMK Sent" --> A7 A7 --> A8 A8 --> A9 B7 --> B8 %% Styling classDef primary fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6 classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E classDef check fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46 class A1,B1 primary class A2,A3,A6,A7,A8,B2,B4,B5,B6,B7 process class A4,B3 decision class A5 check class A9,B8 success
Limitations of ESP-NOW Security
Limitation | Explanation | Potential Mitigation / Consideration |
---|---|---|
No Protection for Broadcasts | ESP-NOW broadcast messages are always unencrypted. | If broadcast data is sensitive, implement application-layer encryption on top of ESP-NOW, or avoid sending sensitive data via broadcast. |
No Built-in Key Exchange | LMKs must be pre-shared or provisioned via an external mechanism. ESP-NOW does not negotiate keys. | Implement a robust LMK provisioning strategy suitable for the application (e.g., pre-programming, manual entry, temporary secure channel). |
No Forward Secrecy | If an LMK is compromised, all past and future messages encrypted with that LMK are also compromised (unless the LMK is changed). | Regularly update LMKs if possible. For highly sensitive applications, consider protocols with forward secrecy if ESP-NOW is part of a larger system. |
Susceptibility to Replay Attacks (Potentially) | While CCMP offers some replay protection via packet numbers, robust application-level replay detection might be needed. | Implement application-layer sequence numbers, timestamps, or challenge-response mechanisms within the encrypted payload if strong replay protection is critical. |
Limited Number of Encrypted Peers | A configurable limit (e.g., CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM , typically 6-10) on simultaneous encrypted peers due to hardware/resource constraints. |
Design the network topology accordingly. If more peers are needed, manage encrypted peer entries dynamically (add/remove as needed) or increase the limit if supported and resources allow. |
Impact on Performance and Resources
Aspect | Impact Description |
---|---|
CPU Overhead | AES encryption/decryption uses CPU cycles. ESP32 hardware acceleration significantly mitigates this, but it’s not zero. A slight increase in CPU usage may occur, especially at high message rates. |
Latency | The encryption and decryption processes add a small amount of latency to each message. Typically negligible for most applications. |
Memory (RAM) | Storing LMKs for each encrypted peer consumes a small amount of RAM (16 bytes per LMK plus some overhead for peer entry). |
Maximum Encrypted Peers | There’s a configurable limit (e.g., CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM ) on the number of peers with whom encrypted communication can be simultaneously maintained. This is due to hardware/software resource limits for key storage and crypto contexts. |
Packet Size | CCMP adds an 8-byte MIC (Message Integrity Code) and an 8-byte CCMP header to the payload, increasing the overall packet size slightly compared to an unencrypted packet with the same payload. |
Practical Examples
Prerequisites:
- At least two ESP32 boards.
- VS Code with the Espressif IDF Extension.
- ESP-IDF v5.x.
- MAC addresses of the ESP32 boards involved.
Example 1: Basic Encrypted ESP-NOW Communication
This example demonstrates sending and receiving encrypted ESP-NOW messages between two ESP32s using a pre-shared LMK.
Shared LMK (Both Devices):
For this example, we’ll use a hardcoded LMK. In a real product, this should be managed more securely.
// Common LMK for both sender and receiver for this specific peer-to-peer link
static const char* s_my_esp_now_lmk = "THIS_IS_LMK_001"; // Must be 16 bytes
Ensure this LMK is exactly 16 characters long. If it’s shorter, pad it or use a different key. If longer, it will be truncated.
Sender ESP32 Code (main/encrypted_sender_main.c):
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_wifi.h"
#include "esp_now.h"
#define ENC_SENDER_TAG "ENC_SENDER"
// Replace with the MAC address of the receiver ESP32
static uint8_t s_peer_mac[ESP_NOW_ETH_ALEN] = {0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX};
#define WIFI_CHANNEL 1
// Common LMK for this peer
static const char* s_my_esp_now_lmk = "THIS_IS_LMK_001"; // 16 bytes
static void app_esp_now_send_cb(const uint8_t *mac_addr, esp_now_send_status_t status) {
ESP_LOGI(ENC_SENDER_TAG, "Encrypted Send CB to " MACSTR ": %s", MAC2STR(mac_addr),
(status == ESP_NOW_SEND_SUCCESS) ? "Success" : "Fail");
}
static void wifi_init(void) {
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_ERROR_CHECK(esp_wifi_set_channel(WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE));
}
void sender_task(void *pvParameter) {
esp_now_peer_info_t peer_info = {0};
memcpy(peer_info.peer_addr, s_peer_mac, ESP_NOW_ETH_ALEN);
peer_info.channel = WIFI_CHANNEL;
peer_info.ifidx = ESP_IF_WIFI_STA;
peer_info.encrypt = true; // Enable encryption
memcpy(peer_info.lmk, s_my_esp_now_lmk, ESP_NOW_KEY_LEN);
if (esp_now_is_peer_exist(s_peer_mac)) {
ESP_LOGI(ENC_SENDER_TAG, "Peer " MACSTR " exists, modifying for encryption.", MAC2STR(s_peer_mac));
ESP_ERROR_CHECK(esp_now_mod_peer(&peer_info));
} else {
ESP_LOGI(ENC_SENDER_TAG, "Adding encrypted peer " MACSTR, MAC2STR(s_peer_mac));
ESP_ERROR_CHECK(esp_now_add_peer(&peer_info));
}
uint32_t count = 0;
char send_data[100];
while (true) {
sprintf(send_data, "Encrypted Hello! Count: %" PRIu32, count++);
esp_err_t ret = esp_now_send(s_peer_mac, (uint8_t *)send_data, strlen(send_data));
if (ret == ESP_OK) {
ESP_LOGI(ENC_SENDER_TAG, "Encrypted data queued for sending: %s", send_data);
} else {
ESP_LOGE(ENC_SENDER_TAG, "Encrypted send error: %s", esp_err_to_name(ret));
}
vTaskDelay(pdMS_TO_TICKS(3000));
}
}
void app_main(void) {
ESP_ERROR_CHECK(nvs_flash_init());
wifi_init();
ESP_LOGI(ENC_SENDER_TAG, "Initializing ESP-NOW for encrypted sending...");
ESP_ERROR_CHECK(esp_now_init());
ESP_ERROR_CHECK(esp_now_register_send_cb(app_esp_now_send_cb));
uint8_t my_mac[6];
esp_wifi_get_mac(ESP_IF_WIFI_STA, my_mac);
ESP_LOGI(ENC_SENDER_TAG, "My STA MAC: " MACSTR, MAC2STR(my_mac));
xTaskCreate(sender_task, "sender_task", 4096, NULL, 5, NULL);
}
Receiver ESP32 Code (main/encrypted_receiver_main.c):
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_wifi.h"
#include "esp_now.h"
#define ENC_RECEIVER_TAG "ENC_RECEIVER"
#define WIFI_CHANNEL 1
// Common LMK - MUST MATCH THE SENDER'S LMK for communication with that sender
static const char* s_my_esp_now_lmk = "THIS_IS_LMK_001"; // 16 bytes
// Replace with the MAC address of the sender ESP32 if receiver needs to send back encrypted
// or to explicitly configure the sender as an encrypted peer for reception context.
static uint8_t s_sender_peer_mac[ESP_NOW_ETH_ALEN] = {0xYY, 0xYY, 0xYY, 0xYY, 0xYY, 0xYY};
static void app_esp_now_recv_cb(const esp_now_recv_info_t *recv_info, const uint8_t *data, int len) {
if (recv_info == NULL || data == NULL || len <= 0) {
ESP_LOGE(ENC_RECEIVER_TAG, "Receive CB error");
return;
}
const uint8_t *mac_addr = recv_info->src_addr;
ESP_LOGI(ENC_RECEIVER_TAG, "Encrypted data received, %d bytes from " MACSTR ":", len, MAC2STR(mac_addr));
char buffer[len + 1];
memcpy(buffer, data, len);
buffer[len] = '\0'; // Null-terminate if it's a string
ESP_LOGI(ENC_RECEIVER_TAG, "Decrypted Data: %s", buffer);
ESP_LOGI(ENC_RECEIVER_TAG, "RSSI: %d", recv_info->rx_ctrl->rssi);
}
static void wifi_init(void) {
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_ERROR_CHECK(esp_wifi_set_channel(WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE));
}
void app_main(void) {
ESP_ERROR_CHECK(nvs_flash_init());
wifi_init();
ESP_LOGI(ENC_RECEIVER_TAG, "Initializing ESP-NOW for encrypted receiving...");
ESP_ERROR_CHECK(esp_now_init());
ESP_ERROR_CHECK(esp_now_register_recv_cb(app_esp_now_recv_cb));
// Crucial for decryption: The sender must have added this receiver as an encrypted peer.
// Optionally, if the receiver wants to explicitly manage the LMK for incoming messages from a known sender,
// or if it needs to send encrypted replies, it should also add the sender as an encrypted peer.
esp_now_peer_info_t peer_info = {0};
memcpy(peer_info.peer_addr, s_sender_peer_mac, ESP_NOW_ETH_ALEN); // MAC of the sender
peer_info.channel = WIFI_CHANNEL; // Or 0 for current channel
peer_info.ifidx = ESP_IF_WIFI_STA;
peer_info.encrypt = true; // Expect encrypted messages from this peer
memcpy(peer_info.lmk, s_my_esp_now_lmk, ESP_NOW_KEY_LEN);
if (esp_now_is_peer_exist(s_sender_peer_mac)) {
ESP_LOGI(ENC_RECEIVER_TAG, "Sender peer " MACSTR " exists, modifying.", MAC2STR(s_sender_peer_mac));
ESP_ERROR_CHECK(esp_now_mod_peer(&peer_info));
} else {
ESP_LOGI(ENC_RECEIVER_TAG, "Adding sender peer " MACSTR " for encrypted context.", MAC2STR(s_sender_peer_mac));
ESP_ERROR_CHECK(esp_now_add_peer(&peer_info));
}
uint8_t my_mac[6];
esp_wifi_get_mac(ESP_IF_WIFI_STA, my_mac);
ESP_LOGI(ENC_RECEIVER_TAG, "My STA MAC: " MACSTR " - Waiting for encrypted ESP-NOW messages...", MAC2STR(my_mac));
}
Build, Flash, and Observe:
- Get MAC addresses: Flash a utility to each ESP32 to get its STA MAC address.
- In
encrypted_sender_main.c
, updates_peer_mac
with the receiver’s MAC. - In
encrypted_receiver_main.c
, updates_sender_peer_mac
with the sender’s MAC. - Ensure
s_my_esp_now_lmk
is identical and 16 bytes in both files. - Ensure
WIFI_CHANNEL
is the same. - Build and flash.
- Monitor serial outputs. The sender should log successful encrypted sends. The receiver should log reception and print the decrypted data. If you change the LMK on one side only, communication will fail.
Tip:
ESP_NOW_KEY_LEN
is defined as 16. If your LMK string is shorter,memcpy
will only copy the actual length of the string, potentially leaving parts of thelmk
buffer uninitialized or zeroed, leading to LMK mismatch. Ensure your LMK source is exactly 16 bytes.
Variant Notes
The ESP-NOW encryption mechanism (AES-128 CCMP) and the LMK-based peer security model are standard features provided by the ESP-IDF Wi-Fi stack. These security features are consistently available across all ESP32 variants that support ESP-NOW (which are all variants with Wi-Fi):
- ESP32 (Original Series)
- ESP32-S2
- ESP32-S3
- ESP32-C3
- ESP32-C6
- ESP32-H2
The core API functions (esp_now_add_peer
with encryption flags, LMK handling) are the same. The primary consideration across variants might be the CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM
(maximum number of encrypted peers), which could potentially differ in default values or maximum configurable limits based on available resources, though it’s typically a software configuration limit. Always check the Kconfig help for this option for your specific ESP-IDF version and target chip.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
LMK Mismatch | Sender’s send_cb reports ESP_NOW_SEND_FAIL . Receiver doesn’t invoke recv_cb , or recv_cb gets garbage/fails integrity check. No ACK received by sender. |
Ensure the 16-byte LMKs are byte-for-byte identical on both sender and receiver for their respective peer entries. Print LMKs byte-by-byte on both devices during init for debugging. Double-check memcpy length and source. |
encrypt Flag Not Set Correctly |
Communication fails. If sender encrypts and receiver doesn’t expect it (or vice-versa), data is misinterpreted. Sender’s send_cb might fail. |
Both sides must agree. Ensure peer_info.encrypt = true; is set on both the sender (for the receiver peer) and the receiver (for the sender peer, if adding it for context/reply). |
Exceeding Max Encrypted Peer Limit (CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM ) |
esp_now_add_peer() or esp_now_mod_peer() returns ESP_ERR_ESPNOW_ENCRYPT_NUM or ESP_ERR_ESPNOW_IF or other resource exhaustion errors. |
Increase CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM in menuconfig if hardware/memory allows (check Kconfig help). Otherwise, redesign to use fewer concurrent encrypted peers or manage them dynamically (add/remove). |
Incorrect LMK Length | Unpredictable behavior, likely seen as LMK mismatch. memcpy with a source string shorter than 16 bytes (excluding null terminator) into the 16-byte lmk array is a common cause. |
Always use a full 16-byte LMK. For C strings, ensure 16 chars + null terminator, and copy only 16 bytes. E.g., static const char my_lmk[ESP_NOW_KEY_LEN + 1] = "THIS_IS_LMK_001"; and then memcpy(peer.lmk, my_lmk, ESP_NOW_KEY_LEN); . Or use uint8_t lmk_bytes[16] = {...}; . |
Attempting to Encrypt Broadcast Messages | esp_now_add_peer() might return an error if adding broadcast MAC with encrypt = true . If it doesn’t error, encryption won’t be applied to actual broadcast sends. |
ESP-NOW encryption is only for unicast messages. For secure broadcasts, implement application-layer encryption. |
Wi-Fi Channel Mismatch | Devices can’t hear each other, encrypted or not. Send callbacks may fail. | Ensure both ESP32s are operating on the same Wi-Fi channel. Use esp_wifi_set_channel() consistently. Peer registration also includes a channel field. |
Receiver Not Adding Sender as Encrypted Peer | Even if the sender correctly encrypts and sends, the receiver might not decrypt if it hasn’t configured the sender’s MAC address with the shared LMK and encrypt = true . |
On the receiver, explicitly add the sender as a peer with encrypt = true and the correct LMK. This primes the ESP-NOW stack to use that LMK for incoming packets from that sender’s MAC. |
Exercises
- Dynamic LMK Entry and Update:
- Modify the encrypted sender/receiver example. Instead of a hardcoded LMK, allow the user to input a 16-character LMK via the serial monitor (UART) on both devices at startup.
- Implement a mechanism (e.g., a specific ESP-NOW command or a button press) that allows one device to signal to the other that it wants to update the LMK. Then, both devices should prompt for a new LMK via UART and update their peer information. (Note: The LMK exchange itself is not part of this exercise, just the coordinated update after manual entry).
- Secure Remote Sensor:
- Sensor ESP32: Simulates reading a sensor value. It has a pre-shared LMK with a Gateway ESP32. It sends its sensor readings encrypted via ESP-NOW to the Gateway.
- Gateway ESP32: Has the same pre-shared LMK for the Sensor. It receives the encrypted sensor data, decrypts it, and prints the value.
- Add a simple “alert” threshold. If the sensor value crosses the threshold, the Gateway should log an “ENCRYPTED ALERT RECEIVED” message.
- LMK Provisioning Discussion & Design:
- Research and write a short design document (not code) outlining two different LMK provisioning methods for a hypothetical ESP-NOW based product (e.g., a set of smart home switches and a central hub).
- Discuss the pros, cons, and security implications of each chosen method (e.g., one method could be factory pre-programming, another could be user-initiated pairing via a temporary SoftAP). Consider usability for the end-user.
Summary
- ESP-NOW supports AES-128 CCMP encryption for unicast messages, providing confidentiality and integrity.
- A shared 16-byte Local Master Key (LMK) is required for each encrypted peer-to-peer link and must be identical on both devices.
- The
encrypt
flag andlmk
field inesp_now_peer_info_t
are used to configure encrypted peers. - LMK provisioning is a critical application-level responsibility, as ESP-NOW does not provide a key exchange mechanism.
- Broadcast messages in ESP-NOW are not encrypted.
- There’s a configurable limit on the number of concurrent encrypted peers.
- All Wi-Fi capable ESP32 variants support ESP-NOW’s encryption features consistently.
Further Reading
- ESP-IDF Programming Guide – ESP-NOW Security: While the main ESP-NOW page covers adding peers with encryption, specific detailed documents on ESP-NOW security might be part of broader security documentation. Refer to the main ESP-NOW API page: https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-reference/network/esp_now.html
- ESP-IDF
CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM
: Check the Kconfig option inmenuconfig
underComponent config -> ESP Wi-Fi
for details on this limit.
