feat(ble/bluedroid): move Encrypted Advertising Data APIs into the host

Provide esp_ble_ead_encrypt/decrypt in Bluedroid and group the GAP Key
Material characteristic under the same EAD Kconfig menu.
This commit is contained in:
zhiweijian
2026-09-08 16:07:44 +08:00
parent b10ae7f167
commit f5e9e2496a
29 changed files with 423 additions and 841 deletions
@@ -5,6 +5,8 @@
This example demonstrates how to receive and decrypt BLE Encrypted Advertising Data (EAD) with Bluedroid stack.
Decryption uses the host APIs in `esp_ble_ead.h` (`esp_ble_ead_decrypt`). Enable `CONFIG_BT_BLE_FEAT_ENC_ADV_DATA` (already set in `sdkconfig.defaults`).
## Overview
This central example works with the `enc_adv_data_prph` peripheral example to demonstrate:
@@ -34,7 +36,7 @@ This central example works with the `enc_adv_data_prph` peripheral example to de
│ ▼ │ │ key to decrypt │
│ Store key │ │ │ │
│ │ │ │ ▼ │
│ ▼ │ │ ✅ Decrypt immediately │
│ ▼ │ │ Decrypt immediately │
│ Later scans: │ │ │
│ ┌─────────┐ ┌─────────┐ │ │
│ │ Central │──▶│ Periph │ │ │
@@ -42,12 +44,12 @@ This central example works with the `enc_adv_data_prph` peripheral example to de
│ │ │ │
│ │ No connection needed │ │
│ ▼ │ │
│ ✅ Decrypt using stored key │ │
│ Decrypt using stored key │ │
│ │ │
├────────────────────────────────┼────────────────────────────────────────────┤
│ ✓ Secure key exchange │ ✓ No connection latency │
│ ✓ Dynamic key support │ ✓ Simpler implementation │
│ ✗ First-time connection needed │ ✗ Key must be pre-provisioned │
│ + Secure key exchange │ + No connection latency │
│ + Dynamic key support │ + Simpler implementation │
│ - First-time connection needed │ - Key must be pre-provisioned │
└────────────────────────────────┴────────────────────────────────────────────┘
```
@@ -69,7 +71,7 @@ This central example works with the `enc_adv_data_prph` peripheral example to de
│ │ 1. Scan │ │
│ │ ──────────────────────────────────────────────────▶ │ │
│ │ │ │
│ │ 2. Receive Adv (UUID=0x2C01, Encrypted Data) │ │
│ │ 2. Receive Adv (UUID=0x1800, Encrypted Data) │ │
│ │ ◀────────────────────────────────────────────────── │ │
│ │ │ │
│ │ [No key yet - cannot decrypt] │ │
@@ -100,7 +102,7 @@ This central example works with the `enc_adv_data_prph` peripheral example to de
│ │ │ │
│ │ 11. Decrypt using stored key (NO CONNECTION!) │ │
│ │ ┌────────────────────────────────────────┐ │ │
│ │ │ ble_ead_decrypt(session_key, iv, ...) │ │ │
│ │ │ esp_ble_ead_decrypt(session_key, iv, ...) │ │ │
│ │ │ Result: "prph" (decrypted name) │ │ │
│ │ └────────────────────────────────────────┘ │ │
│ │ │ │
@@ -136,13 +138,13 @@ This central example works with the `enc_adv_data_prph` peripheral example to de
│ │ │ │
│ │ 3. Immediately decrypt (NO CONNECTION!) │ │
│ │ ┌────────────────────────────────────────┐ │ │
│ │ │ ble_ead_decrypt(pre_shared_key, ...) │ │ │
│ │ │ esp_ble_ead_decrypt(pre_shared_key, ...) │ │ │
│ │ │ Result: "prph" (decrypted name) │ │ │
│ │ └────────────────────────────────────────┘ │ │
│ │ │ │
│ ▼ ▼ │
│ │
│ ⚡ No connection overhead - instant decryption! │
│ No connection overhead - instant decryption! │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
@@ -253,11 +255,11 @@ I (XXX) ENC_ADV_CENT: Decrypted device name: prph
I (XXX) ENC_ADV_CENT_SIMPLE: ========================================
I (XXX) ENC_ADV_CENT_SIMPLE: EAD Central - No Connection Mode
I (XXX) ENC_ADV_CENT_SIMPLE: ========================================
I (XXX) ENC_ADV_CENT_SIMPLE: ⚡ This example decrypts WITHOUT connecting!
I (XXX) ENC_ADV_CENT_SIMPLE: 🔍 Scanning started (no connection mode)
I (XXX) ENC_ADV_CENT_SIMPLE: This example decrypts WITHOUT connecting!
I (XXX) ENC_ADV_CENT_SIMPLE: Scanning started (no connection mode)
...
I (XXX) ENC_ADV_CENT_SIMPLE: ✅ Decryption successful (no connection needed!)
I (XXX) ENC_ADV_CENT_SIMPLE: 📛 Decrypted device name: "prph"
I (XXX) ENC_ADV_CENT_SIMPLE: Decryption successful (no connection needed!)
I (XXX) ENC_ADV_CENT_SIMPLE: Decrypted device name: "prph"
```
## Troubleshooting
@@ -4,5 +4,5 @@ else()
set(MAIN_SRC "enc_adv_data_cent.c")
endif()
idf_component_register(SRCS ${MAIN_SRC} "ble_ead.c"
idf_component_register(SRCS ${MAIN_SRC}
INCLUDE_DIRS ".")
@@ -1,446 +0,0 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "ble_ead.h"
#include "esp_random.h"
#include "esp_log.h"
#include "sdkconfig.h"
#define TAG "BLE_EAD"
/* Select crypto library based on configuration */
#if defined(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT)
#include "tinycrypt/aes.h"
#include "tinycrypt/ccm_mode.h"
#include "tinycrypt/constants.h"
#elif defined(CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS)
#include "psa/crypto.h"
#else
#error "Please select either CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT or CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS"
#endif
/* Additional Authenticated Data for EAD - EA (Encrypted Advertising) */
static const uint8_t ble_ead_aad[BLE_EAD_AAD_SIZE] = { 0xEA };
/**
* @brief Generate randomizer with direction bit set
*
* Per Bluetooth Core Spec Supplement v11, Part A 1.23.3:
* The MSB of the Randomizer shall be set to indicate direction
*/
static int ble_ead_generate_randomizer(uint8_t randomizer[BLE_EAD_RANDOMIZER_SIZE])
{
/* Generate random bytes */
esp_fill_random(randomizer, BLE_EAD_RANDOMIZER_SIZE);
/* Set direction bit (MSB of last byte) - required by spec */
randomizer[BLE_EAD_RANDOMIZER_SIZE - 1] |= (1 << BLE_EAD_RANDOMIZER_DIRECTION_BIT);
return 0;
}
/**
* @brief Generate nonce from IV and randomizer
*
* Nonce = Randomizer (5 bytes) || IV (8 bytes) = 13 bytes
*/
static int ble_ead_generate_nonce(const uint8_t iv[BLE_EAD_IV_SIZE],
const uint8_t randomizer[BLE_EAD_RANDOMIZER_SIZE],
uint8_t nonce[BLE_EAD_NONCE_SIZE])
{
if (iv == NULL || nonce == NULL) {
return -1;
}
/* Randomizer in first 5 bytes */
if (randomizer != NULL) {
memcpy(nonce, randomizer, BLE_EAD_RANDOMIZER_SIZE);
} else {
/* Generate new randomizer with direction bit */
ble_ead_generate_randomizer(nonce);
}
/* IV in last 8 bytes */
memcpy(nonce + BLE_EAD_RANDOMIZER_SIZE, iv, BLE_EAD_IV_SIZE);
return 0;
}
/**
* @brief AES-CCM encryption using selected crypto library
*/
static int ble_aes_ccm_encrypt(const uint8_t *key, const uint8_t *nonce,
const uint8_t *plaintext, size_t plaintext_len,
const uint8_t *aad, size_t aad_len,
uint8_t *ciphertext, size_t tag_len)
{
#if defined(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT)
struct tc_aes_key_sched_struct sched;
struct tc_ccm_mode_struct ccm_state;
int ret;
/* Validate inputs */
if (key == NULL || nonce == NULL || ciphertext == NULL) {
ESP_LOGE(TAG, "Invalid input parameters");
return -1;
}
/* Set AES encryption key */
ret = tc_aes128_set_encrypt_key(&sched, key);
if (ret != TC_CRYPTO_SUCCESS) {
ESP_LOGE(TAG, "tc_aes128_set_encrypt_key failed");
memset(&sched, 0, sizeof(sched));
return -1;
}
/* Configure CCM mode */
ccm_state.sched = &sched;
ccm_state.nonce = (uint8_t *)nonce;
ccm_state.mlen = tag_len;
ret = tc_ccm_config(&ccm_state, &sched, (uint8_t *)nonce, BLE_EAD_NONCE_SIZE, tag_len);
if (ret != TC_CRYPTO_SUCCESS) {
ESP_LOGE(TAG, "tc_ccm_config failed");
memset(&sched, 0, sizeof(sched));
memset(&ccm_state, 0, sizeof(ccm_state));
return -1;
}
/* Encrypt and generate tag */
/* TinyCrypt outputs: ciphertext || tag */
ret = tc_ccm_generation_encryption(ciphertext, plaintext_len + tag_len,
aad, aad_len,
plaintext, plaintext_len,
&ccm_state);
if (ret != TC_CRYPTO_SUCCESS) {
ESP_LOGE(TAG, "tc_ccm_generation_encryption failed");
memset(&sched, 0, sizeof(sched));
memset(&ccm_state, 0, sizeof(ccm_state));
return -1;
}
/* Clear sensitive data from key schedule */
memset(&sched, 0, sizeof(sched));
memset(&ccm_state, 0, sizeof(ccm_state));
return 0;
#elif defined(CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS)
psa_status_t status;
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
psa_key_id_t key_id = 0;
psa_algorithm_t alg = PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, tag_len);
size_t output_length = 0;
/* Validate inputs */
if (key == NULL || nonce == NULL || ciphertext == NULL) {
ESP_LOGE(TAG, "Invalid input parameters");
return -1;
}
/* Set key attributes */
psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_ENCRYPT);
psa_set_key_algorithm(&attributes, alg);
psa_set_key_type(&attributes, PSA_KEY_TYPE_AES);
psa_set_key_bits(&attributes, BLE_EAD_KEY_SIZE * 8);
/* Import key */
status = psa_import_key(&attributes, key, BLE_EAD_KEY_SIZE, &key_id);
if (status != PSA_SUCCESS) {
ESP_LOGE(TAG, "psa_import_key failed: %d", status);
psa_reset_key_attributes(&attributes);
return -1;
}
psa_reset_key_attributes(&attributes);
/* Encrypt and authenticate */
/* PSA AEAD encrypt outputs: ciphertext || tag */
status = psa_aead_encrypt(key_id, alg,
nonce, BLE_EAD_NONCE_SIZE,
aad, aad_len,
plaintext, plaintext_len,
ciphertext, plaintext_len + tag_len,
&output_length);
if (status != PSA_SUCCESS) {
ESP_LOGE(TAG, "psa_aead_encrypt failed: %d", status);
psa_destroy_key(key_id);
return -1;
}
if (output_length != plaintext_len + tag_len) {
ESP_LOGE(TAG, "psa_aead_encrypt output length mismatch: expected %zu, got %zu",
plaintext_len + tag_len, output_length);
psa_destroy_key(key_id);
return -1;
}
psa_destroy_key(key_id);
return 0;
#else
#error "No crypto library selected"
#endif
}
/**
* @brief AES-CCM decryption with authentication using selected crypto library
*/
static int ble_aes_ccm_decrypt(const uint8_t *key, const uint8_t *nonce,
const uint8_t *ciphertext, size_t ciphertext_len,
const uint8_t *aad, size_t aad_len,
uint8_t *plaintext, size_t tag_len,
size_t plaintext_capacity)
{
#if defined(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT)
struct tc_aes_key_sched_struct sched;
struct tc_ccm_mode_struct ccm_state;
int ret;
/* ciphertext_len here includes both ciphertext and tag */
size_t plaintext_len;
/* Validate inputs */
if (key == NULL || nonce == NULL || ciphertext == NULL || plaintext == NULL) {
ESP_LOGE(TAG, "Invalid input parameters");
return -1;
}
/* Check for integer underflow */
if (ciphertext_len < tag_len) {
ESP_LOGE(TAG, "ciphertext_len (%zu) < tag_len (%zu)", ciphertext_len, tag_len);
return -1;
}
plaintext_len = ciphertext_len - tag_len;
if (plaintext_len > plaintext_capacity) {
ESP_LOGE(TAG, "plaintext_len (%zu) > plaintext_capacity (%zu)", plaintext_len, plaintext_capacity);
return -1;
}
/* Set AES encryption key */
ret = tc_aes128_set_encrypt_key(&sched, key);
if (ret != TC_CRYPTO_SUCCESS) {
ESP_LOGE(TAG, "tc_aes128_set_encrypt_key failed");
memset(&sched, 0, sizeof(sched));
return -1;
}
/* Configure CCM mode */
ccm_state.sched = &sched;
ccm_state.nonce = (uint8_t *)nonce;
ccm_state.mlen = tag_len;
ret = tc_ccm_config(&ccm_state, &sched, (uint8_t *)nonce, BLE_EAD_NONCE_SIZE, tag_len);
if (ret != TC_CRYPTO_SUCCESS) {
ESP_LOGE(TAG, "tc_ccm_config failed");
memset(&sched, 0, sizeof(sched));
memset(&ccm_state, 0, sizeof(ccm_state));
return -1;
}
/* Decrypt and verify tag */
/* TinyCrypt expects: ciphertext || tag */
ret = tc_ccm_decryption_verification(plaintext, plaintext_len,
aad, aad_len,
(uint8_t *)ciphertext, ciphertext_len,
&ccm_state);
if (ret != TC_CRYPTO_SUCCESS) {
ESP_LOGE(TAG, "tc_ccm_decryption_verification failed");
memset(&sched, 0, sizeof(sched));
memset(&ccm_state, 0, sizeof(ccm_state));
return -1;
}
/* Clear sensitive data from key schedule */
memset(&sched, 0, sizeof(sched));
memset(&ccm_state, 0, sizeof(ccm_state));
return 0;
#elif defined(CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS)
psa_status_t status;
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
psa_key_id_t key_id = 0;
psa_algorithm_t alg = PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, tag_len);
size_t output_length = 0;
/* ciphertext_len here includes both ciphertext and tag */
size_t plaintext_len;
/* Validate inputs */
if (key == NULL || nonce == NULL || ciphertext == NULL || plaintext == NULL) {
ESP_LOGE(TAG, "Invalid input parameters");
return -1;
}
/* Check for integer underflow */
if (ciphertext_len < tag_len) {
ESP_LOGE(TAG, "ciphertext_len (%zu) < tag_len (%zu)", ciphertext_len, tag_len);
return -1;
}
plaintext_len = ciphertext_len - tag_len;
if (plaintext_len > plaintext_capacity) {
ESP_LOGE(TAG, "plaintext_len (%zu) > plaintext_capacity (%zu)", plaintext_len, plaintext_capacity);
return -1;
}
/* Set key attributes */
psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT);
psa_set_key_algorithm(&attributes, alg);
psa_set_key_type(&attributes, PSA_KEY_TYPE_AES);
psa_set_key_bits(&attributes, BLE_EAD_KEY_SIZE * 8);
/* Import key */
status = psa_import_key(&attributes, key, BLE_EAD_KEY_SIZE, &key_id);
if (status != PSA_SUCCESS) {
ESP_LOGE(TAG, "psa_import_key failed: %d", status);
psa_reset_key_attributes(&attributes);
return -1;
}
psa_reset_key_attributes(&attributes);
/* Decrypt and verify */
/* PSA AEAD decrypt expects: ciphertext || tag */
/* ciphertext_len here already includes tag length */
status = psa_aead_decrypt(key_id, alg,
nonce, BLE_EAD_NONCE_SIZE,
aad, aad_len,
ciphertext, ciphertext_len,
plaintext, plaintext_len,
&output_length);
if (status != PSA_SUCCESS) {
ESP_LOGE(TAG, "psa_aead_decrypt failed: %d", status);
psa_destroy_key(key_id);
return -1;
}
if (output_length != plaintext_len) {
ESP_LOGE(TAG, "psa_aead_decrypt output length mismatch: expected %zu, got %zu",
plaintext_len, output_length);
psa_destroy_key(key_id);
return -1;
}
psa_destroy_key(key_id);
return 0;
#else
#error "No crypto library selected"
#endif
}
int ble_ead_encrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
const uint8_t iv[BLE_EAD_IV_SIZE],
const uint8_t *payload, size_t payload_size,
uint8_t *encrypted_payload)
{
int ret;
uint8_t nonce[BLE_EAD_NONCE_SIZE];
if (session_key == NULL) {
ESP_LOGE(TAG, "session_key is NULL");
return -1;
}
if (iv == NULL) {
ESP_LOGE(TAG, "iv is NULL");
return -1;
}
if (payload == NULL && payload_size > 0) {
ESP_LOGE(TAG, "payload is NULL but payload_size > 0");
return -1;
}
if (encrypted_payload == NULL) {
ESP_LOGE(TAG, "encrypted_payload is NULL");
return -1;
}
/* Generate nonce with random randomizer */
ret = ble_ead_generate_nonce(iv, NULL, nonce);
if (ret != 0) {
return ret;
}
/* Copy randomizer to the start of encrypted payload */
memcpy(encrypted_payload, nonce, BLE_EAD_RANDOMIZER_SIZE);
/* Encrypt: output = ciphertext + MIC */
ret = ble_aes_ccm_encrypt(session_key, nonce,
payload, payload_size,
ble_ead_aad, BLE_EAD_AAD_SIZE,
&encrypted_payload[BLE_EAD_RANDOMIZER_SIZE],
BLE_EAD_MIC_SIZE);
return ret;
}
int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
const uint8_t iv[BLE_EAD_IV_SIZE],
const uint8_t *encrypted_payload, size_t encrypted_payload_size,
uint8_t *payload, size_t payload_capacity)
{
int ret;
uint8_t nonce[BLE_EAD_NONCE_SIZE];
const uint8_t *randomizer;
const uint8_t *ciphertext;
size_t ciphertext_len;
size_t expected_plaintext_len;
if (session_key == NULL) {
ESP_LOGE(TAG, "session_key is NULL");
return -1;
}
if (iv == NULL) {
ESP_LOGE(TAG, "iv is NULL");
return -1;
}
if (encrypted_payload == NULL) {
ESP_LOGE(TAG, "encrypted_payload is NULL");
return -1;
}
if (payload == NULL) {
ESP_LOGE(TAG, "payload is NULL");
return -1;
}
if (encrypted_payload_size < BLE_EAD_RANDOMIZER_SIZE + BLE_EAD_MIC_SIZE) {
ESP_LOGE(TAG, "encrypted_payload_size too small");
return -1;
}
expected_plaintext_len = BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size);
if (expected_plaintext_len > payload_capacity) {
ESP_LOGE(TAG, "EAD plaintext length %zu exceeds payload buffer %zu",
expected_plaintext_len, payload_capacity);
return -1;
}
/* Extract randomizer from the start of encrypted payload */
randomizer = encrypted_payload;
/* Ciphertext + MIC follows the randomizer */
ciphertext = &encrypted_payload[BLE_EAD_RANDOMIZER_SIZE];
/* ciphertext_len includes both ciphertext and MIC (tag) for PSA API */
ciphertext_len = encrypted_payload_size - BLE_EAD_RANDOMIZER_SIZE;
/* Generate nonce from randomizer and IV */
ret = ble_ead_generate_nonce(iv, randomizer, nonce);
if (ret != 0) {
return ret;
}
/* Decrypt and verify */
ret = ble_aes_ccm_decrypt(session_key, nonce,
ciphertext, ciphertext_len,
ble_ead_aad, BLE_EAD_AAD_SIZE,
payload, BLE_EAD_MIC_SIZE,
payload_capacity);
return ret;
}
@@ -1,96 +0,0 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef BLE_EAD_H
#define BLE_EAD_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief BLE Encrypted Advertising Data (EAD) definitions
* Based on Bluetooth Core Specification Version 5.4
*/
#define BLE_EAD_KEY_SIZE 16 /* 128-bit session key */
#define BLE_EAD_IV_SIZE 8 /* 64-bit Initialization Vector */
#define BLE_EAD_RANDOMIZER_SIZE 5 /* 40-bit Randomizer */
#define BLE_EAD_MIC_SIZE 4 /* 32-bit Message Integrity Check */
#define BLE_EAD_NONCE_SIZE 13 /* 104-bit Nonce (Randomizer + IV) */
#define BLE_EAD_AAD_SIZE 1 /* Additional Authenticated Data size */
/* Direction bit position in Randomizer (MSB of last byte)
* Per Bluetooth Core Spec Supplement v11, Part A 1.23.3
*/
#define BLE_EAD_RANDOMIZER_DIRECTION_BIT 7
/* AD Type for Encrypted Advertising Data (0x31) */
#define ESP_BLE_AD_TYPE_ENC_ADV_DATA 0x31
/**
* @brief Calculate encrypted payload size from plaintext size
*/
#define BLE_EAD_ENCRYPTED_PAYLOAD_SIZE(payload_size) \
(BLE_EAD_RANDOMIZER_SIZE + (payload_size) + BLE_EAD_MIC_SIZE)
/**
* @brief Calculate decrypted payload size from encrypted payload size
*/
#define BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_size) \
((encrypted_size) - BLE_EAD_RANDOMIZER_SIZE - BLE_EAD_MIC_SIZE)
/**
* @brief Key material structure for EAD
*/
typedef struct {
uint8_t session_key[BLE_EAD_KEY_SIZE]; /* 128-bit session key */
uint8_t iv[BLE_EAD_IV_SIZE]; /* 64-bit Initialization Vector */
} ble_ead_key_material_t;
/**
* @brief Encrypt advertising data using AES-CCM
*
* @param session_key 16-byte session key
* @param iv 8-byte Initialization Vector
* @param payload Plaintext advertising data to encrypt
* @param payload_size Size of plaintext data
* @param encrypted_payload Output buffer for encrypted data
* Size must be at least BLE_EAD_ENCRYPTED_PAYLOAD_SIZE(payload_size)
*
* @return 0 on success, negative error code on failure
*/
int ble_ead_encrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
const uint8_t iv[BLE_EAD_IV_SIZE],
const uint8_t *payload, size_t payload_size,
uint8_t *encrypted_payload);
/**
* @brief Decrypt advertising data using AES-CCM
*
* @param session_key 16-byte session key
* @param iv 8-byte Initialization Vector
* @param encrypted_payload Encrypted advertising data (includes randomizer and MIC)
* @param encrypted_payload_size Size of encrypted data
* @param payload Output buffer for decrypted data
* @param payload_capacity Size of @a payload in bytes; must be >=
* BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size)
*
* @return 0 on success, negative error code on failure
*/
int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
const uint8_t iv[BLE_EAD_IV_SIZE],
const uint8_t *encrypted_payload, size_t encrypted_payload_size,
uint8_t *payload, size_t payload_capacity);
#ifdef __cplusplus
}
#endif
#endif /* BLE_EAD_H */
@@ -30,7 +30,7 @@
#include "esp_gatt_common_api.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "ble_ead.h"
#include "esp_ble_ead.h"
#define TAG "ENC_ADV_CENT"
@@ -51,7 +51,7 @@ typedef struct {
bool valid;
esp_bd_addr_t addr;
bool key_material_exist;
ble_ead_key_material_t key_material;
esp_ble_ead_key_material_t key_material;
} peer_info_t;
static peer_info_t peers[MAX_PEERS] = {0};
@@ -142,26 +142,26 @@ static void decrypt_enc_adv_data(const uint8_t *adv_data, uint8_t adv_len, const
const uint8_t *enc_data = &adv_data[offset + 2];
uint8_t enc_data_len = len - 1; /* Exclude type byte */
if (enc_data_len < BLE_EAD_RANDOMIZER_SIZE + BLE_EAD_MIC_SIZE) {
if (enc_data_len < ESP_BLE_EAD_RANDOMIZER_SIZE + ESP_BLE_EAD_MIC_SIZE) {
ESP_LOGW(TAG, "Encrypted data too short");
break;
}
uint8_t dec_data[32]; /* Buffer for decrypted data */
size_t dec_len = BLE_EAD_DECRYPTED_PAYLOAD_SIZE(enc_data_len);
size_t dec_len = ESP_BLE_EAD_DECRYPTED_PAYLOAD_SIZE(enc_data_len);
if (dec_len > sizeof(dec_data)) {
ESP_LOGW(TAG, "Encrypted AD would yield %zu plaintext bytes; example buffer is %zu — skip",
dec_len, sizeof(dec_data));
break;
}
int rc = ble_ead_decrypt(
esp_err_t rc = esp_ble_ead_decrypt(
peers[peer_idx].key_material.session_key,
peers[peer_idx].key_material.iv,
enc_data, enc_data_len,
dec_data, sizeof(dec_data));
if (rc == 0) {
if (rc == ESP_OK) {
size_t safe_dec_len = dec_len;
if (safe_dec_len > sizeof(dec_data)) {
ESP_LOGW(TAG, "dec_len %zu > buffer %zu, clamping for log/parse",
@@ -448,17 +448,17 @@ static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
param->read.handle, param->read.value_len);
if (param->read.handle == key_material_char_handle &&
param->read.value_len == sizeof(ble_ead_key_material_t)) {
param->read.value_len == sizeof(esp_ble_ead_key_material_t)) {
/* Store key material */
int peer_idx = find_peer(gattc_remote_bda);
if (peer_idx >= 0) {
memcpy(&peers[peer_idx].key_material, param->read.value,
sizeof(ble_ead_key_material_t));
sizeof(esp_ble_ead_key_material_t));
peers[peer_idx].key_material_exist = true;
ESP_LOGI(TAG, "Key material received:");
ESP_LOG_BUFFER_HEX(TAG, &peers[peer_idx].key_material,
sizeof(ble_ead_key_material_t));
sizeof(esp_ble_ead_key_material_t));
}
/* Disconnect and resume scanning */
@@ -24,18 +24,18 @@
#include "esp_bt_main.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "ble_ead.h"
#include "esp_ble_ead.h"
#define TAG "ENC_ADV_CENT_SIMPLE"
/* Custom service UUID to identify target device */
#define CUSTOM_SERVICE_UUID 0x2C01
/* GAP Service UUID advertised by enc_adv_data_prph (Key Material lives in GAP) */
#define GAP_SERVICE_UUID 0x1800
/*
* Pre-shared Key Material - MUST match the Peripheral!
* In real applications, this would be provisioned securely.
*/
static const ble_ead_key_material_t pre_shared_key = {
static const esp_ble_ead_key_material_t pre_shared_key = {
.session_key = {
0x19, 0x6a, 0x0a, 0xd1, 0x2a, 0x61, 0x20, 0x1e,
0x13, 0x6e, 0x2e, 0xd1, 0x12, 0xda, 0xa9, 0x57
@@ -72,7 +72,7 @@ static bool is_target_device(const uint8_t *adv_data, uint8_t adv_len)
if (payload_len >= 2) {
for (int i = 0; i + 1 < payload_len; i += 2) {
uint16_t uuid = adv_data[offset + 2 + i] | (adv_data[offset + 3 + i] << 8);
if (uuid == CUSTOM_SERVICE_UUID) {
if (uuid == GAP_SERVICE_UUID) {
return true;
}
}
@@ -112,34 +112,34 @@ static void decrypt_adv_data_no_connect(const uint8_t *adv_data, uint8_t adv_len
ESP_LOGI(TAG, "Found encrypted advertising data (%d bytes)", enc_data_len);
ESP_LOG_BUFFER_HEX(TAG, enc_data, enc_data_len);
if (enc_data_len < BLE_EAD_RANDOMIZER_SIZE + BLE_EAD_MIC_SIZE) {
if (enc_data_len < ESP_BLE_EAD_RANDOMIZER_SIZE + ESP_BLE_EAD_MIC_SIZE) {
ESP_LOGW(TAG, "Encrypted data too short");
break;
}
/* Decrypt using pre-shared key */
uint8_t dec_data[32];
size_t dec_len = BLE_EAD_DECRYPTED_PAYLOAD_SIZE(enc_data_len);
size_t dec_len = ESP_BLE_EAD_DECRYPTED_PAYLOAD_SIZE(enc_data_len);
if (dec_len > sizeof(dec_data)) {
ESP_LOGW(TAG, "Encrypted AD would yield %zu plaintext bytes; example buffer is %zu — skip",
dec_len, sizeof(dec_data));
return;
}
int rc = ble_ead_decrypt(
esp_err_t rc = esp_ble_ead_decrypt(
pre_shared_key.session_key,
pre_shared_key.iv,
enc_data, enc_data_len,
dec_data, sizeof(dec_data));
if (rc == 0) {
if (rc == ESP_OK) {
size_t safe_dec_len = dec_len;
if (safe_dec_len > sizeof(dec_data)) {
ESP_LOGW(TAG, "dec_len %zu > buffer %zu, clamping for log/parse",
dec_len, sizeof(dec_data));
safe_dec_len = sizeof(dec_data);
}
ESP_LOGI(TAG, "✅ Decryption successful (no connection needed!)");
ESP_LOGI(TAG, "Decryption successful (no connection needed!)");
ESP_LOGI(TAG, "Decrypted data (%zu bytes):", safe_dec_len);
ESP_LOG_BUFFER_HEX(TAG, dec_data, safe_dec_len);
@@ -164,14 +164,14 @@ static void decrypt_adv_data_no_connect(const uint8_t *adv_data, uint8_t adv_len
name_copy_end <= sizeof(dec_data) &&
name_copy_end <= safe_dec_len) {
memcpy(name, &dec_data[2], name_len);
ESP_LOGI(TAG, "📛 Decrypted device name: \"%s\"", name);
ESP_LOGI(TAG, "Decrypted device name: \"%s\"", name);
}
}
}
}
}
} else {
ESP_LOGE(TAG, "❌ Decryption failed (rc=%d) - wrong key?", rc);
ESP_LOGE(TAG, "Decryption failed (rc=%d) - wrong key?", rc);
}
return; /* Found and processed encrypted data */
}
@@ -194,8 +194,8 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
if (param->scan_start_cmpl.status == ESP_BT_STATUS_SUCCESS) {
ESP_LOGI(TAG, "🔍 Scanning started (no connection mode)");
ESP_LOGI(TAG, "Looking for devices with UUID 0x%04X...", CUSTOM_SERVICE_UUID);
ESP_LOGI(TAG, "Scanning started (no connection mode)");
ESP_LOGI(TAG, "Looking for devices with UUID 0x%04X...", GAP_SERVICE_UUID);
} else {
ESP_LOGE(TAG, "Scan start failed: %d", param->scan_start_cmpl.status);
}
@@ -253,14 +253,14 @@ void app_main(void)
/* Display pre-shared key */
ESP_LOGI(TAG, "Using pre-shared key material:");
ESP_LOGI(TAG, " Session Key:");
ESP_LOG_BUFFER_HEX(TAG, pre_shared_key.session_key, BLE_EAD_KEY_SIZE);
ESP_LOG_BUFFER_HEX(TAG, pre_shared_key.session_key, ESP_BLE_EAD_KEY_SIZE);
ESP_LOGI(TAG, " IV:");
ESP_LOG_BUFFER_HEX(TAG, pre_shared_key.iv, BLE_EAD_IV_SIZE);
ESP_LOG_BUFFER_HEX(TAG, pre_shared_key.iv, ESP_BLE_EAD_IV_SIZE);
/* Start scanning */
ESP_ERROR_CHECK(esp_ble_gap_set_scan_params(&ble_scan_params));
ESP_LOGI(TAG, "");
ESP_LOGI(TAG, "⚡ This example decrypts WITHOUT connecting!");
ESP_LOGI(TAG, " Key must be pre-shared with peripheral.");
ESP_LOGI(TAG, "This example decrypts WITHOUT connecting!");
ESP_LOGI(TAG, "Key must be pre-shared with peripheral.");
}
@@ -8,6 +8,9 @@ CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
# Enable SMP for security
CONFIG_BT_BLE_SMP_ENABLE=y
# Encrypted Advertising Data APIs in Bluedroid host
CONFIG_BT_BLE_FEAT_ENC_ADV_DATA=y
# Select crypto library for EAD (Encrypted Advertising Data)
# Options: CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT or CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS
CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT=y
@@ -5,6 +5,8 @@
This example demonstrates how to use BLE Encrypted Advertising Data (EAD) feature with Bluedroid stack.
Encryption uses the host APIs in `esp_ble_ead.h` (`esp_ble_ead_encrypt`). Enable `CONFIG_BT_BLE_FEAT_ENC_ADV_DATA` (already set in `sdkconfig.defaults`).
## Overview
The Encrypted Advertising Data feature (introduced in Bluetooth Core Specification 5.4) allows devices to encrypt portions of their advertising data using AES-CCM. This enables:
@@ -30,7 +32,7 @@ The Encrypted Advertising Data feature (introduced in Bluetooth Core Specificati
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ BLE Advertising Packet │ │
│ ├──────────┬─────────────┬────────────────┬────────────────────────────┤ │
│ │ Flags │ Name "key" │ UUID 0x2C01 │ Encrypted Data (AD 0x31) │ │
│ │ Flags │ Name "key" │ UUID 0x1800 │ Encrypted Data (AD 0x31) │ │
│ │ (3B) │ (5B) │ (4B) │ (16B) │ │
│ └──────────┴─────────────┴────────────────┴────────────────────────────┘ │
│ │
@@ -89,7 +91,7 @@ Offset Length Type Data Description
────── ────── ──── ──── ───────────
0 2 0x01 0x06 Flags: LE General Discoverable
3 4 0x09 'k' 'e' 'y' Complete Local Name
8 3 0x03 0x01 0x2C 16-bit Service UUID: 0x2C01
8 3 0x03 0x00 0x18 16-bit Service UUID: 0x1800
12 16 0x31 [Encrypted Payload] Encrypted Advertising Data
Encrypted Payload Detail:
@@ -1,2 +1,2 @@
idf_component_register(SRCS "enc_adv_data_prph.c" "ble_ead.c"
idf_component_register(SRCS "enc_adv_data_prph.c"
INCLUDE_DIRS ".")
@@ -2,11 +2,13 @@ menu "Example Configuration"
config EXAMPLE_ENABLE_KEY_MATERIAL
bool "Enable Key Material characteristic in GAP Service"
depends on BT_GATTS_ENABLE
default y
select BT_GATTS_KEY_MATERIAL_CHAR
help
Enable the Key Material characteristic in the built-in GAP service
(UUID 0x1800) using the Bluedroid stack's support for this feature.
(UUID 0x2B88). This also enables Encrypted Advertising Data APIs
(CONFIG_BT_BLE_FEAT_ENC_ADV_DATA).
This is the standard-compliant approach as defined in Bluetooth
Core Specification Version 5.4.
@@ -1,439 +0,0 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "ble_ead.h"
#include "esp_random.h"
#include "esp_log.h"
#include "sdkconfig.h"
#define TAG "BLE_EAD"
/* Select crypto library based on configuration */
#if defined(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT)
#include "tinycrypt/aes.h"
#include "tinycrypt/ccm_mode.h"
#include "tinycrypt/constants.h"
#elif defined(CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS)
#include "psa/crypto.h"
#else
#error "Please select either CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT or CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS"
#endif
/* Additional Authenticated Data for EAD - EA (Encrypted Advertising) */
static const uint8_t ble_ead_aad[BLE_EAD_AAD_SIZE] = { 0xEA };
/**
* @brief Generate randomizer with direction bit set
*
* Per Bluetooth Core Spec Supplement v11, Part A 1.23.3:
* The MSB of the Randomizer shall be set to indicate direction
*/
static int ble_ead_generate_randomizer(uint8_t randomizer[BLE_EAD_RANDOMIZER_SIZE])
{
/* Generate random bytes */
esp_fill_random(randomizer, BLE_EAD_RANDOMIZER_SIZE);
/* Set direction bit (MSB of last byte) - required by spec */
randomizer[BLE_EAD_RANDOMIZER_SIZE - 1] |= (1 << BLE_EAD_RANDOMIZER_DIRECTION_BIT);
return 0;
}
/**
* @brief Generate nonce from IV and randomizer
*
* Nonce = Randomizer (5 bytes) || IV (8 bytes) = 13 bytes
*/
static int ble_ead_generate_nonce(const uint8_t iv[BLE_EAD_IV_SIZE],
const uint8_t randomizer[BLE_EAD_RANDOMIZER_SIZE],
uint8_t nonce[BLE_EAD_NONCE_SIZE])
{
if (iv == NULL || nonce == NULL) {
return -1;
}
/* Randomizer in first 5 bytes */
if (randomizer != NULL) {
memcpy(nonce, randomizer, BLE_EAD_RANDOMIZER_SIZE);
} else {
/* Generate new randomizer with direction bit */
ble_ead_generate_randomizer(nonce);
}
/* IV in last 8 bytes */
memcpy(nonce + BLE_EAD_RANDOMIZER_SIZE, iv, BLE_EAD_IV_SIZE);
return 0;
}
/**
* @brief AES-CCM encryption using selected crypto library
*/
static int ble_aes_ccm_encrypt(const uint8_t *key, const uint8_t *nonce,
const uint8_t *plaintext, size_t plaintext_len,
const uint8_t *aad, size_t aad_len,
uint8_t *ciphertext, size_t tag_len)
{
#if defined(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT)
struct tc_aes_key_sched_struct sched;
struct tc_ccm_mode_struct ccm_state;
int ret;
/* Validate inputs */
if (key == NULL || nonce == NULL || ciphertext == NULL) {
ESP_LOGE(TAG, "Invalid input parameters");
return -1;
}
/* Set AES encryption key */
ret = tc_aes128_set_encrypt_key(&sched, key);
if (ret != TC_CRYPTO_SUCCESS) {
ESP_LOGE(TAG, "tc_aes128_set_encrypt_key failed");
memset(&sched, 0, sizeof(sched));
return -1;
}
/* Configure CCM mode */
ccm_state.sched = &sched;
ccm_state.nonce = (uint8_t *)nonce;
ccm_state.mlen = tag_len;
ret = tc_ccm_config(&ccm_state, &sched, (uint8_t *)nonce, BLE_EAD_NONCE_SIZE, tag_len);
if (ret != TC_CRYPTO_SUCCESS) {
ESP_LOGE(TAG, "tc_ccm_config failed");
memset(&sched, 0, sizeof(sched));
memset(&ccm_state, 0, sizeof(ccm_state));
return -1;
}
/* Encrypt and generate tag */
/* TinyCrypt outputs: ciphertext || tag */
ret = tc_ccm_generation_encryption(ciphertext, plaintext_len + tag_len,
aad, aad_len,
plaintext, plaintext_len,
&ccm_state);
if (ret != TC_CRYPTO_SUCCESS) {
ESP_LOGE(TAG, "tc_ccm_generation_encryption failed");
memset(&sched, 0, sizeof(sched));
memset(&ccm_state, 0, sizeof(ccm_state));
return -1;
}
/* Clear sensitive data from key schedule */
memset(&sched, 0, sizeof(sched));
memset(&ccm_state, 0, sizeof(ccm_state));
return 0;
#elif defined(CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS)
psa_status_t status;
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
psa_key_id_t key_id = 0;
psa_algorithm_t alg = PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, tag_len);
size_t output_length = 0;
/* Set key attributes */
psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_ENCRYPT);
psa_set_key_algorithm(&attributes, alg);
psa_set_key_type(&attributes, PSA_KEY_TYPE_AES);
psa_set_key_bits(&attributes, BLE_EAD_KEY_SIZE * 8);
/* Import key */
status = psa_import_key(&attributes, key, BLE_EAD_KEY_SIZE, &key_id);
if (status != PSA_SUCCESS) {
ESP_LOGE(TAG, "psa_import_key failed: %d", status);
psa_reset_key_attributes(&attributes);
return -1;
}
psa_reset_key_attributes(&attributes);
/* Encrypt and authenticate */
/* PSA AEAD encrypt outputs: ciphertext || tag */
status = psa_aead_encrypt(key_id, alg,
nonce, BLE_EAD_NONCE_SIZE,
aad, aad_len,
plaintext, plaintext_len,
ciphertext, plaintext_len + tag_len,
&output_length);
if (status != PSA_SUCCESS) {
ESP_LOGE(TAG, "psa_aead_encrypt failed: %d", status);
psa_destroy_key(key_id);
return -1;
}
if (output_length != plaintext_len + tag_len) {
ESP_LOGE(TAG, "psa_aead_encrypt output length mismatch: expected %zu, got %zu",
plaintext_len + tag_len, output_length);
psa_destroy_key(key_id);
return -1;
}
psa_destroy_key(key_id);
return 0;
#else
#error "No crypto library selected"
#endif
}
/**
* @brief AES-CCM decryption with authentication using selected crypto library
*/
static int ble_aes_ccm_decrypt(const uint8_t *key, const uint8_t *nonce,
const uint8_t *ciphertext, size_t ciphertext_len,
const uint8_t *aad, size_t aad_len,
uint8_t *plaintext, size_t tag_len,
size_t plaintext_capacity)
{
#if defined(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT)
struct tc_aes_key_sched_struct sched;
struct tc_ccm_mode_struct ccm_state;
int ret;
/* ciphertext_len here includes both ciphertext and tag */
size_t plaintext_len;
/* Validate inputs */
if (key == NULL || nonce == NULL || ciphertext == NULL || plaintext == NULL) {
ESP_LOGE(TAG, "Invalid input parameters");
return -1;
}
/* Check for integer underflow */
if (ciphertext_len < tag_len) {
ESP_LOGE(TAG, "ciphertext_len (%zu) < tag_len (%zu)", ciphertext_len, tag_len);
return -1;
}
plaintext_len = ciphertext_len - tag_len;
if (plaintext_len > plaintext_capacity) {
ESP_LOGE(TAG, "plaintext_len (%zu) > plaintext_capacity (%zu)", plaintext_len, plaintext_capacity);
return -1;
}
/* Set AES encryption key */
ret = tc_aes128_set_encrypt_key(&sched, key);
if (ret != TC_CRYPTO_SUCCESS) {
ESP_LOGE(TAG, "tc_aes128_set_encrypt_key failed");
memset(&sched, 0, sizeof(sched));
return -1;
}
/* Configure CCM mode */
ccm_state.sched = &sched;
ccm_state.nonce = (uint8_t *)nonce;
ccm_state.mlen = tag_len;
ret = tc_ccm_config(&ccm_state, &sched, (uint8_t *)nonce, BLE_EAD_NONCE_SIZE, tag_len);
if (ret != TC_CRYPTO_SUCCESS) {
ESP_LOGE(TAG, "tc_ccm_config failed");
memset(&sched, 0, sizeof(sched));
memset(&ccm_state, 0, sizeof(ccm_state));
return -1;
}
/* Decrypt and verify tag */
/* TinyCrypt expects: ciphertext || tag */
ret = tc_ccm_decryption_verification(plaintext, plaintext_len,
aad, aad_len,
(uint8_t *)ciphertext, ciphertext_len,
&ccm_state);
if (ret != TC_CRYPTO_SUCCESS) {
ESP_LOGE(TAG, "tc_ccm_decryption_verification failed");
memset(&sched, 0, sizeof(sched));
memset(&ccm_state, 0, sizeof(ccm_state));
return -1;
}
/* Clear sensitive data from key schedule */
memset(&sched, 0, sizeof(sched));
memset(&ccm_state, 0, sizeof(ccm_state));
return 0;
#elif defined(CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS)
psa_status_t status;
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
psa_key_id_t key_id = 0;
psa_algorithm_t alg = PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, tag_len);
size_t output_length = 0;
/* ciphertext_len here includes both ciphertext and tag */
size_t plaintext_len;
/* Validate inputs */
if (key == NULL || nonce == NULL || ciphertext == NULL || plaintext == NULL) {
ESP_LOGE(TAG, "Invalid input parameters");
return -1;
}
/* Check for integer underflow */
if (ciphertext_len < tag_len) {
ESP_LOGE(TAG, "ciphertext_len (%zu) < tag_len (%zu)", ciphertext_len, tag_len);
return -1;
}
plaintext_len = ciphertext_len - tag_len;
if (plaintext_len > plaintext_capacity) {
ESP_LOGE(TAG, "plaintext_len (%zu) > plaintext_capacity (%zu)", plaintext_len, plaintext_capacity);
return -1;
}
/* Set key attributes */
psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT);
psa_set_key_algorithm(&attributes, alg);
psa_set_key_type(&attributes, PSA_KEY_TYPE_AES);
psa_set_key_bits(&attributes, BLE_EAD_KEY_SIZE * 8);
/* Import key */
status = psa_import_key(&attributes, key, BLE_EAD_KEY_SIZE, &key_id);
if (status != PSA_SUCCESS) {
ESP_LOGE(TAG, "psa_import_key failed: %d", status);
psa_reset_key_attributes(&attributes);
return -1;
}
psa_reset_key_attributes(&attributes);
/* Decrypt and verify */
/* PSA AEAD decrypt expects: ciphertext || tag */
status = psa_aead_decrypt(key_id, alg,
nonce, BLE_EAD_NONCE_SIZE,
aad, aad_len,
ciphertext, ciphertext_len,
plaintext, plaintext_len,
&output_length);
if (status != PSA_SUCCESS) {
ESP_LOGE(TAG, "psa_aead_decrypt failed: %d", status);
psa_destroy_key(key_id);
return -1;
}
if (output_length != plaintext_len) {
ESP_LOGE(TAG, "psa_aead_decrypt output length mismatch: expected %zu, got %zu",
plaintext_len, output_length);
psa_destroy_key(key_id);
return -1;
}
psa_destroy_key(key_id);
return 0;
#else
#error "No crypto library selected"
#endif
}
int ble_ead_encrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
const uint8_t iv[BLE_EAD_IV_SIZE],
const uint8_t *payload, size_t payload_size,
uint8_t *encrypted_payload)
{
int ret;
uint8_t nonce[BLE_EAD_NONCE_SIZE];
if (session_key == NULL) {
ESP_LOGE(TAG, "session_key is NULL");
return -1;
}
if (iv == NULL) {
ESP_LOGE(TAG, "iv is NULL");
return -1;
}
if (payload == NULL && payload_size > 0) {
ESP_LOGE(TAG, "payload is NULL but payload_size > 0");
return -1;
}
if (encrypted_payload == NULL) {
ESP_LOGE(TAG, "encrypted_payload is NULL");
return -1;
}
/* Generate nonce with random randomizer */
ret = ble_ead_generate_nonce(iv, NULL, nonce);
if (ret != 0) {
return ret;
}
/* Copy randomizer to the start of encrypted payload */
memcpy(encrypted_payload, nonce, BLE_EAD_RANDOMIZER_SIZE);
/* Encrypt: output = ciphertext + MIC */
ret = ble_aes_ccm_encrypt(session_key, nonce,
payload, payload_size,
ble_ead_aad, BLE_EAD_AAD_SIZE,
&encrypted_payload[BLE_EAD_RANDOMIZER_SIZE],
BLE_EAD_MIC_SIZE);
return ret;
}
int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
const uint8_t iv[BLE_EAD_IV_SIZE],
const uint8_t *encrypted_payload, size_t encrypted_payload_size,
uint8_t *payload, size_t payload_capacity)
{
int ret;
uint8_t nonce[BLE_EAD_NONCE_SIZE];
const uint8_t *randomizer;
const uint8_t *ciphertext;
size_t ciphertext_len;
size_t expected_plaintext_len;
if (session_key == NULL) {
ESP_LOGE(TAG, "session_key is NULL");
return -1;
}
if (iv == NULL) {
ESP_LOGE(TAG, "iv is NULL");
return -1;
}
if (encrypted_payload == NULL) {
ESP_LOGE(TAG, "encrypted_payload is NULL");
return -1;
}
if (payload == NULL) {
ESP_LOGE(TAG, "payload is NULL");
return -1;
}
if (encrypted_payload_size < BLE_EAD_RANDOMIZER_SIZE + BLE_EAD_MIC_SIZE) {
ESP_LOGE(TAG, "encrypted_payload_size too small");
return -1;
}
expected_plaintext_len = BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size);
if (expected_plaintext_len > payload_capacity) {
ESP_LOGE(TAG, "EAD plaintext length %zu exceeds payload buffer %zu",
expected_plaintext_len, payload_capacity);
return -1;
}
/* Extract randomizer from the start of encrypted payload */
randomizer = encrypted_payload;
/* Ciphertext + MIC follows the randomizer */
ciphertext = &encrypted_payload[BLE_EAD_RANDOMIZER_SIZE];
/* ciphertext_len includes both ciphertext and MIC (tag) for PSA API */
ciphertext_len = encrypted_payload_size - BLE_EAD_RANDOMIZER_SIZE;
/* Generate nonce from randomizer and IV */
ret = ble_ead_generate_nonce(iv, randomizer, nonce);
if (ret != 0) {
return ret;
}
/* Decrypt and verify */
ret = ble_aes_ccm_decrypt(session_key, nonce,
ciphertext, ciphertext_len,
ble_ead_aad, BLE_EAD_AAD_SIZE,
payload, BLE_EAD_MIC_SIZE,
payload_capacity);
return ret;
}
@@ -1,96 +0,0 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef BLE_EAD_H
#define BLE_EAD_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief BLE Encrypted Advertising Data (EAD) definitions
* Based on Bluetooth Core Specification Version 5.4
*/
#define BLE_EAD_KEY_SIZE 16 /* 128-bit session key */
#define BLE_EAD_IV_SIZE 8 /* 64-bit Initialization Vector */
#define BLE_EAD_RANDOMIZER_SIZE 5 /* 40-bit Randomizer */
#define BLE_EAD_MIC_SIZE 4 /* 32-bit Message Integrity Check */
#define BLE_EAD_NONCE_SIZE 13 /* 104-bit Nonce (Randomizer + IV) */
#define BLE_EAD_AAD_SIZE 1 /* Additional Authenticated Data size */
/* Direction bit position in Randomizer (MSB of last byte)
* Per Bluetooth Core Spec Supplement v11, Part A 1.23.3
*/
#define BLE_EAD_RANDOMIZER_DIRECTION_BIT 7
/* AD Type for Encrypted Advertising Data (0x31) */
#define ESP_BLE_AD_TYPE_ENC_ADV_DATA 0x31
/**
* @brief Calculate encrypted payload size from plaintext size
*/
#define BLE_EAD_ENCRYPTED_PAYLOAD_SIZE(payload_size) \
(BLE_EAD_RANDOMIZER_SIZE + (payload_size) + BLE_EAD_MIC_SIZE)
/**
* @brief Calculate decrypted payload size from encrypted payload size
*/
#define BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_size) \
((encrypted_size) - BLE_EAD_RANDOMIZER_SIZE - BLE_EAD_MIC_SIZE)
/**
* @brief Key material structure for EAD
*/
typedef struct {
uint8_t session_key[BLE_EAD_KEY_SIZE]; /* 128-bit session key */
uint8_t iv[BLE_EAD_IV_SIZE]; /* 64-bit Initialization Vector */
} ble_ead_key_material_t;
/**
* @brief Encrypt advertising data using AES-CCM
*
* @param session_key 16-byte session key
* @param iv 8-byte Initialization Vector
* @param payload Plaintext advertising data to encrypt
* @param payload_size Size of plaintext data
* @param encrypted_payload Output buffer for encrypted data
* Size must be at least BLE_EAD_ENCRYPTED_PAYLOAD_SIZE(payload_size)
*
* @return 0 on success, negative error code on failure
*/
int ble_ead_encrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
const uint8_t iv[BLE_EAD_IV_SIZE],
const uint8_t *payload, size_t payload_size,
uint8_t *encrypted_payload);
/**
* @brief Decrypt advertising data using AES-CCM
*
* @param session_key 16-byte session key
* @param iv 8-byte Initialization Vector
* @param encrypted_payload Encrypted advertising data (includes randomizer and MIC)
* @param encrypted_payload_size Size of encrypted data
* @param payload Output buffer for decrypted data
* @param payload_capacity Size of @a payload in bytes; must be >=
* BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size)
*
* @return 0 on success, negative error code on failure
*/
int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
const uint8_t iv[BLE_EAD_IV_SIZE],
const uint8_t *encrypted_payload, size_t encrypted_payload_size,
uint8_t *payload, size_t payload_capacity);
#ifdef __cplusplus
}
#endif
#endif /* BLE_EAD_H */
@@ -30,7 +30,7 @@
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "esp_gatt_common_api.h"
#include "ble_ead.h"
#include "esp_ble_ead.h"
#define TAG "ENC_ADV_PRPH"
@@ -48,7 +48,7 @@ static uint8_t unencrypted_adv_pattern[] = {
};
/* Session key and IV for encryption - in real application, generate securely! */
static ble_ead_key_material_t key_material = {
static esp_ble_ead_key_material_t key_material = {
.session_key = {
0x19, 0x6a, 0x0a, 0xd1, 0x2a, 0x61, 0x20, 0x1e,
0x13, 0x6e, 0x2e, 0xd1, 0x12, 0xda, 0xa9, 0x57
@@ -71,7 +71,7 @@ static esp_ble_adv_params_t adv_params = {
};
/* Calculate encrypted payload size */
#define ENCRYPTED_ADV_DATA_LEN BLE_EAD_ENCRYPTED_PAYLOAD_SIZE(sizeof(unencrypted_adv_pattern))
#define ENCRYPTED_ADV_DATA_LEN ESP_BLE_EAD_ENCRYPTED_PAYLOAD_SIZE(sizeof(unencrypted_adv_pattern))
/**
* @brief Encrypt advertising data and set raw advertising data
@@ -80,16 +80,16 @@ static void set_encrypted_adv_data(void)
{
esp_err_t ret;
uint8_t encrypted_adv_data[ENCRYPTED_ADV_DATA_LEN];
int rc;
esp_err_t rc;
ESP_LOGI(TAG, "Data before encryption:");
ESP_LOG_BUFFER_HEX(TAG, unencrypted_adv_pattern, sizeof(unencrypted_adv_pattern));
/* Encrypt the advertising data */
rc = ble_ead_encrypt(key_material.session_key, key_material.iv,
unencrypted_adv_pattern, sizeof(unencrypted_adv_pattern),
encrypted_adv_data);
if (rc != 0) {
rc = esp_ble_ead_encrypt(key_material.session_key, key_material.iv,
unencrypted_adv_pattern, sizeof(unencrypted_adv_pattern),
encrypted_adv_data);
if (rc != ESP_OK) {
ESP_LOGE(TAG, "Encryption of adv data failed: %d", rc);
return;
}
@@ -8,6 +8,10 @@ CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
# Enable SMP for security
CONFIG_BT_BLE_SMP_ENABLE=y
# Encrypted Advertising Data APIs in Bluedroid host
CONFIG_BT_BLE_FEAT_ENC_ADV_DATA=y
CONFIG_BT_GATTS_KEY_MATERIAL_CHAR=y
# Select crypto library for EAD (Encrypted Advertising Data)
# Options: CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT or CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS
CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT=y