Chapter 60: BLE Security Levels and Pairing
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand the fundamental goals and mechanisms of BLE security.
- Describe the roles of the Security Manager Protocol (SMP) in BLE.
- Explain the pairing process, including feature exchange and key generation.
- Differentiate between Legacy Pairing and LE Secure Connections (LESC).
- Implement various pairing methods based on I/O capabilities (Just Works, Passkey Entry, Numeric Comparison).
- Understand and implement bonding to store and reuse security keys.
- Configure different BLE security modes and levels.
- Utilize ESP-IDF APIs to manage pairing, bonding, and encryption.
- Secure GATT attributes using appropriate permissions.
- Recognize the importance of privacy features like Identity Resolving Key (IRK).
Introduction
In our exploration of Bluetooth Low Energy, we’ve covered how devices discover each other, connect, and exchange data using GATT services and characteristics. However, in many real-world applications, the data exchanged is sensitive, or the control exerted over a device is critical. Consider a smart lock, a medical sensor, or a payment system – unauthorized access or data tampering in these scenarios could have severe consequences. This is where BLE security becomes paramount.
BLE provides a robust framework for securing wireless communication, encompassing authentication, authorization, confidentiality, and integrity. This chapter delves into the mechanisms that protect BLE links, focusing on pairing (the process of establishing a trusted relationship and generating cryptographic keys) and bonding (storing these keys for future use). We will explore different security levels, pairing methods, the enhanced security offered by LE Secure Connections (LESC), and how to implement these features using ESP-IDF v5.x on your ESP32.
Understanding and correctly implementing BLE security is not just an optional add-on; it’s a fundamental requirement for building trustworthy and resilient BLE-enabled products.
Theory
BLE security is managed by the Security Manager Protocol (SMP), which runs over a fixed L2CAP channel. SMP is responsible for identity and key management, including pairing, key distribution, and device authentication.
Fundamental Security Goals in BLE
- Authentication: Verifying that a peer device is who it claims to be, protecting against impersonation.
- Authorization: Ensuring that a peer device is permitted to access specific resources or perform certain actions (primarily handled at the GATT attribute level via permissions, but reliant on an authenticated link).
- Confidentiality (Encryption): Protecting data from eavesdropping by encrypting it during transmission. BLE uses AES-CCM (Advanced Encryption Standard – Counter with CBC-MAC) for encryption.
- Integrity (Data Signing): Ensuring that data has not been tampered with during transmission. This is also achieved using AES-CCM or through specific data signing keys (CSRK).
- Privacy: Preventing unauthorized tracking of a device by using resolvable private addresses (RPAs).
Pairing: Establishing Trust and Keys
Pairing is the process by which two BLE devices establish a trusted relationship and generate shared secret keys. These keys are then used to secure the connection.
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%% sequenceDiagram participant DeviceA as Initiator (e.g., Client) participant DeviceB as Responder (e.g., Server) Note over DeviceA, DeviceB: Phase 1: Pairing Feature Exchange DeviceA->>DeviceB: Pairing Request (I/O Caps, AuthReq, Key Size) activate DeviceB DeviceB->>DeviceA: Pairing Response (I/O Caps, AuthReq, Key Size) deactivate DeviceB Note over DeviceA, DeviceB: Both devices determine pairing method <br>(Just Works, Passkey, etc.) alt Legacy Pairing Note over DeviceA, DeviceB: Phase 2 (Legacy): STK Generation DeviceA->>DeviceB: Confirmations / Random Numbers (depends on method) DeviceB->>DeviceA: Confirmations / Random Numbers (depends on method) Note over DeviceA, DeviceB: STK (Short Term Key) generated. Link encrypted with STK. else LE Secure Connections (LESC) Note over DeviceA, DeviceB: Phase 2 (LESC): LTK Generation (ECDH) DeviceA->>DeviceB: ECDH Public Key A activate DeviceB DeviceB->>DeviceA: ECDH Public Key B deactivate DeviceB Note over DeviceA, DeviceB: Both devices compute shared secret via ECDH. alt Numeric Comparison or Passkey Entry DeviceA->>DeviceB: Nonces / Commitments for Auth DeviceB->>DeviceA: Nonces / Commitments for Auth end Note over DeviceA, DeviceB: LTK (Long Term Key) generated. Link encrypted with LTK. end Note over DeviceA, DeviceB: Phase 3: Transport Specific Key Distribution (TSKD) - If Bonding DeviceA->>DeviceB: Distribute Keys (e.g., IRK, CSRK, LTK if LESC & bonding) activate DeviceB DeviceB->>DeviceA: Distribute Keys (e.g., IRK, CSRK, LTK if LESC & bonding) deactivate DeviceB Note over DeviceA, DeviceB: Keys stored if bonding enabled.
Pairing Phases:
Pairing typically involves three phases:
- Phase 1: Pairing Feature Exchange:
- Both devices exchange their I/O capabilities, authentication requirements (MITM protection, LESC support, bonding flags), and maximum encryption key size.
- I/O Capabilities: Define what input/output mechanisms the device has for user interaction during pairing. This is crucial for selecting the pairing method.
ESP_IO_CAP_OUT
(DisplayOnly): Device can display a 6-digit number (e.g., LCD).ESP_IO_CAP_IO
(DisplayYesNo): Device can display a 6-digit number and has Yes/No input capability.ESP_IO_CAP_IN
(KeyboardOnly): Device has a numeric keyboard but no display.ESP_IO_CAP_NONE
(NoInputNoOutput): Device has no display and no keyboard.ESP_IO_CAP_KBDISP
(KeyboardDisplay): Device has both a keyboard and a display.
- Authentication Requirements (AuthReq): Flags indicating bonding preferences, MITM protection requirement, LE Secure Connections support, etc.
I/O Capability | ESP-IDF Macro | Description | Example Device Type |
---|---|---|---|
DisplayOnly | ESP_IO_CAP_OUT | Device can display a 6-digit number but has no input. | Simple sensor with a display, some smartwatches. |
DisplayYesNo | ESP_IO_CAP_IO | Device can display a 6-digit number and has Yes/No input capability (e.g., two buttons). | Smartwatch with confirmation buttons, fitness trackers. |
KeyboardOnly | ESP_IO_CAP_IN | Device has a numeric keyboard (or equivalent input for 6 digits) but no display. | BLE remote control, some headsets with input buttons. |
NoInputNoOutput | ESP_IO_CAP_NONE | Device has no display and no keyboard/buttons for pairing interaction. | Wireless mouse, simple beacons, embedded sensors without UI. |
KeyboardDisplay | ESP_IO_CAP_KBDISP | Device has both a numeric keyboard (or equivalent) and a display. | Smartphones, tablets, computers. |
- Phase 2: Key Generation & Exchange:
- This phase differs significantly between Legacy Pairing and LE Secure Connections.
- Legacy Pairing (BLE 4.0/4.1):
- Generates a temporary key called the Short Term Key (STK).
- The method used to generate the STK depends on the I/O capabilities of the two devices (Just Works, Passkey Entry, OOB).
- Vulnerable to passive eavesdropping during key exchange if “Just Works” or unauthenticated OOB is used.
- LE Secure Connections (LESC) (BLE 4.2+):
- Uses Elliptic Curve Diffie-Hellman (ECDH) public key cryptography to generate a Long Term Key (LTK).
- Provides significantly stronger security, including protection against passive eavesdropping and active Man-In-The-Middle (MITM) attacks (when using authenticated methods like Numeric Comparison or Passkey Entry).
- Numeric Comparison pairing method is exclusive to LESC. “Just Works” under LESC still uses ECDH, offering better protection than legacy Just Works.
Feature | Legacy Pairing (BLE 4.0/4.1) | LE Secure Connections (LESC) (BLE 4.2+) |
---|---|---|
Primary Key Generated | Short Term Key (STK) | Long Term Key (LTK) |
Key Exchange Method | Based on pairing method (Just Works, Passkey, OOB) – STK generation. | Elliptic Curve Diffie-Hellman (ECDH) public key cryptography. |
Protection Against Passive Eavesdropping | Vulnerable (especially with Just Works or unauthenticated OOB). | Resistant (due to ECDH). |
Protection Against Active MITM Attacks | Limited; relies on authenticated pairing method (Passkey, authenticated OOB). Just Works offers no MITM protection. | Stronger; Numeric Comparison provides MITM protection. Passkey Entry also provides MITM. LESC Just Works is better than legacy but still no MITM without OOB auth of public keys. |
Pairing Methods Supported | Just Works, Passkey Entry, Out-of-Band (OOB). | Just Works, Passkey Entry, Numeric Comparison, Out-of-Band (OOB). |
Numeric Comparison Method | Not available. | Available (most secure user-assisted method). |
Overall Security Strength | Moderate (can be weak if Just Works is used). | High (especially with authenticated methods). |
ESP32 Support | Yes | Yes (hardware supports ECDH). |
- Phase 3: Transport Specific Key Distribution (TSKD):
- After the STK (legacy) or LTK (LESC) is established and the link is encrypted, devices can distribute additional keys:
- Identity Resolving Key (IRK): Used to generate and resolve Resolvable Private Addresses (RPAs), enhancing privacy.
- Connection Signature Resolving Key (CSRK): Used for signing data at the ATT layer, ensuring data integrity for unencrypted characteristics (less common when encryption is active).
- The LTK itself (if generated during LESC, or derived from STK in legacy if bonding) is also considered distributed/established in this phase for bonding.
- After the STK (legacy) or LTK (LESC) is established and the link is encrypted, devices can distribute additional keys:
Pairing Methods
The pairing method is determined by the I/O capabilities exchanged in Phase 1 and whether LESC is used.
Pairing Method | User Interaction | Typical I/O Capabilities Involved | MITM Protection (Legacy) | MITM Protection (LESC) | Use Case Example |
---|---|---|---|---|---|
Just Works | None. Automatic. | At least one device is NoInputNoOutput. | No | No (but ECDH protects vs passive eavesdropping) | Wireless mouse, simple sensor. |
Passkey Entry | One device displays 6-digit key, other enters it. | DisplayOnly + KeyboardOnly; KeyboardDisplay + KeyboardOnly; DisplayOnly + KeyboardDisplay. |
Yes | Yes | Headset + Phone. |
Numeric Comparison | Both devices display 6-digit key, user confirms match (Yes/No). | Both devices: DisplayYesNo or KeyboardDisplay. (LESC Only) | N/A | Yes (Strongest user-assisted) | Phone + Smartwatch. |
Out-of-Band (OOB) | Security data exchanged via separate channel (e.g., NFC, QR code). | Any (OOB data flag set). | Yes (if OOB channel is secure) | Yes (if OOB channel is secure) | NFC tap-to-pair. |
- Just Works:
- Used when at least one device has
NoInputNoOutput
I/O capability (or when both areNoInputNoOutput
). - No user interaction is required. The devices pair automatically.
- Legacy Pairing: Provides no MITM protection. An attacker could impersonate one of the devices.
- LE Secure Connections: If both devices support LESC and use it for Just Works, ECDH provides protection against passive eavesdropping, but still no MITM protection without further authentication.
- Use Case: Simple devices like a wireless mouse or basic sensor where user interaction is not feasible.
- Used when at least one device has
- Passkey Entry:
- One device displays a 6-digit random passkey (acting as
DisplayOnly
orKeyboardDisplay
), and the user enters this passkey on the other device (acting asKeyboardOnly
orKeyboardDisplay
). - Provides MITM protection in both Legacy Pairing and LESC.
- Use Case: Pairing a headset (keyboard input) with a phone (display).
- One device displays a 6-digit random passkey (acting as
- Numeric Comparison (LE Secure Connections Only):
- Both devices must support LESC and have
DisplayYesNo
orKeyboardDisplay
capabilities. - Each device generates and displays a 6-digit number. The user compares the numbers on both devices and confirms if they match (Yes/No input).
- Provides strong MITM protection. Considered the most secure user-assisted pairing method.
- Use Case: Pairing two smartphones, or a smartwatch with a phone.
- Both devices must support LESC and have
- Out-of-Band (OOB):
- Security data (like a temporary key or public keys for LESC) is exchanged through a separate, secure channel (e.g., NFC, QR code).
- If the OOB channel is secure, this method provides strong MITM protection.
- Use Case: Tapping an NFC-enabled device to a reader to initiate pairing.
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%% graph TD A[Start: Pairing Feature Exchange Complete] --> B{Both Devices Support LESC?}; B -- Yes --> C{OOB Data Available for LESC?}; C -- Yes --> D[LESC OOB Pairing]; C -- No --> E{"Numeric Comparison Possible?<br>(Both DisplayYesNo or KbdDisp)"}; E -- Yes --> F[LESC Numeric Comparison]; E -- No --> G{"Passkey Entry Possible ? <br>(e.g., One DisplayOnly, Other KbdOnly/KbdDisp)"}; G -- Yes --> H[LESC Passkey Entry]; G -- No --> I[LESC Just Works]; B -- No (At least one uses Legacy) --> J{OOB Data Available for Legacy?}; J -- Yes --> K[Legacy OOB Pairing]; J -- No --> L{"Passkey Entry Possible ? <br>(e.g., One DisplayOnly, Other KbdOnly/KbdDisp)"}; L -- Yes --> M[Legacy Passkey Entry]; L -- No --> N[Legacy Just Works]; D --> Z[End Pairing Method Selected]; F --> Z; H --> Z; I --> Z; K --> Z; M --> Z; N --> Z; classDef startNode fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef decisionNode fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef processNode fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef endNode fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46; class A startNode; class B,C,E,G,J,L decisionNode; class D,F,H,I,K,M,N processNode; class Z endNode;
Bonding: Remembering Paired Devices
Bonding is the process of storing the security keys (LTK, IRK, CSRK) and identity information (peer device address) generated during pairing. This allows bonded devices to reconnect securely in the future without repeating the full pairing procedure.
- When a bonded device reconnects, it can re-establish an encrypted link using the stored LTK.
- If using private addresses, the IRK allows the device to recognize its bonded peer.
- Bonding information is typically stored in Non-Volatile Storage (NVS) on the ESP32.
- The decision to bond is usually indicated by setting a “Bonding Flag” during the Pairing Feature Exchange.
LE Secure Connections (LESC)
Introduced in Bluetooth Core Specification v4.2, LESC significantly enhances BLE security:
- ECDH Key Exchange: Uses Elliptic Curve Diffie-Hellman for key generation, which is resistant to passive eavesdropping.
- Numeric Comparison: A user-friendly and secure pairing method.
- Improved MITM Protection: Even “Just Works” under LESC is more secure than legacy “Just Works” because the key exchange itself is protected. However, for true MITM protection with LESC Just Works, OOB authentication of the public keys would be needed.
- ESP32 hardware (from original ESP32 onwards) supports the necessary cryptographic operations for LESC.
Security Modes and Levels
BLE defines security modes and levels to categorize the security of a connection:
Mode | Level | Description | MITM Protection | Pairing Type Example |
---|---|---|---|---|
LE Security Mode 1 (Encryption Focused) |
Level 1: No Security | No authentication, no encryption. Data sent in plain text. | No | N/A (No pairing) |
Level 2: Unauthenticated Pairing with Encryption | Pairing occurs without MITM protection. Link is encrypted. | No | Legacy Just Works | |
Level 3: Authenticated Pairing with Encryption | Pairing occurs with MITM protection. Link is encrypted. | Yes | Legacy Passkey Entry, Authenticated OOB | |
Level 4: Authenticated LE Secure Connections Pairing with Encryption | Pairing uses LESC with MITM protection. Link is encrypted. (Highest security for link encryption) | Yes | LESC Numeric Comparison, LESC Passkey Entry, Authenticated LESC OOB | |
LE Security Mode 2 (Data Signing Focused) |
Deals with connectionless data signing using CSRK. Less common when link-layer encryption (Mode 1) is active. | Yes (if CSRK from authenticated pairing) | N/A (Applies to signed data packets) |
- LE Security Mode 1 (Encryption Focused):
- Level 1: No Security. No authentication, no encryption. Data is sent in plain text.
- Level 2: Unauthenticated Pairing with Encryption. Pairing occurs without MITM protection (e.g., legacy Just Works). The link is encrypted using the generated key.
- Level 3: Authenticated Pairing with Encryption. Pairing occurs with MITM protection (e.g., Passkey Entry, Numeric Comparison, authenticated OOB). The link is encrypted.
- Level 4: Authenticated LE Secure Connections Pairing with Encryption. Pairing uses LESC with MITM protection. The link is encrypted. This is the highest security level for link encryption.
- LE Security Mode 2 (Data Signing Focused):
- This mode deals with connectionless data signing using the CSRK. It’s less commonly used when link-layer encryption (Mode 1) is active.
Attribute Permissions and Link Security
GATT characteristics and descriptors have permissions (e.g., ESP_GATT_PERM_READ_ENCRYPTED
, ESP_GATT_PERM_WRITE_ENC_MITM
).
- If an attribute requires encryption, the BLE stack will automatically attempt to encrypt the link if it’s not already encrypted. This might trigger a security request and pairing if the devices are not already bonded with an LTK.
- If an attribute requires authentication (MITM protection), the link must have been established with at least Security Mode 1, Level 3 (or Level 4 for LESC).
Privacy and Identity Resolving Key (IRK)
To prevent tracking, BLE devices can use Resolvable Private Addresses (RPAs) instead of their public MAC address during advertising and connection.
- An RPA is generated using the device’s Identity Resolving Key (IRK) and a random component.
- The IRK is exchanged and stored during bonding.
- Only devices that possess the peer’s IRK can resolve its RPA back to its true identity address.
- This makes it difficult for unauthorized listeners to track a device over time by its address.
ESP-IDF Security APIs
ESP-IDF provides APIs (primarily in esp_gap_ble_api.h
) to configure and manage BLE security:
- Setting Security Parameters:
esp_ble_gap_set_security_param(esp_ble_sm_param_t param_type, void *value, uint8_t len)
: Used to set I/O capability, authentication requirements, key sizes, and initiate security requests.ESP_BLE_SM_SET_INIT_KEY
: Sets initiator keys (LTK, IRK, CSRK to be distributed).ESP_BLE_SM_SET_RSP_KEY
: Sets responder keys (LTK, IRK, CSRK to be distributed).ESP_BLE_SM_IO_CAP_REQ
: Sets the device’s I/O capability.ESP_BLE_SM_AUTHEN_REQ_MODE
: Sets authentication requirements (bonding flags, MITM, LESC).ESP_BLE_SM_MAX_KEY_SIZE
,ESP_BLE_SM_MIN_KEY_SIZE
: Key size preferences.
- Initiating Encryption/Pairing (Central Role):
esp_ble_set_encryption(esp_bd_addr_t bd_addr, esp_ble_sec_act_t sec_act)
: A central device can use this to request encryption (ESP_BLE_SEC_ENCRYPT
) or encryption with MITM protection (ESP_BLE_SEC_ENCRYPT_MITM_REQ
). This will trigger pairing if necessary.
- Handling Security Events (GAP Callback):
ESP_GAP_BLE_SEC_REQ_EVT
: Peripheral receives a security request from the central. The peripheral should respond by callingesp_ble_gap_security_rsp()
.ESP_GAP_BLE_PASSKEY_REQ_EVT
: Device needs the user to enter a passkey. Respond withesp_ble_gap_passkey_reply()
.ESP_GAP_BLE_PASSKEY_NOTIF_EVT
: Device needs to display a passkey to the user.ESP_GAP_BLE_NC_REQ_EVT
(LESC Numeric Comparison): Device needs user to confirm displayed numbers match. Respond withesp_ble_gap_numeric_reply()
.ESP_GAP_BLE_KEY_EVT
: Provides the encryption key (e.g., LTK) after successful pairing/bonding. This is where you might store bonded device information.ESP_GAP_BLE_AUTH_CMPL_EVT
: Authentication/Pairing complete event. Indicates success or failure and provides bonding status and link encryption status.ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT
: Confirmation of bond removal.
- Bonding Management:
- ESP-IDF handles storing bonded device information in NVS automatically if bonding flags are set.
esp_ble_get_bond_device_list()
: Retrieves the list of bonded devices.esp_ble_remove_bond_device()
: Removes bonding information for a device.
API Function / Event | Parameter Type (esp_ble_sm_param_t for set_security_param) | Purpose |
---|---|---|
esp_ble_gap_set_security_param() | ESP_BLE_SM_AUTHEN_REQ_MODE | Sets authentication requirements (Bonding, MITM, LESC flags). Value is esp_ble_auth_req_t. |
esp_ble_gap_set_security_param() | ESP_BLE_SM_IOCAP_MODE | Sets the device’s I/O capability. Value is esp_ble_io_cap_t. |
esp_ble_gap_set_security_param() | ESP_BLE_SM_MAX_KEY_SIZE / MIN_KEY_SIZE | Sets preferred encryption key size (7-16 bytes). Value is uint8_t. |
esp_ble_gap_set_security_param() | ESP_BLE_SM_SET_INIT_KEY / ESP_BLE_SM_SET_RSP_KEY | Sets which keys the device will distribute (Initiator) or accept (Responder) – LTK, IRK, CSRK. Value is uint8_t bitmask. |
esp_ble_set_encryption() | (N/A – Direct function) | Client initiates encryption/pairing with a peer. Takes esp_bd_addr_t, esp_ble_sec_act_t. |
esp_ble_gap_security_rsp() | (N/A – Direct function) | Peripheral responds to a security request from the client. Takes esp_bd_addr_t, bool accept. |
esp_ble_gap_passkey_reply() | (N/A – Direct function) | Device provides the entered passkey during Passkey Entry. Takes esp_bd_addr_t, bool accept, uint32_t passkey. |
esp_ble_gap_numeric_reply() | (N/A – Direct function) | Device confirms (or denies) match during Numeric Comparison. Takes esp_bd_addr_t, bool accept. |
esp_ble_get_bond_device_list() | (N/A – Direct function) | Retrieves the list of bonded devices from NVS. |
esp_ble_remove_bond_device() | (N/A – Direct function) | Removes bonding information for a specific device. |
Event: ESP_GAP_BLE_SEC_REQ_EVT | (GAP Callback Event) | Peripheral received security request from client. Must call esp_ble_gap_security_rsp(). |
Event: ESP_GAP_BLE_PASSKEY_REQ_EVT | (GAP Callback Event) | Device needs user to enter a passkey. Must call esp_ble_gap_passkey_reply(). |
Event: ESP_GAP_BLE_PASSKEY_NOTIF_EVT | (GAP Callback Event) | Device needs to display a passkey to the user. (Param: key_notif.passkey) |
Event: ESP_GAP_BLE_NC_REQ_EVT | (GAP Callback Event) | LESC Numeric Comparison: device displays number, user confirms. Must call esp_ble_gap_numeric_reply(). (Param: key_notif.passkey) |
Event: ESP_GAP_BLE_AUTH_CMPL_EVT | (GAP Callback Event) | Authentication/Pairing complete. Indicates success/failure, bonding status, MITM, LESC, key type. |
Event: ESP_GAP_BLE_KEY_EVT | (GAP Callback Event) | Provides encryption key information (e.g., LTK) after successful pairing/bonding. |
Practical Examples
Prerequisites:
- Two ESP32 boards (ESP32, ESP32-C3, ESP32-S3, ESP32-C6, ESP32-H2). One will act as a GATT server (peripheral), the other as a GATT client (central).
- VS Code with the Espressif IDF Extension.
- For Passkey/Numeric Comparison, you’ll need to observe serial monitor output on both ESP32s.
Tip: For these examples, it’s helpful to start with the GATT server and client examples from Chapters 57 and 59 and then add the security configurations. The focus here is on the security setup and event handling.
Example 1: “Just Works” Pairing with Bonding
This example demonstrates simple pairing without user interaction, enabling encryption and bonding.
Device A (GATT Server/Peripheral):
Modify the GATT server from Chapter 57.
// In app_main() before GATTS registration:
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND; // Require LESC, MITM (will fallback if client doesn't support), and Bonding
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; // No Input No Output
uint8_t key_size = 16; // Minimum 7, maximum 16
uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK; // Distribute LTK and IRK
uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK; // Accept LTK and IRK
esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(uint8_t)); // Server as initiator of key distribution
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t)); // Server as responder of key distribution
// In your GAP event handler (esp_gap_cb for server):
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
// ... (other GAP events like advertising start) ...
switch (event) {
// ...
case ESP_GAP_BLE_SEC_REQ_EVT:
ESP_LOGI(GATTS_TAG, "ESP_GAP_BLE_SEC_REQ_EVT");
// Send security response to the peer device.
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
break;
case ESP_GAP_BLE_AUTH_CMPL_EVT:
if (param->ble_security.auth_cmpl.success) {
ESP_LOGI(GATTS_TAG, "Authentication success: bd_addr %02x:%02x:%02x:%02x:%02x:%02x, bond: %d, MITM: %d, LESC: %d, key_type: 0x%02x",
param->ble_security.auth_cmpl.bd_addr[0], param->ble_security.auth_cmpl.bd_addr[1],
param->ble_security.auth_cmpl.bd_addr[2], param->ble_security.auth_cmpl.bd_addr[3],
param->ble_security.auth_cmpl.bd_addr[4], param->ble_security.auth_cmpl.bd_addr[5],
param->ble_security.auth_cmpl.is_bond,
param->ble_security.auth_cmpl.mitm,
param->ble_security.auth_cmpl.sec_mode, // Actually indicates LESC if >= ESP_LE_KEY_TYPE_PLC_ESP_AES_CMAC_T2_KEY
param->ble_security.auth_cmpl.key_type);
if (param->ble_security.auth_cmpl.is_bond) {
ESP_LOGI(GATTS_TAG, "Device bonded.");
}
} else {
ESP_LOGE(GATTS_TAG, "Authentication failed, reason 0x%x", param->ble_security.auth_cmpl.fail_reason);
}
break;
// ... (handle other events like ESP_GAP_BLE_KEY_EVT to see distributed keys)
default:
break;
}
}
Device B (GATT Client/Central):
Modify the GATT client from Chapter 59.
// In app_main() before GATTC registration:
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND;
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE;
uint8_t key_size = 16;
uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t));
// In GATTC event handler (esp_gattc_cb for client):
static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
// ... (other GATTC events) ...
switch (event) {
case ESP_GATTC_OPEN_EVT:
if (param->open.status == ESP_GATT_OK) {
ESP_LOGI(GATTC_TAG, "Connected successfully, initiating encryption...");
current_conn_id = param->open.conn_id; // Store conn_id
memcpy(remote_bda_connected, param->open.remote_bda, sizeof(esp_bd_addr_t)); // Store remote BDA
// Initiate encryption (will trigger pairing if not bonded)
esp_err_t enc_ret = esp_ble_set_encryption(param->open.remote_bda, ESP_BLE_SEC_ENCRYPT_MITM); // Request encryption with MITM (will adapt based on capabilities)
if (enc_ret != ESP_OK) {
ESP_LOGE(GATTC_TAG, "Failed to initiate encryption: %s", esp_err_to_name(enc_ret));
}
} // ... else handle connection failure ...
break;
// ...
default:
break;
}
}
// In GAP event handler (esp_gap_cb for client):
// (Handle ESP_GAP_BLE_AUTH_CMPL_EVT similar to the server to see pairing results)
// (Handle ESP_GAP_BLE_SEC_REQ_EVT if peripheral requests security first, though client usually initiates)
Build, Flash, Observe:
- Flash both ESP32s.
- Observe logs. The client should connect, then initiate encryption. This will trigger the pairing process.
- Since both have
ESP_IO_CAP_NONE
andESP_LE_AUTH_REQ_SC_MITM_BOND
(which includes bonding and allows LESC if both support it), they should perform Just Works pairing (potentially LESC Just Works). ESP_GAP_BLE_AUTH_CMPL_EVT
should indicate success and bonding.- Disconnect and reconnect. The devices should now use the bonded information to encrypt the link without full re-pairing. Check logs for encryption status.
Example 2: Passkey Entry Pairing
Device A (GATT Server/Peripheral – Displays Passkey):
// In app_main() for server:
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND; // Require MITM
esp_ble_io_cap_t iocap = ESP_IO_CAP_OUT; // DisplayOnly (or KBDISP if it also had a keyboard)
// ... (set other security params as in Example 1) ...
// In GAP event handler for server:
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: // Server displays this passkey
ESP_LOGI(GATTS_TAG, "ESP_GAP_BLE_PASSKEY_NOTIF_EVT, passkey: %" PRIu32, param->ble_security.key_notif.passkey);
// In a real app, display this on an LCD.
break;
Device B (GATT Client/Central – Enters Passkey):
// In app_main() for client:
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND; // Require MITM
esp_ble_io_cap_t iocap = ESP_IO_CAP_IN; // KeyboardOnly (or KBDISP)
// ... (set other security params as in Example 1) ...
// In GAP event handler for client:
case ESP_GAP_BLE_PASSKEY_REQ_EVT: // Client needs to enter the passkey displayed by server
ESP_LOGI(GATTC_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT, please enter passkey shown on peer device");
// In a real app, get 6-digit input from user. For example, read from UART.
// For this example, we'll simulate by hardcoding or assuming a fixed value for testing.
// uint32_t passkey_entered = 123456; // Replace with actual input mechanism
// esp_ble_gap_passkey_reply(param->ble_security.key_req.bd_addr, true, passkey_entered);
ESP_LOGW(GATTC_TAG, "Passkey request - implement user input and call esp_ble_gap_passkey_reply()");
// For testing, you might need to manually type the passkey from server's log into client's serial input
// and have a task that reads UART and calls esp_ble_gap_passkey_reply.
break;
Build, Flash, Observe:
- Flash both.
- Server (Device A) will log
ESP_GAP_BLE_PASSKEY_NOTIF_EVT
with a 6-digit passkey. - Client (Device B) will log
ESP_GAP_BLE_PASSKEY_REQ_EVT
. - You need a way to get the passkey from Server A’s log and provide it to Client B (e.g., via serial input on Client B, which then calls
esp_ble_gap_passkey_reply
). - If passkeys match, pairing completes with MITM protection.
Example 3: Numeric Comparison (LESC)
Device A (e.g., Server):
// In app_main() for server:
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND; // SC for Secure Connections, MITM
esp_ble_io_cap_t iocap = ESP_IO_CAP_IO; // DisplayYesNo (or KBDISP)
// ... (set other security params) ...
// In GAP event handler for server:
case ESP_GAP_BLE_NC_REQ_EVT: // Numeric Comparison request
ESP_LOGI(GATTS_TAG, "ESP_GAP_BLE_NC_REQ_EVT, passkey: %" PRIu32, param->ble_security.key_notif.passkey); // This is the number to compare
ESP_LOGI(GATTS_TAG, "Please compare with peer device and confirm (Y/N).");
// In a real app, display number and get Yes/No input.
// For example, if numbers match:
// esp_ble_gap_numeric_reply(param->ble_security.key_notif.bd_addr, true);
ESP_LOGW(GATTS_TAG, "Numeric comparison - implement user confirmation and call esp_ble_gap_numeric_reply()");
break;
Device B (e.g., Client):
// In app_main() for client:
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND;
esp_ble_io_cap_t iocap = ESP_IO_CAP_IO; // DisplayYesNo (or KBDISP)
// ... (set other security params) ...
// In GAP event handler for client:
case ESP_GAP_BLE_NC_REQ_EVT:
ESP_LOGI(GATTC_TAG, "ESP_GAP_BLE_NC_REQ_EVT, passkey: %" PRIu32, param->ble_security.key_notif.passkey);
ESP_LOGI(GATTC_TAG, "Please compare with peer device and confirm (Y/N).");
// esp_ble_gap_numeric_reply(param->ble_security.key_notif.bd_addr, true); // If match
ESP_LOGW(GATTC_TAG, "Numeric comparison - implement user confirmation and call esp_ble_gap_numeric_reply()");
break;
Build, Flash, Observe:
- Both devices will log
ESP_GAP_BLE_NC_REQ_EVT
with a 6-digit number. - Verify the numbers are the same on both serial monitors.
- Implement a way to send
esp_ble_gap_numeric_reply(bd_addr, true)
on both devices if they match (e.g., via a serial command). - Pairing completes with strong MITM protection.
Variant Notes
- ESP32-S2: As this variant lacks Bluetooth hardware, BLE security features are not applicable.
- ESP32 (Original), ESP32-S3, ESP32-C3, ESP32-C6, ESP32-H2: All these variants fully support the BLE security features described, including Legacy Pairing and LE Secure Connections (LESC). They have the necessary hardware acceleration for cryptographic operations like AES and ECDH. The ESP-IDF security APIs are consistent across these chips.
- LESC Support: While the hardware supports LESC, ensure your
esp_ble_auth_req_t
flags correctly request or allow LESC (e.g.,ESP_LE_AUTH_REQ_SC_ONLY
orESP_LE_AUTH_REQ_SC_BOND
orESP_LE_AUTH_REQ_SC_MITM_BOND
). If one device does not support or is not configured for LESC, the pairing might fall back to legacy methods or fail, depending on the strictness of the configuration.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
Mismatched I/O Capabilities or AuthReq Flags | Pairing fails (e.g., ESP_GATT_AUTH_FAIL in ESP_GAP_BLE_AUTH_CMPL_EVT). No common pairing method can be selected. | Ensure I/O capabilities allow a valid pairing method. Both devices must agree on LESC if used. Check Bluetooth Core Spec pairing tables. Ensure auth_req flags (Bonding, MITM, LESC) are compatible. |
Not Responding to Security GAP Events | Pairing process stalls or times out. | Implement handlers for all relevant security events (ESP_GAP_BLE_SEC_REQ_EVT, _PASSKEY_REQ_EVT, _NC_REQ_EVT) and call appropriate response functions (_security_rsp, _passkey_reply, _numeric_reply) promptly. |
Incorrect Key Distribution Configuration | Bonding fails, devices can’t reconnect securely, privacy (IRK) features don’t work. Failure to exchange LTK, IRK, or CSRK. | Ensure ESP_BLE_SM_SET_INIT_KEY and ESP_BLE_SM_SET_RSP_KEY flags are correctly set on both devices to distribute/accept needed keys (e.g., ESP_BLE_ENC_KEY_MASK, ESP_BLE_ID_KEY_MASK). |
Bonding Data Not Persisting / Cleared | Devices re-pair every time. No reuse of stored keys. | Ensure NVS is initialized. Set bonding flags (e.g., ESP_LE_AUTH_BOND in auth_req) on both devices. Avoid unintentional NVS erasure. |
LE Secure Connections Fallback or Failure | Pairing fails if one device is LESC-only and peer doesn’t support/isn’t configured for LESC. Or, I/O caps don’t support LESC authenticated method when MITM is required. | Ensure both devices support and are configured for LESC if desired (compatible auth_req flags). For Numeric Comparison, both need ESP_IO_CAP_IO or ESP_IO_CAP_KBDISP. |
Attribute Permissions Not Enforced | Sensitive data accessed without proper security level, or writes succeed when they should fail. | Verify GATT attribute permissions (e.g., ESP_GATT_PERM_READ_ENCRYPTED) are set correctly. Ensure link is encrypted/authenticated before accessing such attributes. The stack usually handles this, but client might need to initiate esp_ble_set_encryption(). |
Exercises
- Implement and Test Bonding Deletion:
- Using the “Just Works” pairing example (Example 1), after successfully bonding two ESP32s, add functionality to one of them (e.g., triggered by a button press or a serial command) to remove the bond information for the other device using
esp_ble_remove_bond_device()
. - Verify that after removing the bond, the devices go through the full pairing process again upon the next connection attempt.
- Also, try clearing all bond information by erasing the NVS
ble_sec
namespace or the entire NVS.
- Using the “Just Works” pairing example (Example 1), after successfully bonding two ESP32s, add functionality to one of them (e.g., triggered by a button press or a serial command) to remove the bond information for the other device using
- Secure a GATT Characteristic:
- Take the GATT server example from Chapter 57. Modify one of its characteristics (e.g., a writable characteristic) to require authenticated encryption for write access (
ESP_GATT_PERM_WRITE_ENC_MITM
). - Implement a GATT client that attempts to write to this characteristic.
- Observe that the write fails if the link is not already secured with at least MITM protection.
- Ensure pairing (e.g., Passkey or Numeric Comparison) completes successfully, then try writing to the characteristic again. It should now succeed.
- Take the GATT server example from Chapter 57. Modify one of its characteristics (e.g., a writable characteristic) to require authenticated encryption for write access (
- Role Reversal for Passkey Entry:
- Modify Example 2 (Passkey Entry). Make Device A (Server) the one that requires keyboard input (
ESP_IO_CAP_IN
) and Device B (Client) the one that displays the passkey (ESP_IO_CAP_OUT
). - Adjust the event handling (
ESP_GAP_BLE_PASSKEY_REQ_EVT
on Server,ESP_GAP_BLE_PASSKEY_NOTIF_EVT
on Client) accordingly. - Test the pairing process.
- Modify Example 2 (Passkey Entry). Make Device A (Server) the one that requires keyboard input (
Summary
- BLE security relies on the Security Manager Protocol (SMP) for pairing, key distribution, and authentication.
- Pairing establishes trust and generates cryptographic keys (STK for legacy, LTK for LESC). It involves feature exchange, key generation, and optional key distribution.
- I/O capabilities (
DisplayOnly
,KeyboardOnly
,NoInputNoOutput
, etc.) determine the available pairing methods. - Pairing Methods: Just Works, Passkey Entry, Numeric Comparison (LESC only), and Out-of-Band (OOB).
- LE Secure Connections (LESC) uses ECDH key exchange for significantly stronger security and MITM protection compared to legacy pairing.
- Bonding stores generated keys (LTK, IRK, CSRK) in NVS, allowing devices to reconnect securely without re-pairing.
- Security Modes & Levels define the security guarantees of a link, with Level 4 (Authenticated LESC pairing with encryption) being the strongest.
- ESP-IDF provides
esp_ble_gap_set_security_param()
to configure security settings and delivers security-related events through the GAP callback. - Attribute permissions in GATT (e.g.,
ESP_GATT_PERM_WRITE_ENCRYPTED
) enforce security requirements for data access.
Further Reading
- ESP-IDF Security Examples:
- ESP-IDF API Reference –
esp_gap_ble_api.h
: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/bluetooth/esp_gap_ble.html - Bluetooth Core Specification: (Available from bluetooth.com) Volume 3, Part H (Security Manager) and Volume 6, Part B (Link Layer) for in-depth details: https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host/security-manager-specification.html
- Bluetooth SIG – “Bluetooth Secure Connections” Whitepaper.
