Merge branch 'feat/support_bluedroid_host_smp_with_psa_tinycrypt_v5.4' into 'release/v5.4'

Feat/support bluedroid host smp with psa tinycrypt v5.4

See merge request espressif/esp-idf!44835
This commit is contained in:
Island
2026-01-07 14:16:31 +08:00
48 changed files with 3612 additions and 52 deletions
+6 -1
View File
@@ -693,7 +693,12 @@ if(CONFIG_BT_ENABLED)
)
endif()
if(NOT (CONFIG_BT_LE_CRYPTO_STACK_MBEDTLS OR CONFIG_BT_NIMBLE_CRYPTO_STACK_MBEDTLS))
# Compile TinyCrypt if:
# 1. Controller uses TinyCrypt (not mbedTLS), OR
# 2. NimBLE uses TinyCrypt (not mbedTLS), OR
# 3. Bluedroid Host SMP uses TinyCrypt
if(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT OR
(NOT CONFIG_BT_LE_CRYPTO_STACK_MBEDTLS AND NOT CONFIG_BT_NIMBLE_CRYPTO_STACK_MBEDTLS))
list(APPEND include_dirs
common/tinycrypt/include
common/tinycrypt/port
+37
View File
@@ -6,6 +6,43 @@ config BT_ALARM_MAX_NUM
This option decides the maximum number of alarms which
could be used by Bluetooth host.
choice BT_SMP_CRYPTO_STACK
prompt "SMP cryptographic stack"
depends on (BT_BLE_SMP_ENABLE || BT_SMP_ENABLE || BT_NIMBLE_SECURITY_ENABLE)
default BT_SMP_CRYPTO_STACK_NATIVE
help
Select the cryptographic library to use for SMP operations (AES, AES-CMAC, ECDH P-256).
config BT_SMP_CRYPTO_STACK_NATIVE
bool "Native Bluedroid implementation"
depends on (BT_BLE_SMP_ENABLE || BT_SMP_ENABLE)
help
Use the built-in Bluedroid cryptographic implementation.
This provides compatibility with all features.
This option is only available for Bluedroid host.
config BT_SMP_CRYPTO_STACK_TINYCRYPT
bool "TinyCrypt"
help
Use TinyCrypt library for cryptographic operations.
TinyCrypt is a lightweight cryptographic library designed for constrained devices.
This can reduce code size compared to the native implementation.
This is the default option.
config BT_SMP_CRYPTO_STACK_MBEDTLS
bool "mbedTLS"
select MBEDTLS_AES_C
select MBEDTLS_CMAC_C
select MBEDTLS_ECDH_C
select MBEDTLS_ECP_C
select MBEDTLS_ECP_DP_SECP256R1_ENABLED
help
Use mbedTLS library for cryptographic operations.
This can provide hardware acceleration on supported platforms and reduce code size
by sharing crypto implementations with other components.
endchoice
menu "BLE Log"
source "$IDF_PATH/components/bt/common/ble_log/Kconfig.in"
endmenu
+10
View File
@@ -326,6 +326,16 @@ config BT_GATTS_SECURITY_LEVELS_CHAR
help
Enable LE GATT Security Levels Characteristic
config BT_GATTS_KEY_MATERIAL_CHAR
bool "Enable Encrypted Data Key Material Characteristic"
depends on BT_GATTS_ENABLE
default n
help
Enable the Encrypted Data Key Material characteristic in GAP service.
This characteristic allows advertising data to be decrypted and authenticated
using the key material (session key + IV) as defined in Bluetooth Core
Specification Version 5.4. The characteristic requires encrypted link to read.
menuconfig BT_GATTC_ENABLE
bool "Include GATT client module(GATTC)"
depends on BT_BLE_ENABLED
@@ -440,6 +440,28 @@ esp_err_t esp_ble_gap_get_device_name(void)
return (btc_transfer_context(&msg, NULL, 0, NULL, NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL);
}
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
esp_err_t esp_ble_gap_set_key_material(const uint8_t session_key[16], const uint8_t iv[8])
{
btc_msg_t msg = {0};
btc_ble_gap_args_t arg;
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
if (session_key == NULL || iv == NULL) {
return ESP_ERR_INVALID_ARG;
}
msg.sig = BTC_SIG_API_CALL;
msg.pid = BTC_PID_GAP_BLE;
msg.act = BTC_GAP_BLE_ACT_SET_KEY_MATERIAL;
memcpy(arg.set_key_material.session_key, session_key, 16);
memcpy(arg.set_key_material.iv, iv, 8);
return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gap_args_t), NULL, NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL);
}
#endif // (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
esp_err_t esp_ble_gap_get_local_used_addr(esp_bd_addr_t local_used_addr, uint8_t * addr_type)
{
if(esp_bluedroid_get_status() != (ESP_BLUEDROID_STATUS_ENABLED)) {
@@ -3120,6 +3120,25 @@ esp_err_t esp_ble_gap_set_device_name(const char *name);
*/
esp_err_t esp_ble_gap_get_device_name(void);
#if defined(CONFIG_BT_GATTS_KEY_MATERIAL_CHAR) && CONFIG_BT_GATTS_KEY_MATERIAL_CHAR
/**
* @brief Set the Encrypted Data Key Material in GAP service
*
* This function sets the session key and IV that will be exposed
* through the Key Material characteristic (UUID 0x2B88) in the GAP service.
* The Key Material allows central devices to decrypt encrypted advertising data.
*
* @param[in] session_key - 16-byte (128-bit) session key for AES-CCM encryption
* @param[in] iv - 8-byte (64-bit) initialization vector
*
* @return
* - ESP_OK : success
* - other : failed
*
*/
esp_err_t esp_ble_gap_set_key_material(const uint8_t session_key[16], const uint8_t iv[8]);
#endif // CONFIG_BT_GATTS_KEY_MATERIAL_CHAR
/**
* @brief This function is called to get local used address and address type.
* uint8_t *esp_bt_dev_get_address(void) get the public address
@@ -5406,6 +5406,22 @@ void bta_dm_ble_config_local_icon (tBTA_DM_MSG *p_data)
BTM_BleConfigLocalIcon (p_data->ble_local_icon.icon);
}
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
/*******************************************************************************
**
** Function bta_dm_ble_set_key_material
**
** Description This function sets the Encrypted Data Key Material.
**
**
*******************************************************************************/
void bta_dm_ble_set_key_material (tBTA_DM_MSG *p_data)
{
BTM_BleSetKeyMaterial (p_data->ble_key_material.session_key,
p_data->ble_key_material.iv);
}
#endif
#if (BLE_HOST_BLE_OBSERVE_EN == TRUE)
/*******************************************************************************
**
@@ -2223,6 +2223,41 @@ void BTA_DmBleConfigLocalIcon(uint16_t icon)
}
}
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
/*******************************************************************************
**
** Function BTA_DmBleSetKeyMaterial
**
** Description Set the Encrypted Data Key Material in GAP service
**
** Parameters: session_key - 16-byte session key (must not be NULL)
** iv - 8-byte initialization vector (must not be NULL)
**
** Returns void
**
*******************************************************************************/
void BTA_DmBleSetKeyMaterial(const uint8_t *session_key, const uint8_t *iv)
{
tBTA_DM_API_KEY_MATERIAL *p_msg;
if (session_key == NULL || iv == NULL) {
APPL_TRACE_ERROR("%s: NULL pointer parameter", __func__);
return;
}
if ((p_msg = (tBTA_DM_API_KEY_MATERIAL *) osi_malloc(sizeof(tBTA_DM_API_KEY_MATERIAL))) != NULL) {
memset(p_msg, 0, sizeof(tBTA_DM_API_KEY_MATERIAL));
p_msg->hdr.event = BTA_DM_API_KEY_MATERIAL_EVT;
memcpy(p_msg->session_key, session_key, 16);
memcpy(p_msg->iv, iv, 8);
bta_sys_sendmsg(p_msg);
} else {
APPL_TRACE_ERROR("%s: failed to allocate memory", __func__);
}
}
#endif
#if (BLE_HOST_BLE_MULTI_ADV_EN == TRUE)
/*******************************************************************************
**
@@ -170,6 +170,9 @@ const tBTA_DM_ACTION bta_dm_action[BTA_DM_MAX_EVT] = {
bta_dm_ble_config_local_privacy, /* BTA_DM_API_LOCAL_PRIVACY_EVT */
#endif
bta_dm_ble_config_local_icon, /* BTA_DM_API_LOCAL_ICON_EVT */
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
bta_dm_ble_set_key_material, /* BTA_DM_API_KEY_MATERIAL_EVT */
#endif
#if (BLE_42_ADV_EN == TRUE)
bta_dm_ble_set_adv_params_all, /* BTA_DM_API_BLE_ADV_PARAM_All_EVT */
bta_dm_ble_set_adv_config, /* BTA_DM_API_BLE_SET_ADV_CONFIG_EVT */
@@ -159,6 +159,9 @@ enum {
BTA_DM_API_LOCAL_PRIVACY_EVT,
#endif
BTA_DM_API_LOCAL_ICON_EVT,
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
BTA_DM_API_KEY_MATERIAL_EVT,
#endif
/*******This event added by Yulong at 2016/10/20 to
support setting the ble advertising param by the APP******/
@@ -853,6 +856,14 @@ typedef struct {
uint16_t icon;
} tBTA_DM_API_LOCAL_ICON;
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
typedef struct {
BT_HDR hdr;
uint8_t session_key[16];
uint8_t iv[8];
} tBTA_DM_API_KEY_MATERIAL;
#endif
/* set scan parameter for BLE connections */
typedef struct {
BT_HDR hdr;
@@ -1900,6 +1911,9 @@ typedef union {
tBTA_DM_API_ENABLE_PRIVACY ble_remote_privacy;
tBTA_DM_API_LOCAL_PRIVACY ble_local_privacy;
tBTA_DM_API_LOCAL_ICON ble_local_icon;
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
tBTA_DM_API_KEY_MATERIAL ble_key_material;
#endif
tBTA_DM_API_BLE_ADV_PARAMS_ALL ble_set_adv_params_all;
tBTA_DM_API_SET_ADV_CONFIG ble_set_adv_data;
tBTA_DM_API_SET_ADV_CONFIG_RAW ble_set_adv_data_raw;
@@ -2504,6 +2518,9 @@ extern void bta_dm_ble_stop_advertising(tBTA_DM_MSG *p_data);
#endif // #if (BLE_HOST_STOP_ADV_UNUSED == TRUE)
extern void bta_dm_ble_config_local_privacy (tBTA_DM_MSG *p_data);
extern void bta_dm_ble_config_local_icon (tBTA_DM_MSG *p_data);
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
extern void bta_dm_ble_set_key_material (tBTA_DM_MSG *p_data);
#endif
extern void bta_dm_ble_set_adv_params_all(tBTA_DM_MSG *p_data);
extern void bta_dm_ble_set_adv_config (tBTA_DM_MSG *p_data);
extern void bta_dm_ble_set_adv_config_raw (tBTA_DM_MSG *p_data);
@@ -2939,6 +2939,22 @@ extern void BTA_DmBleConfigLocalPrivacy(BOOLEAN privacy_enable, tBTA_SET_LOCAL_P
*******************************************************************************/
extern void BTA_DmBleConfigLocalIcon(uint16_t icon);
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
/*******************************************************************************
**
** Function BTA_DmBleSetKeyMaterial
**
** Description Set the Encrypted Data Key Material in GAP service
**
** Parameters: session_key - 16-byte session key
** iv - 8-byte initialization vector
**
** Returns void
**
*******************************************************************************/
extern void BTA_DmBleSetKeyMaterial(const uint8_t *session_key, const uint8_t *iv);
#endif
/*******************************************************************************
**
** Function BTA_DmBleEnableRemotePrivacy
@@ -3192,6 +3192,11 @@ void btc_gap_ble_call_handler(btc_msg_t *msg)
BTA_DmBleGapCsProcEnable(arg_5->cs_procedure_enable_params.conn_handle, arg_5->cs_procedure_enable_params.config_id, arg_5->cs_procedure_enable_params.enable);
break;
#endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE)
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
case BTC_GAP_BLE_ACT_SET_KEY_MATERIAL:
BTA_DmBleSetKeyMaterial(arg->set_key_material.session_key, arg->set_key_material.iv);
break;
#endif
default:
break;
}
@@ -160,6 +160,9 @@ typedef enum {
BTC_GAP_BLE_CS_SET_PROCEDURE_PARAMS,
BTC_GAP_BLE_CS_PROCEDURE_ENABLE,
#endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE)
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
BTC_GAP_BLE_ACT_SET_KEY_MATERIAL,
#endif
} btc_gap_ble_act_t;
/* btc_ble_gap_args_t */
@@ -215,6 +218,13 @@ typedef union {
struct cfg_local_icon_args {
uint16_t icon;
} cfg_local_icon;
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
//BTC_GAP_BLE_ACT_SET_KEY_MATERIAL
struct set_key_material_args {
uint8_t session_key[16];
uint8_t iv[8];
} set_key_material;
#endif
//BTC_GAP_BLE_ACT_UPDATE_WHITE_LIST
struct update_white_list_args {
bool add_remove;
@@ -436,6 +436,24 @@
#define UC_BT_BLE_SMP_BOND_NVS_FLASH FALSE
#endif
#ifdef CONFIG_BT_SMP_CRYPTO_STACK_NATIVE
#define UC_BT_SMP_CRYPTO_STACK_NATIVE TRUE
#else
#define UC_BT_SMP_CRYPTO_STACK_NATIVE FALSE
#endif
#ifdef CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS
#define UC_BT_SMP_CRYPTO_MBEDTLS TRUE
#else
#define UC_BT_SMP_CRYPTO_MBEDTLS FALSE
#endif
#ifdef CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT
#define UC_BT_SMP_CRYPTO_TINYCRYPT TRUE
#else
#define UC_BT_SMP_CRYPTO_TINYCRYPT FALSE
#endif
//Device Name Maximum Length
#ifdef CONFIG_BT_MAX_DEVICE_NAME_LEN
#define UC_MAX_LOC_BD_NAME_LEN CONFIG_BT_MAX_DEVICE_NAME_LEN
@@ -561,6 +579,12 @@
#define UC_BT_GATTS_SECURITY_LEVELS_CHAR FALSE
#endif
#ifdef CONFIG_BT_GATTS_KEY_MATERIAL_CHAR
#define UC_BT_GATTS_KEY_MATERIAL_CHAR CONFIG_BT_GATTS_KEY_MATERIAL_CHAR
#else
#define UC_BT_GATTS_KEY_MATERIAL_CHAR FALSE
#endif
#ifdef CONFIG_BT_BLE_ACT_SCAN_REP_ADV_SCAN
#define UC_BT_BLE_ACT_SCAN_REP_ADV_SCAN CONFIG_BT_BLE_ACT_SCAN_REP_ADV_SCAN
#else
@@ -494,6 +494,24 @@
#define BLE_SMP_BOND_NVS_FLASH FALSE
#endif
#if (UC_BT_SMP_CRYPTO_STACK_NATIVE)
#define SMP_CRYPTO_STACK_NATIVE TRUE
#else
#define SMP_CRYPTO_STACK_NATIVE FALSE
#endif /* UC_BT_SMP_CRYPTO_STACK_NATIVE */
#if (UC_BT_SMP_CRYPTO_MBEDTLS)
#define SMP_CRYPTO_MBEDTLS TRUE
#else
#define SMP_CRYPTO_MBEDTLS FALSE
#endif /* UC_BT_SMP_CRYPTO_MBEDTLS */
#if (UC_BT_SMP_CRYPTO_TINYCRYPT)
#define SMP_CRYPTO_TINYCRYPT TRUE
#else
#define SMP_CRYPTO_TINYCRYPT FALSE
#endif /* UC_BT_SMP_CRYPTO_TINYCRYPT */
#ifdef UC_BTDM_BLE_ADV_REPORT_FLOW_CTRL_SUPP
#define BLE_ADV_REPORT_FLOW_CONTROL (UC_BTDM_BLE_ADV_REPORT_FLOW_CTRL_SUPP && BLE_INCLUDED)
#endif /* UC_BTDM_BLE_ADV_REPORT_FLOW_CTRL_SUPP */
@@ -771,6 +789,12 @@
#define BT_GATTS_SECURITY_LEVELS_CHAR FALSE
#endif
#if (UC_BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
#define BT_GATTS_KEY_MATERIAL_CHAR TRUE
#else
#define BT_GATTS_KEY_MATERIAL_CHAR FALSE
#endif
#ifdef UC_BT_BLE_ACT_SCAN_REP_ADV_SCAN
#define BTM_BLE_ACTIVE_SCAN_REPORT_ADV_SCAN_RSP_INDIVIDUALLY UC_BT_BLE_ACT_SCAN_REP_ADV_SCAN
#endif
@@ -1164,6 +1164,39 @@ void BTM_BleConfigLocalIcon(uint16_t icon)
#endif
}
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
/*******************************************************************************
**
** Function BTM_BleSetKeyMaterial
**
** Description Set the Encrypted Data Key Material in GAP service
**
** Parameters session_key: 16-byte session key (must not be NULL)
** iv: 8-byte initialization vector (must not be NULL)
**
** Returns void
**
*******************************************************************************/
void BTM_BleSetKeyMaterial(const uint8_t *session_key, const uint8_t *iv)
{
#if (defined(GAP_INCLUDED) && GAP_INCLUDED == TRUE && GATTS_INCLUDED == TRUE)
tGAP_BLE_ATTR_VALUE p_value;
if (session_key == NULL || iv == NULL) {
BTM_TRACE_ERROR("%s: NULL pointer parameter", __func__);
return;
}
memset(&p_value, 0, sizeof(tGAP_BLE_ATTR_VALUE));
memcpy(p_value.key_material.session_key, session_key, GAP_KEY_MATERIAL_SESSION_KEY_SIZE);
memcpy(p_value.key_material.iv, iv, GAP_KEY_MATERIAL_IV_SIZE);
GAP_BleAttrDBUpdate(GATT_UUID_GAP_KEY_MATERIAL, &p_value);
#else
BTM_TRACE_ERROR("%s\n", __func__);
#endif
}
#endif
/*******************************************************************************
**
** Function BTM_BleConfigConnParams
@@ -262,6 +262,13 @@ tGATT_STATUS gap_read_attr_value (UINT16 handle, tGATT_VALUE *p_value, BOOLEAN i
p_value->len = 2;
break;
#endif // (BT_GATTS_SECURITY_LEVELS_CHAR == TRUE)
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
case GATT_UUID_GAP_KEY_MATERIAL:
ARRAY_TO_STREAM(p, p_db_attr->attr_value.key_material.session_key, GAP_KEY_MATERIAL_SESSION_KEY_SIZE);
ARRAY_TO_STREAM(p, p_db_attr->attr_value.key_material.iv, GAP_KEY_MATERIAL_IV_SIZE);
p_value->len = GAP_KEY_MATERIAL_SIZE;
break;
#endif // (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
}
return GATT_SUCCESS;
}
@@ -481,6 +488,20 @@ void gap_attr_db_init(void)
p_db_attr++;
#endif // (BT_GATTS_SECURITY_LEVELS_CHAR == TRUE)
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
/* Add Encrypted Data Key Material Characteristic
* Per Bluetooth spec: readable only when authenticated and authorized,
* requires encrypted link to read.
*/
uuid.len = LEN_UUID_16;
uuid.uu.uuid16 = p_db_attr->uuid = GATT_UUID_GAP_KEY_MATERIAL;
p_db_attr->handle = GATTS_AddCharacteristic(service_handle, &uuid,
GATT_PERM_READ_ENCRYPTED, GATT_CHAR_PROP_BIT_READ,
NULL, NULL);
memset(&p_db_attr->attr_value.key_material, 0, sizeof(tGAP_BLE_KEY_MATERIAL));
p_db_attr++;
#endif // (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
/* start service now */
memset (&app_uuid.uu.uuid128, 0x81, LEN_UUID_128);
@@ -512,6 +533,11 @@ void GAP_BleAttrDBUpdate(UINT16 attr_uuid, tGAP_BLE_ATTR_VALUE *p_value)
GAP_TRACE_EVENT("GAP_BleAttrDBUpdate attr_uuid=0x%04x\n", attr_uuid);
if (p_value == NULL) {
GAP_TRACE_ERROR("GAP_BleAttrDBUpdate: NULL pointer parameter");
return;
}
for (i = 0; i < GAP_MAX_CHAR_NUM; i ++, p_db_attr ++) {
if (p_db_attr->uuid == attr_uuid) {
GAP_TRACE_EVENT("Found attr_uuid=0x%04x\n", attr_uuid);
@@ -540,6 +566,13 @@ void GAP_BleAttrDBUpdate(UINT16 attr_uuid, tGAP_BLE_ATTR_VALUE *p_value)
break;
#endif // (BT_GATTS_SECURITY_LEVELS_CHAR == TRUE)
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
case GATT_UUID_GAP_KEY_MATERIAL:
memcpy(&p_db_attr->attr_value.key_material, &p_value->key_material,
sizeof(tGAP_BLE_KEY_MATERIAL));
break;
#endif // (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
}
break;
}
@@ -93,7 +93,11 @@ typedef struct {
#if BLE_INCLUDED == TRUE
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
#define GAP_MAX_CHAR_NUM 6
#else
#define GAP_MAX_CHAR_NUM 5
#endif
typedef struct {
UINT16 handle;
@@ -2948,6 +2948,22 @@ BOOLEAN BTM_BleConfigPrivacy(BOOLEAN enable, tBTM_SET_LOCAL_PRIVACY_CBACK *set_l
*******************************************************************************/
void BTM_BleConfigLocalIcon(uint16_t icon);
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
/*******************************************************************************
**
** Function BTM_BleSetKeyMaterial
**
** Description Set the Encrypted Data Key Material in GAP service
**
** Parameters session_key: 16-byte session key
** iv: 8-byte initialization vector
**
** Returns void
**
*******************************************************************************/
void BTM_BleSetKeyMaterial(const uint8_t *session_key, const uint8_t *iv);
#endif
/*******************************************************************************
**
** Function BTM_BleConfigConnParams
@@ -113,6 +113,17 @@ typedef struct {
UINT16 sp_tout;
} tGAP_BLE_PREF_PARAM;
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
#define GAP_KEY_MATERIAL_SESSION_KEY_SIZE 16 /* 128-bit session key */
#define GAP_KEY_MATERIAL_IV_SIZE 8 /* 64-bit IV */
#define GAP_KEY_MATERIAL_SIZE (GAP_KEY_MATERIAL_SESSION_KEY_SIZE + GAP_KEY_MATERIAL_IV_SIZE)
typedef struct {
UINT8 session_key[GAP_KEY_MATERIAL_SESSION_KEY_SIZE];
UINT8 iv[GAP_KEY_MATERIAL_IV_SIZE];
} tGAP_BLE_KEY_MATERIAL;
#endif // (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
typedef union {
tGAP_BLE_PREF_PARAM conn_param;
BD_ADDR reconn_bda;
@@ -122,6 +133,9 @@ typedef union {
#if (BT_GATTS_SECURITY_LEVELS_CHAR == TRUE)
UINT16 security_level;
#endif // (BT_GATTS_SECURITY_LEVELS_CHAR == TRUE)
#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
tGAP_BLE_KEY_MATERIAL key_material;
#endif // (BT_GATTS_KEY_MATERIAL_CHAR == TRUE)
} tGAP_BLE_ATTR_VALUE;
@@ -53,6 +53,7 @@
#define GATT_UUID_GAP_CENTRAL_ADDR_RESOL 0x2AA6
#define GATT_UUID_GAP_GATT_SECURITY_LEVELS 0x2BF5
#define GATT_UUID_GAP_KEY_MATERIAL 0x2B88 /* Encrypted Data Key Material */
/* Attribute Profile Attribute UUID */
#define GATT_UUID_GATT_SRV_CHGD 0x2A05
+5 -1
View File
@@ -48,6 +48,8 @@
/* add the target configuration to allow using internal data types and compilation options */
#include "common/bt_target.h"
#if (SMP_CRYPTO_STACK_NATIVE == TRUE)
/* define if you have fast 32-bit types on your system */
#if 1
# define HAVE_UINT_32T
@@ -569,7 +571,7 @@ return_type aes_set_key( const unsigned char key[], length_type keylen, aes_cont
/* Encrypt a single block of 16 bytes */
/* @breif change the name by snake for avoid the conflict with libcrypto */
/* @brief change the name by snake for avoid the conflict with libcrypto */
return_type bluedroid_aes_encrypt( const unsigned char in[N_BLOCK], unsigned char out[N_BLOCK], const aes_context ctx[1] )
{
if ( ctx->rnd ) {
@@ -935,4 +937,6 @@ void bluedroid_aes_decrypt_256( const unsigned char in[N_BLOCK], unsigned char o
copy_and_key( out, s1, o_key );
}
#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */
#endif
@@ -31,6 +31,10 @@
#ifndef AES_H
#define AES_H
#include "common/bt_target.h"
#if (SMP_CRYPTO_STACK_NATIVE == TRUE)
#if 1
# define AES_ENC_PREKEYED /* AES encryption with a precomputed key schedule */
#endif
@@ -117,7 +121,7 @@ return_type aes_cbc_decrypt( const unsigned char *in,
The encryption subroutines take a key in an array of bytes in
key[L] where L is 16, 24 or 32 bytes for key lengths of 128,
192, and 256 bits respectively. They then encrypts the input
data, in[] with this key and put the reult in the output array
data, in[] with this key and put the result in the output array
out[]. In addition, the second key array, o_key[L], is used
to output the key that is needed by the decryption subroutine
to reverse the encryption operation. The two key arrays can
@@ -159,4 +163,6 @@ void bluedroid_aes_decrypt_256( const unsigned char in[N_BLOCK],
unsigned char o_key[2 * N_BLOCK] );
#endif
#endif
#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */
#endif /* AES_H */
@@ -24,9 +24,12 @@
#pragma once
#include "p_256_multprecision.h"
#include "common/bt_target.h"
#if (SMP_CRYPTO_STACK_NATIVE == TRUE)
#include "p_256_multprecision.h"
typedef unsigned long DWORD;
typedef struct {
@@ -72,3 +75,5 @@ bool ECC_CheckPointIsInElliCur_P256(Point *p);
#define ECC_PointMult(q, p, n, keyLength) ECC_PointMult_Bin_NAF(q, p, n, keyLength)
void p_256_init_curve(UINT32 keyLength);
#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */
@@ -24,6 +24,9 @@
#pragma once
#include "stack/bt_types.h"
#include "common/bt_target.h"
#if (SMP_CRYPTO_STACK_NATIVE == TRUE)
/* Type definitions */
typedef unsigned long DWORD;
@@ -58,3 +61,5 @@ DWORD multiprecision_lshift(DWORD *c, DWORD *a, uint32_t keyLength);
void multiprecision_mult(DWORD *c, DWORD *a, DWORD *b, uint32_t keyLength);
void multiprecision_fast_mod(DWORD *c, DWORD *a);
void multiprecision_fast_mod_P256(DWORD *c, DWORD *a);
#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */
@@ -23,6 +23,10 @@
******************************************************************************/
#include <string.h>
#include "common/bt_target.h"
#if (SMP_CRYPTO_STACK_NATIVE == TRUE)
#include "p_256_ecc_pp.h"
void p_256_init_curve(UINT32 keyLength)
@@ -76,3 +80,5 @@ void p_256_init_curve(UINT32 keyLength)
ec->G.y[0] = 0x37bf51f5;
}
}
#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */
@@ -24,9 +24,12 @@
//#include <stdio.h>
//#include <stdlib.h>
#include <string.h>
#include "common/bt_target.h"
#if (SMP_CRYPTO_STACK_NATIVE == TRUE)
#include "p_256_ecc_pp.h"
#include "p_256_multprecision.h"
#include "common/bt_target.h"
#if SMP_DYNAMIC_MEMORY == FALSE
elliptic_curve_t curve;
@@ -281,3 +284,5 @@ bool ECC_CheckPointIsInElliCur_P256(Point *p)
return true;
}
}
#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */
@@ -24,6 +24,9 @@
#include <string.h>
#include "common/bt_target.h"
#if (SMP_CRYPTO_STACK_NATIVE == TRUE)
#include "p_256_ecc_pp.h"
#include "p_256_multprecision.h"
@@ -365,7 +368,7 @@ void multiprecision_fast_mod_P256(DWORD *c, DWORD *a)
uint8_t UB;
uint8_t UC;
uint8_t UD;
uint8_t UE;
uint8_t U_E;
uint8_t UF;
uint8_t UG;
DWORD U;
@@ -381,7 +384,7 @@ void multiprecision_fast_mod_P256(DWORD *c, DWORD *a)
// E = a[8] + a[9];
E = a[8];
E += a[9];
UE = (E < a[9]);
U_E = (E < a[9]);
// F = a[9] + a[10];
F = a[9];
@@ -418,7 +421,7 @@ void multiprecision_fast_mod_P256(DWORD *c, DWORD *a)
c[0] = a[0];
c[0] += E;
U = (c[0] < E);
U += UE;
U += U_E;
U -= (c[0] < A);
U -= UA;
c[0] -= A;
@@ -479,7 +482,7 @@ void multiprecision_fast_mod_P256(DWORD *c, DWORD *a)
U -= (c[3] < a[15]);
c[3] -= a[15];
U -= (c[3] < E);
U -= UE;
U -= U_E;
c[3] -= E;
if (U & 0x80000000) {
@@ -546,7 +549,7 @@ void multiprecision_fast_mod_P256(DWORD *c, DWORD *a)
c[6] += a[15];
U += (c[6] < a[15]);
U -= (c[6] < E);
U -= UE;
U -= U_E;
c[6] -= E;
if (U & 0x80000000) {
@@ -645,3 +648,5 @@ void multiprecision_inv_mod(DWORD *aminus, DWORD *u, uint32_t keyLength)
multiprecision_copy(aminus, C, keyLength);
}
}
#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */
@@ -22,7 +22,15 @@
#include "btm_int.h"
#include "stack/l2c_api.h"
#include "smp_int.h"
#if (SMP_CRYPTO_MBEDTLS == TRUE)
#include "mbedtls/ecp.h"
#elif (SMP_CRYPTO_TINYCRYPT == TRUE)
#include "tinycrypt/ecc_dh.h"
#include "tinycrypt/ecc.h"
#include "tinycrypt/constants.h"
#else
#include "p_256_ecc_pp.h"
#endif
//#include "utils/include/bt_utils.h"
#if SMP_INCLUDED == TRUE
@@ -771,10 +779,98 @@ void smp_process_pairing_public_key(tSMP_CB *p_cb, tSMP_INT_DATA *p_data)
}
/* In order to prevent the x and y coordinates of the public key from being modified,
we need to check whether the x and y coordinates are on the given elliptic curve. */
#if (SMP_CRYPTO_MBEDTLS == TRUE)
{
/*
* mbedTLS validates the public key using mbedtls_ecp_check_pubkey.
*/
mbedtls_ecp_group grp = {0};
mbedtls_ecp_point pt = {0};
int rc;
UINT8 pub_be[BT_OCTET32_LEN + BT_OCTET32_LEN + 1]; /* 0x04 || X (32 bytes) || Y (32 bytes) */
mbedtls_ecp_group_init(&grp);
mbedtls_ecp_point_init(&pt);
/* Load the group */
rc = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1);
if (rc != 0) {
SMP_TRACE_ERROR("%s, Invalid Public key. mbedtls_ecp_group_load failed: %d\n", __func__, rc);
mbedtls_ecp_point_free(&pt);
mbedtls_ecp_group_free(&grp);
reason = SMP_INVALID_PARAMETERS;
smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &reason);
return;
}
/* Construct peer public key in uncompressed format (0x04 || X || Y) */
pub_be[0] = 0x04;
for (int i = 0; i < BT_OCTET32_LEN; i++) {
pub_be[1 + i] = p_cb->peer_publ_key.x[BT_OCTET32_LEN - 1 - i];
pub_be[33 + i] = p_cb->peer_publ_key.y[BT_OCTET32_LEN - 1 - i];
}
/* Read public key */
rc = mbedtls_ecp_point_read_binary(&grp, &pt, pub_be, sizeof(pub_be));
if (rc != 0) {
SMP_TRACE_ERROR("%s, Invalid Public key. mbedtls_ecp_point_read_binary failed: %d\n", __func__, rc);
mbedtls_ecp_point_free(&pt);
mbedtls_ecp_group_free(&grp);
reason = SMP_INVALID_PARAMETERS;
smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &reason);
return;
}
/* Validate public key - check if it's on the curve */
rc = mbedtls_ecp_check_pubkey(&grp, &pt);
if (rc != 0) {
SMP_TRACE_ERROR("%s, Invalid Public key. mbedtls_ecp_check_pubkey failed: %d\n", __func__, rc);
mbedtls_ecp_point_free(&pt);
mbedtls_ecp_group_free(&grp);
reason = SMP_INVALID_PARAMETERS;
smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &reason);
return;
}
/* Key is valid, clean up */
mbedtls_ecp_point_free(&pt);
mbedtls_ecp_group_free(&grp);
}
#elif (SMP_CRYPTO_TINYCRYPT == TRUE)
{
/*
* TinyCrypt validates the public key using uECC_valid_public_key.
* TinyCrypt expects public key in format: X (32 bytes) || Y (32 bytes), no prefix.
*/
UINT8 pub_be[64]; /* TinyCrypt format: X (32 bytes) || Y (32 bytes), no prefix */
/* Convert peer public key from little-endian to big-endian */
/* TinyCrypt format: X (32 bytes) || Y (32 bytes), no prefix */
for (int i = 0; i < BT_OCTET32_LEN; i++) {
pub_be[i] = p_cb->peer_publ_key.x[BT_OCTET32_LEN - 1 - i];
pub_be[BT_OCTET32_LEN + i] = p_cb->peer_publ_key.y[BT_OCTET32_LEN - 1 - i];
}
/* Validate public key - TinyCrypt will check if it's on the curve */
/* uECC_valid_public_key returns 0 if valid, negative value if invalid */
if (uECC_valid_public_key(pub_be, uECC_secp256r1()) < 0) {
SMP_TRACE_ERROR("%s, Invalid Public key. uECC_valid_public_key failed\n", __func__);
reason = SMP_INVALID_PARAMETERS;
smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &reason);
memset(pub_be, 0, sizeof(pub_be));
return;
}
/* Clear sensitive data from stack */
memset(pub_be, 0, sizeof(pub_be));
}
#else
if (!ECC_CheckPointIsInElliCur_P256((Point *)&p_cb->peer_publ_key)) {
SMP_TRACE_ERROR("%s, Invalid Public key.", __func__);
smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &reason);
return;
}
#endif /* SMP_CRYPTO_MBEDTLS */
p_cb->flags |= SMP_PAIR_FLAG_HAVE_PEER_PUBL_KEY;
smp_wait_for_both_public_keys(p_cb, NULL);
@@ -35,7 +35,9 @@
#include "stack/hcimsgs.h"
#include "stack/btu.h"
#if (SMP_CRYPTO_STACK_NATIVE == TRUE)
#include "p_256_ecc_pp.h"
#endif
#include "osi/allocator.h"
/*******************************************************************************
@@ -51,12 +53,16 @@ void SMP_Init(void)
{
#if SMP_DYNAMIC_MEMORY
smp_cb_ptr = (tSMP_CB *)osi_malloc(sizeof(tSMP_CB));
#if (SMP_CRYPTO_STACK_NATIVE == TRUE)
curve_ptr = (elliptic_curve_t *)osi_malloc(sizeof(elliptic_curve_t));
curve_p256_ptr = (elliptic_curve_t *)osi_malloc(sizeof(elliptic_curve_t));
#endif
#endif
memset(&smp_cb, 0, sizeof(tSMP_CB));
#if (SMP_CRYPTO_STACK_NATIVE == TRUE)
memset(&curve, 0, sizeof(elliptic_curve_t));
memset(&curve_p256, 0, sizeof(elliptic_curve_t));
#endif
#if defined(SMP_INITIAL_TRACE_LEVEL)
smp_cb.trace_level = SMP_INITIAL_TRACE_LEVEL;
@@ -66,8 +72,10 @@ void SMP_Init(void)
SMP_TRACE_EVENT ("%s", __FUNCTION__);
smp_l2cap_if_init();
#if (SMP_CRYPTO_STACK_NATIVE == TRUE)
/* initialization of P-256 parameters */
p_256_init_curve(KEY_LENGTH_DWORDS_P256);
#endif
}
void SMP_Free(void)
@@ -75,8 +83,10 @@ void SMP_Free(void)
memset(&smp_cb, 0, sizeof(tSMP_CB));
#if SMP_DYNAMIC_MEMORY
FREE_AND_RESET(smp_cb_ptr);
#if (SMP_CRYPTO_STACK_NATIVE == TRUE)
FREE_AND_RESET(curve_ptr);
FREE_AND_RESET(curve_p256_ptr);
#endif
#endif /* #if SMP_DYNAMIC_MEMORY */
}
+195 -28
View File
@@ -32,7 +32,16 @@
#include "stack/btm_ble_api.h"
#include "smp_int.h"
#include "stack/hcimsgs.h"
#if (SMP_CRYPTO_MBEDTLS == TRUE)
#include "mbedtls/cipher.h"
#include "mbedtls/cmac.h"
#elif (SMP_CRYPTO_TINYCRYPT == TRUE)
#include "tinycrypt/aes.h"
#include "tinycrypt/cmac_mode.h"
#include "tinycrypt/constants.h"
#endif
#if (SMP_CRYPTO_STACK_NATIVE == TRUE)
typedef struct {
UINT8 *text;
UINT16 len;
@@ -46,6 +55,7 @@ const BT_OCTET16 const_Rb = {
0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */
void print128(BT_OCTET16 x, const UINT8 *key_name)
{
@@ -75,6 +85,7 @@ void print128(BT_OCTET16 x, const UINT8 *key_name)
** Returns void
**
*******************************************************************************/
#if (SMP_CRYPTO_STACK_NATIVE == TRUE)
static void padding ( BT_OCTET16 dest, UINT8 length )
{
UINT8 i, *p = dest;
@@ -83,6 +94,7 @@ static void padding ( BT_OCTET16 dest, UINT8 length )
p[BT_OCTET16_LEN - i - 1] = ( i == length ) ? 0x80 : 0;
}
}
/*******************************************************************************
**
** Function leftshift_onebit
@@ -104,6 +116,8 @@ static void leftshift_onebit(UINT8 *input, UINT8 *output)
}
return;
}
#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */
/*******************************************************************************
**
** Function cmac_aes_cleanup
@@ -113,6 +127,7 @@ static void leftshift_onebit(UINT8 *input, UINT8 *output)
** Returns void
**
*******************************************************************************/
#if (SMP_CRYPTO_STACK_NATIVE == TRUE)
static void cmac_aes_cleanup(void)
{
if (cmac_cb.text != NULL) {
@@ -167,11 +182,12 @@ static BOOLEAN cmac_aes_k_calculate(BT_OCTET16 key, UINT8 *p_signature, UINT16 t
return FALSE;
}
}
/*******************************************************************************
**
** Function cmac_prepare_last_block
**
** Description This function proceeed to prepare the last block of message
** Description This function proceed to prepare the last block of message
** Mn depending on the size of the message.
**
** Returns void
@@ -197,6 +213,8 @@ static void cmac_prepare_last_block (BT_OCTET16 k1, BT_OCTET16 k2)
smp_xor_128(&cmac_cb.text[0], k2);
}
}
#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */
/*******************************************************************************
**
** Function cmac_subkey_cont
@@ -206,6 +224,7 @@ static void cmac_prepare_last_block (BT_OCTET16 k1, BT_OCTET16 k2)
** Returns void
**
*******************************************************************************/
#if (SMP_CRYPTO_STACK_NATIVE == TRUE)
static void cmac_subkey_cont(tSMP_ENC *p)
{
UINT8 k1[BT_OCTET16_LEN], k2[BT_OCTET16_LEN];
@@ -262,6 +281,8 @@ static BOOLEAN cmac_generate_subkey(BT_OCTET16 key)
return ret;
}
#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */
/*******************************************************************************
**
** Function aes_cipher_msg_auth_code
@@ -271,7 +292,7 @@ static BOOLEAN cmac_generate_subkey(BT_OCTET16 key)
** Parameters key - CMAC key in little endian order, expect SRK when used by SMP.
** input - text to be signed in little endian byte order.
** length - length of the input in byte.
** tlen - lenth of mac desired
** tlen - length of mac desired
** p_signature - data pointer to where signed data to be stored, tlen long.
**
** Returns FALSE if out of resources, TRUE in other cases.
@@ -280,43 +301,189 @@ static BOOLEAN cmac_generate_subkey(BT_OCTET16 key)
BOOLEAN aes_cipher_msg_auth_code(BT_OCTET16 key, UINT8 *input, UINT16 length,
UINT16 tlen, UINT8 *p_signature)
{
UINT16 len, diff;
UINT16 n = (length + BT_OCTET16_LEN - 1) / BT_OCTET16_LEN; /* n is number of rounds */
BOOLEAN ret = FALSE;
SMP_TRACE_EVENT ("%s", __func__);
if (n == 0) {
n = 1;
#if (SMP_CRYPTO_MBEDTLS == TRUE)
{
/*
* mbedTLS CMAC implementation.
* Bluedroid and mbedTLS both use little-endian, so no byte order conversion needed.
*/
mbedtls_cipher_context_t ctx = {0};
const mbedtls_cipher_info_t *cipher_info;
int rc;
SMP_TRACE_DEBUG("AES128_CMAC (mbedTLS) started, length = %d", length);
mbedtls_cipher_init(&ctx);
cipher_info = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB);
if (cipher_info == NULL) {
SMP_TRACE_ERROR("mbedtls_cipher_info_from_type failed");
mbedtls_cipher_free(&ctx);
return FALSE;
}
rc = mbedtls_cipher_setup(&ctx, cipher_info);
if (rc != 0) {
SMP_TRACE_ERROR("mbedtls_cipher_setup failed: %d", rc);
mbedtls_cipher_free(&ctx);
return FALSE;
}
rc = mbedtls_cipher_cmac_starts(&ctx, key, 128);
if (rc != 0) {
SMP_TRACE_ERROR("mbedtls_cipher_cmac_starts failed: %d", rc);
mbedtls_cipher_free(&ctx);
return FALSE;
}
if (length > 0 && input != NULL) {
rc = mbedtls_cipher_cmac_update(&ctx, input, length);
if (rc != 0) {
SMP_TRACE_ERROR("mbedtls_cipher_cmac_update failed: %d", rc);
mbedtls_cipher_free(&ctx);
return FALSE;
}
}
UINT8 mac[BT_OCTET16_LEN];
rc = mbedtls_cipher_cmac_finish(&ctx, mac);
mbedtls_cipher_free(&ctx);
if (rc != 0) {
SMP_TRACE_ERROR("mbedtls_cipher_cmac_finish failed: %d", rc);
/* Clear sensitive data from stack */
memset(mac, 0, sizeof(mac));
return FALSE;
}
/* Truncate to tlen bytes */
for (UINT16 i = 0; i < tlen && i < BT_OCTET16_LEN; i++) {
p_signature[i] = mac[i];
}
/* Clear sensitive data from stack */
memset(mac, 0, sizeof(mac));
ret = TRUE;
}
len = n * BT_OCTET16_LEN;
#elif (SMP_CRYPTO_TINYCRYPT == TRUE)
{
/*
* TinyCrypt CMAC implementation.
* Bluedroid uses little-endian, TinyCrypt uses big-endian.
* We reverse the key and input, then reverse the output.
*/
struct tc_aes_key_sched_struct sched;
struct tc_cmac_struct state;
UINT8 key_be[BT_OCTET16_LEN];
UINT8 *input_be = NULL;
UINT8 mac_be[BT_OCTET16_LEN];
SMP_TRACE_DEBUG("AES128_CMAC started, allocate buffer size = %d", len);
/* allocate a memory space of multiple of 16 bytes to hold text */
if ((cmac_cb.text = (UINT8 *)osi_malloc(len)) != NULL) {
cmac_cb.round = n;
SMP_TRACE_DEBUG("AES128_CMAC (TinyCrypt) started, length = %d", length);
memset(cmac_cb.text, 0, len);
diff = len - length;
/* Convert key from little-endian to big-endian */
for (int i = 0; i < BT_OCTET16_LEN; i++) {
key_be[i] = key[BT_OCTET16_LEN - 1 - i];
}
if (input != NULL && length > 0) {
memcpy(&cmac_cb.text[diff] , input, (int)length);
cmac_cb.len = length;
/* Setup CMAC */
if (tc_cmac_setup(&state, key_be, &sched) == TC_CRYPTO_FAIL) {
SMP_TRACE_ERROR("tc_cmac_setup failed");
memset(key_be, 0, sizeof(key_be));
memset(&sched, 0, sizeof(sched));
return FALSE;
}
/* Allocate and convert input from little-endian to big-endian */
if (length > 0) {
input_be = (UINT8 *)osi_malloc(length);
if (input_be == NULL) {
SMP_TRACE_ERROR("No resources for input_be");
tc_cmac_erase(&state);
memset(key_be, 0, sizeof(key_be));
memset(&sched, 0, sizeof(sched));
return FALSE;
}
for (UINT16 i = 0; i < length; i++) {
input_be[i] = input[length - 1 - i];
}
/* Update CMAC with input data */
if (tc_cmac_update(&state, input_be, length) == TC_CRYPTO_FAIL) {
SMP_TRACE_ERROR("tc_cmac_update failed");
osi_free(input_be);
tc_cmac_erase(&state);
memset(key_be, 0, sizeof(key_be));
memset(&sched, 0, sizeof(sched));
return FALSE;
}
osi_free(input_be);
}
/* Finalize CMAC */
if (tc_cmac_final(mac_be, &state) == TC_CRYPTO_FAIL) {
SMP_TRACE_ERROR("tc_cmac_final failed");
tc_cmac_erase(&state);
memset(key_be, 0, sizeof(key_be));
memset(&sched, 0, sizeof(sched));
return FALSE;
}
/* Convert MAC from big-endian to little-endian and truncate to tlen bytes */
for (UINT16 i = 0; i < tlen && i < BT_OCTET16_LEN; i++) {
p_signature[i] = mac_be[BT_OCTET16_LEN - 1 - i];
}
/* Clear sensitive data from stack */
tc_cmac_erase(&state);
memset(key_be, 0, sizeof(key_be));
memset(mac_be, 0, sizeof(mac_be));
memset(&sched, 0, sizeof(sched));
ret = TRUE;
}
#else
{
UINT16 len, diff;
UINT16 n = (length + BT_OCTET16_LEN - 1) / BT_OCTET16_LEN;
if (n == 0) {
n = 1;
}
len = n * BT_OCTET16_LEN;
SMP_TRACE_DEBUG("AES128_CMAC started, allocate buffer size = %d", len);
/* allocate a memory space of multiple of 16 bytes to hold text */
if ((cmac_cb.text = (UINT8 *)osi_malloc(len)) != NULL) {
cmac_cb.round = n;
memset(cmac_cb.text, 0, len);
diff = len - length;
if (input != NULL && length > 0) {
memcpy(&cmac_cb.text[diff] , input, (int)length);
cmac_cb.len = length;
} else {
cmac_cb.len = 0;
}
/* prepare calculation for subkey s and last block of data */
if (cmac_generate_subkey(key)) {
/* start calculation */
ret = cmac_aes_k_calculate(key, p_signature, tlen);
}
/* clean up */
cmac_aes_cleanup();
} else {
cmac_cb.len = 0;
ret = FALSE;
SMP_TRACE_ERROR("No resources");
}
/* prepare calculation for subkey s and last block of data */
if (cmac_generate_subkey(key)) {
/* start calculation */
ret = cmac_aes_k_calculate(key, p_signature, tlen);
}
/* clean up */
cmac_aes_cleanup();
} else {
ret = FALSE;
SMP_TRACE_ERROR("No resources");
}
#endif /* SMP_CRYPTO_MBEDTLS */
return ret;
}
+305 -13
View File
@@ -34,8 +34,29 @@
#include "btm_int.h"
#include "btm_ble_int.h"
#include "stack/hcimsgs.h"
#if (SMP_CRYPTO_MBEDTLS == TRUE)
#include "mbedtls/aes.h"
#include "mbedtls/ecdh.h"
#include "mbedtls/ecp.h"
#include "esp_random.h"
/* Random number generator function for mbedTLS ECP operations */
static int smp_mbedtls_rng(void *ctx, unsigned char *output, size_t len)
{
(void)ctx; /* Unused parameter */
esp_fill_random(output, len);
return 0;
}
#elif (SMP_CRYPTO_TINYCRYPT == TRUE)
#include "tinycrypt/aes.h"
#include "tinycrypt/cmac_mode.h"
#include "tinycrypt/ecc_dh.h"
#include "tinycrypt/ecc.h"
#include "tinycrypt/constants.h"
#else
#include "aes.h"
#include "p_256_ecc_pp.h"
#endif /* SMP_CRYPTO_MBEDTLS */
#include "device/controller.h"
#ifndef SMP_MAX_ENC_REPEAT
@@ -159,12 +180,11 @@ BOOLEAN smp_encrypt_data (UINT8 *key, UINT8 key_len,
UINT8 *plain_text, UINT8 pt_len,
tSMP_ENC *p_out)
{
aes_context ctx;
UINT8 *p_start = NULL;
UINT8 *p = NULL;
UINT8 *p_rev_data = NULL; /* input data in big endilan format */
UINT8 *p_rev_key = NULL; /* input key in big endilan format */
UINT8 *p_rev_output = NULL; /* encrypted output in big endilan format */
UINT8 *p_rev_data = NULL; /* input data in big endian format */
UINT8 *p_rev_key = NULL; /* input key in big endian format */
UINT8 *p_rev_output = NULL; /* encrypted output in big endian format */
SMP_TRACE_DEBUG ("%s\n", __func__);
if ( (p_out == NULL ) || (key_len != SMP_ENCRYT_KEY_SIZE) ) {
@@ -194,8 +214,64 @@ BOOLEAN smp_encrypt_data (UINT8 *key, UINT8 key_len,
smp_debug_print_nbyte_little_endian(p_start, (const UINT8 *)"Plain text", SMP_ENCRYT_DATA_SIZE);
#endif
p_rev_output = p;
aes_set_key(p_rev_key, SMP_ENCRYT_KEY_SIZE, &ctx);
bluedroid_aes_encrypt(p_rev_data, p, &ctx); /* outputs in byte 48 to byte 63 */
#if (SMP_CRYPTO_MBEDTLS == TRUE)
{
mbedtls_aes_context ctx = {0};
int rc;
mbedtls_aes_init(&ctx);
rc = mbedtls_aes_setkey_enc(&ctx, p_rev_key, 128);
if (rc != 0) {
SMP_TRACE_ERROR("%s mbedtls_aes_setkey_enc failed: %d\n", __func__, rc);
mbedtls_aes_free(&ctx);
/* Clear sensitive data before freeing */
memset(p_start, 0, SMP_ENCRYT_DATA_SIZE * 4);
osi_free(p_start);
return FALSE;
}
rc = mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, p_rev_data, p_rev_output);
mbedtls_aes_free(&ctx);
if (rc != 0) {
SMP_TRACE_ERROR("%s mbedtls_aes_crypt_ecb failed: %d\n", __func__, rc);
/* Clear sensitive data before freeing */
memset(p_start, 0, SMP_ENCRYT_DATA_SIZE * 4);
osi_free(p_start);
return FALSE;
}
}
#elif (SMP_CRYPTO_TINYCRYPT == TRUE)
{
struct tc_aes_key_sched_struct sched;
/* TinyCrypt expects big-endian key and data */
if (tc_aes128_set_encrypt_key(&sched, p_rev_key) == TC_CRYPTO_FAIL) {
SMP_TRACE_ERROR("%s tc_aes128_set_encrypt_key failed\n", __func__);
memset(&sched, 0, sizeof(sched));
osi_free(p_start);
return FALSE;
}
if (tc_aes_encrypt(p_rev_output, p_rev_data, &sched) == TC_CRYPTO_FAIL) {
SMP_TRACE_ERROR("%s tc_aes_encrypt failed\n", __func__);
memset(&sched, 0, sizeof(sched));
osi_free(p_start);
return FALSE;
}
/* Clear sensitive data from key schedule */
memset(&sched, 0, sizeof(sched));
}
#else
{
aes_context ctx;
aes_set_key(p_rev_key, SMP_ENCRYT_KEY_SIZE, &ctx);
bluedroid_aes_encrypt(p_rev_data, p_rev_output, &ctx); /* outputs in byte 48 to byte 63 */
}
#endif /* SMP_CRYPTO_MBEDTLS */
p = p_out->param_buf;
REVERSE_ARRAY_TO_STREAM (p, p_rev_output, SMP_ENCRYT_DATA_SIZE);
@@ -207,6 +283,8 @@ BOOLEAN smp_encrypt_data (UINT8 *key, UINT8 key_len,
p_out->status = HCI_SUCCESS;
p_out->opcode = HCI_BLE_ENCRYPT;
/* Clear sensitive data (including key at byte 32-47) before freeing */
memset(p_start, 0, SMP_ENCRYT_DATA_SIZE * 4);
osi_free(p_start);
return TRUE;
@@ -1118,8 +1196,6 @@ void smp_continue_private_key_creation (tSMP_CB *p_cb, tBTM_RAND_ENC *p)
*******************************************************************************/
void smp_process_private_key(tSMP_CB *p_cb)
{
Point public_key;
BT_OCTET32 private_key;
tSMP_LOC_OOB_DATA *p_loc_oob = &p_cb->sc_oob_data.loc_oob_data;
SMP_TRACE_DEBUG ("%s", __FUNCTION__);
@@ -1131,10 +1207,101 @@ void smp_process_private_key(tSMP_CB *p_cb)
memcpy(p_cb->loc_publ_key.y, p_loc_oob->publ_key_used.y, BT_OCTET32_LEN);
memcpy(p_cb->local_random, p_loc_oob->randomizer, BT_OCTET16_LEN);
} else {
#if (SMP_CRYPTO_MBEDTLS == TRUE)
mbedtls_ecp_keypair keypair = {0};
int rc;
size_t olen;
mbedtls_ecp_keypair_init(&keypair);
/* Load the group */
rc = mbedtls_ecp_group_load(&keypair.MBEDTLS_PRIVATE(grp), MBEDTLS_ECP_DP_SECP256R1);
if (rc != 0) {
SMP_TRACE_ERROR("%s mbedtls_ecp_group_load failed: %d\n", __FUNCTION__, rc);
goto mbedtls_pubkey_cleanup;
}
/* Import private key (little-endian) */
rc = mbedtls_mpi_read_binary(&keypair.MBEDTLS_PRIVATE(d), p_cb->private_key, BT_OCTET32_LEN);
if (rc != 0) {
SMP_TRACE_ERROR("%s mbedtls_mpi_read_binary failed: %d\n", __FUNCTION__, rc);
goto mbedtls_pubkey_cleanup;
}
/* Validate private key */
rc = mbedtls_ecp_check_privkey(&keypair.MBEDTLS_PRIVATE(grp), &keypair.MBEDTLS_PRIVATE(d));
if (rc != 0) {
SMP_TRACE_ERROR("%s mbedtls_ecp_check_privkey failed: %d\n", __FUNCTION__, rc);
goto mbedtls_pubkey_cleanup;
}
/* Compute public key from private key */
/* mbedtls_ecp_keypair_calc_public requires a non-NULL RNG function for side-channel protection */
rc = mbedtls_ecp_keypair_calc_public(&keypair, smp_mbedtls_rng, NULL);
if (rc != 0) {
SMP_TRACE_ERROR("%s mbedtls_ecp_keypair_calc_public failed: %d\n", __FUNCTION__, rc);
goto mbedtls_pubkey_cleanup;
}
/* Export public key in uncompressed format: 0x04 || X || Y */
UINT8 pub_be[BT_OCTET32_LEN + BT_OCTET32_LEN + 1];
rc = mbedtls_ecp_point_write_binary(&keypair.MBEDTLS_PRIVATE(grp), &keypair.MBEDTLS_PRIVATE(Q),
MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, pub_be, sizeof(pub_be));
if (rc != 0 || olen != sizeof(pub_be)) {
SMP_TRACE_ERROR("%s mbedtls_ecp_point_write_binary failed: %d\n", __FUNCTION__, rc);
goto mbedtls_pubkey_cleanup;
}
/* Convert X and Y from big-endian to little-endian */
/* pub_be: 0x04 || X (32 bytes) || Y (32 bytes) */
for (int i = 0; i < BT_OCTET32_LEN; i++) {
p_cb->loc_publ_key.x[i] = pub_be[1 + BT_OCTET32_LEN - 1 - i];
p_cb->loc_publ_key.y[i] = pub_be[33 + BT_OCTET32_LEN - 1 - i];
}
mbedtls_pubkey_cleanup:
/* Clear sensitive data - mbedtls_ecp_keypair_free will zero the private key */
mbedtls_ecp_keypair_free(&keypair);
/* Note: pub_be contains public key data, no need to clear */
#elif (SMP_CRYPTO_TINYCRYPT == TRUE)
{
UINT8 pub_key[64]; /* TinyCrypt format: X (32 bytes) || Y (32 bytes), no prefix */
UINT8 priv_be[BT_OCTET32_LEN];
/* Convert private key from little-endian to big-endian */
for (int i = 0; i < BT_OCTET32_LEN; i++) {
priv_be[i] = p_cb->private_key[BT_OCTET32_LEN - 1 - i];
}
/* Compute public key from private key */
/* uECC_compute_public_key returns 1 if successful, 0 if failed */
if (uECC_compute_public_key(priv_be, pub_key, uECC_secp256r1()) != TC_CRYPTO_SUCCESS) {
SMP_TRACE_ERROR("%s uECC_compute_public_key failed\n", __FUNCTION__);
memset(priv_be, 0, sizeof(priv_be));
memset(pub_key, 0, sizeof(pub_key));
return;
}
/* Convert X and Y from big-endian to little-endian */
/* TinyCrypt format: X (32 bytes) || Y (32 bytes) */
for (int i = 0; i < BT_OCTET32_LEN; i++) {
p_cb->loc_publ_key.x[i] = pub_key[BT_OCTET32_LEN - 1 - i];
p_cb->loc_publ_key.y[i] = pub_key[BT_OCTET32_LEN + BT_OCTET32_LEN - 1 - i];
}
/* Clear sensitive data from stack */
memset(priv_be, 0, sizeof(priv_be));
memset(pub_key, 0, sizeof(pub_key));
}
#else
Point public_key;
BT_OCTET32 private_key;
memcpy(private_key, p_cb->private_key, BT_OCTET32_LEN);
ECC_PointMult(&public_key, &(curve_p256.G), (DWORD *) private_key, KEY_LENGTH_DWORDS_P256);
memcpy(p_cb->loc_publ_key.x, public_key.x, BT_OCTET32_LEN);
memcpy(p_cb->loc_publ_key.y, public_key.y, BT_OCTET32_LEN);
#endif /* SMP_CRYPTO_MBEDTLS */
}
smp_debug_print_nbyte_little_endian (p_cb->private_key, (const UINT8 *)"private",
@@ -1161,11 +1328,137 @@ void smp_process_private_key(tSMP_CB *p_cb)
*******************************************************************************/
void smp_compute_dhkey (tSMP_CB *p_cb)
{
SMP_TRACE_DEBUG ("%s\n", __FUNCTION__);
#if (SMP_CRYPTO_MBEDTLS == TRUE)
mbedtls_ecp_group grp = {0};
mbedtls_ecp_point Q = {0};
mbedtls_mpi d = {0};
mbedtls_mpi z = {0};
int rc;
UINT8 peer_pub_be[BT_OCTET32_LEN + BT_OCTET32_LEN + 1]; /* 0x04 || X (32 bytes) || Y (32 bytes) */
mbedtls_ecp_group_init(&grp);
mbedtls_ecp_point_init(&Q);
mbedtls_mpi_init(&d);
mbedtls_mpi_init(&z);
/* Load the group */
rc = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1);
if (rc != 0) {
SMP_TRACE_ERROR("%s mbedtls_ecp_group_load failed: %d\n", __FUNCTION__, rc);
goto mbedtls_dhkey_cleanup;
}
/* Import private key (little-endian) */
rc = mbedtls_mpi_read_binary(&d, p_cb->private_key, BT_OCTET32_LEN);
if (rc != 0) {
SMP_TRACE_ERROR("%s mbedtls_mpi_read_binary failed: %d\n", __FUNCTION__, rc);
goto mbedtls_dhkey_cleanup;
}
/* Construct peer public key in uncompressed format: 0x04 || X || Y */
peer_pub_be[0] = 0x04;
for (int i = 0; i < BT_OCTET32_LEN; i++) {
peer_pub_be[1 + i] = p_cb->peer_publ_key.x[BT_OCTET32_LEN - 1 - i];
peer_pub_be[33 + i] = p_cb->peer_publ_key.y[BT_OCTET32_LEN - 1 - i];
}
/* Read peer public key */
rc = mbedtls_ecp_point_read_binary(&grp, &Q, peer_pub_be, sizeof(peer_pub_be));
if (rc != 0) {
SMP_TRACE_ERROR("%s mbedtls_ecp_point_read_binary failed: %d\n", __FUNCTION__, rc);
goto mbedtls_dhkey_cleanup;
}
/* Validate peer public key */
rc = mbedtls_ecp_check_pubkey(&grp, &Q);
if (rc != 0) {
SMP_TRACE_ERROR("%s mbedtls_ecp_check_pubkey failed: %d\n", __FUNCTION__, rc);
goto mbedtls_dhkey_cleanup;
}
/* Compute ECDH shared secret */
/* mbedtls_ecdh_compute_shared requires a non-NULL RNG function for side-channel protection */
rc = mbedtls_ecdh_compute_shared(&grp, &z, &Q, &d, smp_mbedtls_rng, NULL);
if (rc != 0) {
SMP_TRACE_ERROR("%s mbedtls_ecdh_compute_shared failed: %d\n", __FUNCTION__, rc);
goto mbedtls_dhkey_cleanup;
}
/* Export shared secret (big-endian) and convert to little-endian for DHKey */
UINT8 shared_secret[BT_OCTET32_LEN];
rc = mbedtls_mpi_write_binary(&z, shared_secret, BT_OCTET32_LEN);
if (rc != 0) {
SMP_TRACE_ERROR("%s mbedtls_mpi_write_binary failed: %d\n", __FUNCTION__, rc);
goto mbedtls_dhkey_cleanup;
}
/* Convert shared secret from big-endian to little-endian for DHKey */
for (int i = 0; i < BT_OCTET32_LEN; i++) {
p_cb->dhkey[i] = shared_secret[BT_OCTET32_LEN - 1 - i];
}
mbedtls_dhkey_cleanup:
/* Clear sensitive data - mbedtls_mpi_free will zero the memory */
mbedtls_mpi_free(&z);
mbedtls_mpi_free(&d);
mbedtls_ecp_point_free(&Q);
mbedtls_ecp_group_free(&grp);
/* Clear sensitive data from stack */
memset(shared_secret, 0, sizeof(shared_secret));
/* Note: peer_pub_be contains public key data, no need to clear */
#elif (SMP_CRYPTO_TINYCRYPT == TRUE)
{
UINT8 priv_be[BT_OCTET32_LEN];
UINT8 peer_pub_be[64]; /* TinyCrypt format: X (32 bytes) || Y (32 bytes), no prefix */
UINT8 shared_secret[BT_OCTET32_LEN];
/* Convert private key from little-endian to big-endian */
for (int i = 0; i < BT_OCTET32_LEN; i++) {
priv_be[i] = p_cb->private_key[BT_OCTET32_LEN - 1 - i];
}
/* Convert peer public key from little-endian to big-endian */
/* TinyCrypt format: X (32 bytes) || Y (32 bytes), no prefix */
for (int i = 0; i < BT_OCTET32_LEN; i++) {
peer_pub_be[i] = p_cb->peer_publ_key.x[BT_OCTET32_LEN - 1 - i];
peer_pub_be[BT_OCTET32_LEN + i] = p_cb->peer_publ_key.y[BT_OCTET32_LEN - 1 - i];
}
/* Validate peer public key */
/* uECC_valid_public_key returns 0 if valid, negative value if invalid */
if (uECC_valid_public_key(peer_pub_be, uECC_secp256r1()) < 0) {
SMP_TRACE_ERROR("%s Invalid peer public key\n", __FUNCTION__);
memset(priv_be, 0, sizeof(priv_be));
memset(peer_pub_be, 0, sizeof(peer_pub_be));
return;
}
/* Compute ECDH shared secret */
/* uECC_shared_secret returns TC_CRYPTO_SUCCESS (1) if successful, TC_CRYPTO_FAIL (0) if failed */
if (uECC_shared_secret(peer_pub_be, priv_be, shared_secret, uECC_secp256r1()) != TC_CRYPTO_SUCCESS) {
SMP_TRACE_ERROR("%s uECC_shared_secret failed\n", __FUNCTION__);
memset(priv_be, 0, sizeof(priv_be));
memset(peer_pub_be, 0, sizeof(peer_pub_be));
memset(shared_secret, 0, sizeof(shared_secret));
return;
}
/* Convert shared secret from big-endian to little-endian for DHKey */
for (int i = 0; i < BT_OCTET32_LEN; i++) {
p_cb->dhkey[i] = shared_secret[BT_OCTET32_LEN - 1 - i];
}
/* Clear sensitive data from stack */
memset(priv_be, 0, sizeof(priv_be));
memset(peer_pub_be, 0, sizeof(peer_pub_be));
memset(shared_secret, 0, sizeof(shared_secret));
}
#else
Point peer_publ_key, new_publ_key;
BT_OCTET32 private_key;
SMP_TRACE_DEBUG ("%s\n", __FUNCTION__);
memcpy(private_key, p_cb->private_key, BT_OCTET32_LEN);
memcpy(peer_publ_key.x, p_cb->peer_publ_key.x, BT_OCTET32_LEN);
memcpy(peer_publ_key.y, p_cb->peer_publ_key.y, BT_OCTET32_LEN);
@@ -1173,8 +1466,9 @@ void smp_compute_dhkey (tSMP_CB *p_cb)
ECC_PointMult(&new_publ_key, &peer_publ_key, (DWORD *) private_key, KEY_LENGTH_DWORDS_P256);
memcpy(p_cb->dhkey, new_publ_key.x, BT_OCTET32_LEN);
#endif /* SMP_CRYPTO_MBEDTLS */
smp_debug_print_nbyte_little_endian (p_cb->dhkey, (const UINT8 *)"Old DHKey",
smp_debug_print_nbyte_little_endian (p_cb->dhkey, (const UINT8 *)"DHKey",
BT_OCTET32_LEN);
smp_debug_print_nbyte_little_endian (p_cb->private_key, (const UINT8 *)"private",
@@ -1183,8 +1477,6 @@ void smp_compute_dhkey (tSMP_CB *p_cb)
BT_OCTET32_LEN);
smp_debug_print_nbyte_little_endian (p_cb->peer_publ_key.y, (const UINT8 *)"rem public(y)",
BT_OCTET32_LEN);
smp_debug_print_nbyte_little_endian (p_cb->dhkey, (const UINT8 *)"Reverted DHKey",
BT_OCTET32_LEN);
}
/*******************************************************************************
@@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(enc_adv_data_cent)
@@ -0,0 +1,273 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- |
# BLE Encrypted Advertising Data Central Example (Bluedroid)
This example demonstrates how to receive and decrypt BLE Encrypted Advertising Data (EAD) with Bluedroid stack.
## Overview
This central example works with the `enc_adv_data_prph` peripheral example to demonstrate:
1. Scanning for devices that advertise encrypted data
2. Connecting to read the Key Material characteristic
3. Decrypting advertising data using the obtained key
## Two Operation Modes
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Mode Selection │
├────────────────────────────────┬────────────────────────────────────────────┤
│ Mode 1: With Connection │ Mode 2: No Connection │
│ (Default) │ (Pre-shared Key) │
├────────────────────────────────┼────────────────────────────────────────────┤
│ │ │
│ First scan: │ All scans: │
│ ┌─────────┐ ┌─────────┐ │ ┌─────────┐ ┌─────────┐ │
│ │ Central │══▶│ Periph │ │ │ Central │──▶│ Periph │ │
│ └─────────┘ └─────────┘ │ └─────────┘ └─────────┘ │
│ │ │ │ │ │
│ │ Connect │ │ │ Scan only │
│ │ Read Key │ │ ▼ │
│ │ Disconnect │ │ Use pre-configured │
│ ▼ │ │ key to decrypt │
│ Store key │ │ │ │
│ │ │ │ ▼ │
│ ▼ │ │ ✅ Decrypt immediately │
│ Later scans: │ │ │
│ ┌─────────┐ ┌─────────┐ │ │
│ │ Central │──▶│ Periph │ │ │
│ └─────────┘ └─────────┘ │ │
│ │ │ │
│ │ No connection needed │ │
│ ▼ │ │
│ ✅ Decrypt using stored key │ │
│ │ │
├────────────────────────────────┼────────────────────────────────────────────┤
│ ✓ Secure key exchange │ ✓ No connection latency │
│ ✓ Dynamic key support │ ✓ Simpler implementation │
│ ✗ First-time connection needed │ ✗ Key must be pre-provisioned │
└────────────────────────────────┴────────────────────────────────────────────┘
```
## System Flow Diagram
### Mode 1: With Connection (Default)
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Central Flow - With Connection Mode │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────────┐ │
│ │ Central │ │ Peripheral │ │
│ └────┬────┘ └──────┬──────┘ │
│ │ │ │
│ │ ════════════════ First Encounter ════════════════ │ │
│ │ │ │
│ │ 1. Scan │ │
│ │ ──────────────────────────────────────────────────▶ │ │
│ │ │ │
│ │ 2. Receive Adv (UUID=0x2C01, Encrypted Data) │ │
│ │ ◀────────────────────────────────────────────────── │ │
│ │ │ │
│ │ [No key yet - cannot decrypt] │ │
│ │ │ │
│ │ 3. Connect │ │
│ │ ═══════════════════════════════════════════════════▶│ │
│ │ │ │
│ │ 4. Establish Encrypted Link (Pairing) │ │
│ │ ◀═══════════════════════════════════════════════════│ │
│ │ │ │
│ │ 5. Read Key Material Characteristic (0x2B88) │ │
│ │ ═══════════════════════════════════════════════════▶│ │
│ │ │ │
│ │ 6. Response: [Session Key (16B)] [IV (8B)] │ │
│ │ ◀═══════════════════════════════════════════════════│ │
│ │ │ │
│ │ 7. Store key in memory │ │
│ │ 8. Disconnect │ │
│ │ ═══════════════════════════════════════════════════▶│ │
│ │ │ │
│ │ ════════════════ Later Scans ════════════════════ │ │
│ │ │ │
│ │ 9. Scan │ │
│ │ ──────────────────────────────────────────────────▶ │ │
│ │ │ │
│ │ 10. Receive Encrypted Adv Data │ │
│ │ ◀────────────────────────────────────────────────── │ │
│ │ │ │
│ │ 11. Decrypt using stored key (NO CONNECTION!) │ │
│ │ ┌────────────────────────────────────────┐ │ │
│ │ │ ble_ead_decrypt(session_key, iv, ...) │ │ │
│ │ │ Result: "prph" (decrypted name) │ │ │
│ │ └────────────────────────────────────────┘ │ │
│ │ │ │
│ ▼ ▼ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
### Mode 2: No Connection (Pre-shared Key)
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Central Flow - No Connection Mode │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ Pre-configured Key (same as Peripheral) │ │
│ │ Session Key: 19 6a 0a d1 2a 61 20 1e │ │
│ │ 13 6e 2e d1 12 da a9 57 │ │
│ │ IV: 9E 7a 00 ef b1 7a e7 46 │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────┐ ┌─────────────┐ │
│ │ Central │ │ Peripheral │ │
│ └────┬────┘ └──────┬──────┘ │
│ │ │ │
│ │ 1. Scan (passive) │ │
│ │ ──────────────────────────────────────────────────▶ │ │
│ │ │ │
│ │ 2. Receive Encrypted Adv Data │ │
│ │ ◀────────────────────────────────────────────────── │ │
│ │ │ │
│ │ 3. Immediately decrypt (NO CONNECTION!) │ │
│ │ ┌────────────────────────────────────────┐ │ │
│ │ │ ble_ead_decrypt(pre_shared_key, ...) │ │ │
│ │ │ Result: "prph" (decrypted name) │ │ │
│ │ └────────────────────────────────────────┘ │ │
│ │ │ │
│ ▼ ▼ │
│ │
│ ⚡ No connection overhead - instant decryption! │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
## Decryption Process
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Decryption Process │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Received Encrypted Advertising Data: │
│ ┌───────────────────┬─────────────────────┬─────────────────┐ │
│ │ Randomizer │ Ciphertext │ MIC │ │
│ │ (5 bytes) │ (6 bytes) │ (4 bytes) │ │
│ └─────────┬─────────┴──────────┬──────────┴────────┬────────┘ │
│ │ │ │ │
│ ▼ │ │ │
│ 1. Extract Randomizer │ │ │
│ ┌─────────────────────┐ │ │ │
│ │ XX XX XX XX [D|XX] │ │ │ │
│ └─────────────────────┘ │ │ │
│ │ │ │ │
│ ▼ │ │ │
│ 2. Build Nonce │ │ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Randomizer (5B) + IV from Key Material (8B) = Nonce (13B) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ 3. AES-CCM Decrypt + Verify │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Input: Ciphertext, MIC, Nonce, Session Key, AAD (0xEA) │ │
│ │ Algorithm: AES-CCM-128 Authenticated Decryption │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 4. Output: Decrypted Plaintext │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ [05] [09] [70 72 70 68] │ │
│ │ Len Type 'p' 'r' 'p' 'h' │ │
│ │ │ │
│ │ → Complete Local Name: "prph" │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
## How to Use Example
### Hardware Required
* Two development boards with ESP32/ESP32-C2/ESP32-C3/ESP32-C6/ESP32-H2/ESP32-S3 SoC
* USB cables for power supply and programming
### Setup
1. Flash `enc_adv_data_prph` on one board (peripheral)
2. Flash `enc_adv_data_cent` on another board (central)
### Configure the project
```bash
idf.py set-target <chip_name>
idf.py menuconfig
```
In menuconfig, navigate to:
- `Example Configuration` → `Central Mode`
- `With Connection` - Connect to read key (default)
- `No Connection` - Use pre-shared key
### Build and Flash
```bash
idf.py -p PORT flash monitor
```
(To exit the serial monitor, type `Ctrl-]`.)
### Example Output
**Mode 1 - With Connection (first scan):**
```
I (XXX) ENC_ADV_CENT: Encrypted Advertising Data Central started
I (XXX) ENC_ADV_CENT: Scanning started
I (XXX) ENC_ADV_CENT: Found target device: xx:xx:xx:xx:xx:xx
I (XXX) ENC_ADV_CENT: Connecting to get key material...
I (XXX) ENC_ADV_CENT: Connected, conn_id 0
I (XXX) ENC_ADV_CENT: Authentication success
I (XXX) ENC_ADV_CENT: Key material received:
I (XXX) ENC_ADV_CENT: 19 6a 0a d1 2a 61 20 1e 13 6e 2e d1 12 da a9 57 9e 7a 00 ef b1 7a e7 46
I (XXX) ENC_ADV_CENT: Disconnected
```
**Mode 1 - With Connection (subsequent scans):**
```
I (XXX) ENC_ADV_CENT: Found target device: xx:xx:xx:xx:xx:xx
I (XXX) ENC_ADV_CENT: Have key material, decrypting...
I (XXX) ENC_ADV_CENT: Decryption successful!
I (XXX) ENC_ADV_CENT: Decrypted data:
I (XXX) ENC_ADV_CENT: 05 09 70 72 70 68
I (XXX) ENC_ADV_CENT: Decrypted device name: prph
```
**Mode 2 - No Connection:**
```
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: ✅ Decryption successful (no connection needed!)
I (XXX) ENC_ADV_CENT_SIMPLE: 📛 Decrypted device name: "prph"
```
## Troubleshooting
### Authentication Failed
- Ensure both devices have matching security parameters
- Try erasing NVS on both devices: `idf.py erase_flash`
### Decryption Failed
- Verify the peripheral and central use matching session key and IV
- Check that the encrypted advertising data format is correct
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub.
@@ -0,0 +1,8 @@
if(CONFIG_EXAMPLE_MODE_NO_CONNECTION)
set(MAIN_SRC "enc_adv_data_cent_no_connect.c")
else()
set(MAIN_SRC "enc_adv_data_cent.c")
endif()
idf_component_register(SRCS ${MAIN_SRC} "ble_ead.c"
INCLUDE_DIRS ".")
@@ -0,0 +1,22 @@
menu "Example Configuration"
choice EXAMPLE_MODE
prompt "Central Mode"
default EXAMPLE_MODE_WITH_CONNECTION
help
Select the central operation mode.
config EXAMPLE_MODE_WITH_CONNECTION
bool "With Connection (read key from peripheral)"
help
Connect to peripheral to read Key Material characteristic.
This is the standard way when key is not pre-shared.
config EXAMPLE_MODE_NO_CONNECTION
bool "No Connection (use pre-shared key)"
help
Use pre-configured key to decrypt without connecting.
Key must match the peripheral's key.
endchoice
endmenu
@@ -0,0 +1,394 @@
/*
* SPDX-FileCopyrightText: 2025-2026 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 "mbedtls/ccm.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)
mbedtls_ccm_context ctx = {0};
int ret;
/* Validate inputs */
if (key == NULL || nonce == NULL || ciphertext == NULL) {
ESP_LOGE(TAG, "Invalid input parameters");
return -1;
}
mbedtls_ccm_init(&ctx);
/* Set encryption key */
ret = mbedtls_ccm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, key, BLE_EAD_KEY_SIZE * 8);
if (ret != 0) {
ESP_LOGE(TAG, "mbedtls_ccm_setkey failed: %d", ret);
mbedtls_ccm_free(&ctx);
return -1;
}
/* Encrypt and authenticate */
/* mbedtls_ccm_encrypt_and_tag outputs: ciphertext || tag */
ret = mbedtls_ccm_encrypt_and_tag(&ctx, plaintext_len,
nonce, BLE_EAD_NONCE_SIZE,
aad, aad_len,
plaintext, ciphertext,
ciphertext + plaintext_len, tag_len);
if (ret != 0) {
ESP_LOGE(TAG, "mbedtls_ccm_encrypt_and_tag failed: %d", ret);
mbedtls_ccm_free(&ctx);
return -1;
}
mbedtls_ccm_free(&ctx);
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)
{
#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;
/* 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)
mbedtls_ccm_context ctx = {0};
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;
mbedtls_ccm_init(&ctx);
/* Set decryption key */
ret = mbedtls_ccm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, key, BLE_EAD_KEY_SIZE * 8);
if (ret != 0) {
ESP_LOGE(TAG, "mbedtls_ccm_setkey failed: %d", ret);
mbedtls_ccm_free(&ctx);
return -1;
}
/* Decrypt and verify */
/* mbedtls_ccm_auth_decrypt expects: ciphertext || tag */
/* ciphertext_len here already includes tag length */
ret = mbedtls_ccm_auth_decrypt(&ctx, plaintext_len,
nonce, BLE_EAD_NONCE_SIZE,
aad, aad_len,
ciphertext, plaintext,
ciphertext + plaintext_len, tag_len);
if (ret != 0) {
ESP_LOGE(TAG, "mbedtls_ccm_auth_decrypt failed: %d", ret);
mbedtls_ccm_free(&ctx);
return -1;
}
mbedtls_ccm_free(&ctx);
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)
{
int ret;
uint8_t nonce[BLE_EAD_NONCE_SIZE];
const uint8_t *randomizer;
const uint8_t *ciphertext;
size_t ciphertext_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;
}
/* 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 mbedTLS 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);
return ret;
}
@@ -0,0 +1,95 @@
/*
* 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
* Size must be at least 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);
#ifdef __cplusplus
}
#endif
#endif /* BLE_EAD_H */
@@ -0,0 +1,519 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/**
* @brief BLE Encrypted Advertising Data Central Example
*
* This example demonstrates how to:
* 1. Scan for devices broadcasting encrypted advertising data
* 2. Connect to read Key Material characteristic
* 3. Decrypt the advertising data using the obtained key
*
* Based on Bluetooth Core Specification Version 5.4 - Encrypted Advertising Data
*/
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gattc_api.h"
#include "esp_gatt_defs.h"
#include "esp_bt_main.h"
#include "esp_gatt_common_api.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "ble_ead.h"
#define TAG "ENC_ADV_CENT"
/* Service and characteristic UUIDs */
#define GAP_SERVICE_UUID 0x1800 /* GAP Service UUID */
#define KEY_MATERIAL_CHAR_UUID 0x2B88 /* Key Material Characteristic UUID */
/* Profile configuration */
#define PROFILE_NUM 1
#define PROFILE_APP_ID 0
#define INVALID_HANDLE 0
/* Maximum peers to track */
#define MAX_PEERS 5
/* Peer information structure */
typedef struct {
bool valid;
esp_bd_addr_t addr;
bool key_material_exist;
ble_ead_key_material_t key_material;
} peer_info_t;
static peer_info_t peers[MAX_PEERS] = {0};
/* GATT client state */
static bool is_connected = false;
static bool get_server = false;
static uint16_t conn_id_stored = 0;
static uint16_t service_start_handle = 0;
static uint16_t service_end_handle = 0;
static uint16_t key_material_char_handle = INVALID_HANDLE;
static esp_bd_addr_t current_peer_addr = {0};
/* GATT interface */
static esp_gatt_if_t gattc_if_stored = ESP_GATT_IF_NONE;
/* Scan parameters */
static esp_ble_scan_params_t ble_scan_params = {
.scan_type = BLE_SCAN_TYPE_ACTIVE,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
.scan_interval = 0x50,
.scan_window = 0x30,
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE,
};
/* Forward declarations */
static void start_scan(void);
/**
* @brief Find peer by address
*/
static int find_peer(const esp_bd_addr_t addr)
{
for (int i = 0; i < MAX_PEERS; i++) {
if (peers[i].valid && memcmp(peers[i].addr, addr, sizeof(esp_bd_addr_t)) == 0) {
return i;
}
}
return -1;
}
/**
* @brief Add or update peer
*/
static int add_peer(const esp_bd_addr_t addr)
{
int idx = find_peer(addr);
if (idx >= 0) {
return idx; /* Already exists */
}
/* Find empty slot */
for (int i = 0; i < MAX_PEERS; i++) {
if (!peers[i].valid) {
peers[i].valid = true;
memcpy(peers[i].addr, addr, sizeof(esp_bd_addr_t));
peers[i].key_material_exist = false;
return i;
}
}
return -1; /* No space */
}
/**
* @brief Decrypt encrypted advertising data
*/
static void decrypt_enc_adv_data(const uint8_t *adv_data, uint8_t adv_len, const esp_bd_addr_t addr)
{
int peer_idx = find_peer(addr);
if (peer_idx < 0 || !peers[peer_idx].key_material_exist) {
ESP_LOGW(TAG, "No key material for peer, cannot decrypt");
return;
}
uint8_t offset = 0;
while (offset < adv_len) {
uint8_t len = adv_data[offset];
if (len == 0 || offset + len >= adv_len) {
break;
}
uint8_t type = adv_data[offset + 1];
if (type == ESP_BLE_AD_TYPE_ENC_ADV_DATA) {
/* Found encrypted advertising data */
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) {
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);
int rc = ble_ead_decrypt(
peers[peer_idx].key_material.session_key,
peers[peer_idx].key_material.iv,
enc_data, enc_data_len,
dec_data);
if (rc == 0) {
ESP_LOGI(TAG, "Decryption successful!");
ESP_LOGI(TAG, "Decrypted data:");
ESP_LOG_BUFFER_HEX(TAG, dec_data, dec_len);
/* Parse decrypted advertising structure */
if (dec_len >= 2) {
uint8_t dec_type = dec_data[1];
if (dec_type == ESP_BLE_AD_TYPE_NAME_CMPL || dec_type == ESP_BLE_AD_TYPE_NAME_SHORT) {
char name[32] = {0};
size_t name_len = dec_data[0] - 1;
if (name_len < sizeof(name)) {
memcpy(name, &dec_data[2], name_len);
ESP_LOGI(TAG, "Decrypted device name: %s", name);
}
}
}
} else {
ESP_LOGE(TAG, "Decryption failed: %d", rc);
}
break;
}
offset += len + 1;
}
}
/**
* @brief Check if device advertises GAP service UUID
*/
static bool should_connect(const uint8_t *adv_data, uint8_t adv_len)
{
uint8_t offset = 0;
while (offset < adv_len) {
uint8_t len = adv_data[offset];
if (len == 0 || offset + len >= adv_len) {
break;
}
uint8_t type = adv_data[offset + 1];
if (type == ESP_BLE_AD_TYPE_16SRV_CMPL || type == ESP_BLE_AD_TYPE_16SRV_PART) {
/* Check for GAP service UUID */
for (int i = 0; i < len - 1; i += 2) {
uint16_t uuid = adv_data[offset + 2 + i] | (adv_data[offset + 3 + i] << 8);
if (uuid == GAP_SERVICE_UUID) {
return true;
}
}
}
offset += len + 1;
}
return false;
}
/**
* @brief Start scanning
*/
static void start_scan(void)
{
esp_ble_gap_start_scanning(30); /* Scan for 30 seconds */
}
/**
* @brief GAP event handler
*/
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
ESP_LOGI(TAG, "Scan parameters set");
start_scan();
break;
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
if (param->scan_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(TAG, "Scan start failed: %d", param->scan_start_cmpl.status);
} else {
ESP_LOGI(TAG, "Scanning started");
}
break;
case ESP_GAP_BLE_SCAN_RESULT_EVT: {
esp_ble_gap_cb_param_t *scan_result = param;
if (scan_result->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
uint8_t *adv_data = scan_result->scan_rst.ble_adv;
uint8_t adv_len = scan_result->scan_rst.adv_data_len;
if (should_connect(adv_data, adv_len)) {
ESP_LOGI(TAG, "Found target device: "ESP_BD_ADDR_STR"",
ESP_BD_ADDR_HEX(scan_result->scan_rst.bda));
int peer_idx = find_peer(scan_result->scan_rst.bda);
if (peer_idx >= 0 && peers[peer_idx].key_material_exist) {
/* Already have key, try to decrypt */
ESP_LOGI(TAG, "Have key material, decrypting...");
decrypt_enc_adv_data(adv_data, adv_len, scan_result->scan_rst.bda);
} else {
/* Need to connect and get key */
if (!is_connected) {
ESP_LOGI(TAG, "Connecting to get key material...");
add_peer(scan_result->scan_rst.bda);
memcpy(current_peer_addr, scan_result->scan_rst.bda, sizeof(esp_bd_addr_t));
esp_ble_gap_stop_scanning();
esp_ble_gatt_creat_conn_params_t conn_params = {0};
memcpy(conn_params.remote_bda, scan_result->scan_rst.bda, ESP_BD_ADDR_LEN);
conn_params.remote_addr_type = scan_result->scan_rst.ble_addr_type;
conn_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
conn_params.is_direct = true;
conn_params.is_aux = false;
esp_ble_gattc_enh_open(gattc_if_stored, &conn_params);
}
}
}
} else if (scan_result->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
ESP_LOGI(TAG, "Scan complete");
if (!is_connected) {
start_scan(); /* Restart scanning */
}
}
break;
}
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
ESP_LOGI(TAG, "Scan stopped");
break;
case ESP_GAP_BLE_SEC_REQ_EVT:
ESP_LOGI(TAG, "Security request");
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(TAG, "Authentication success");
} else {
ESP_LOGW(TAG, "Authentication failed: 0x%x", param->ble_security.auth_cmpl.fail_reason);
}
break;
default:
break;
}
}
/**
* @brief GATTC event handler
*/
static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param)
{
switch (event) {
case ESP_GATTC_REG_EVT:
ESP_LOGI(TAG, "GATT client registered, status %d, if %d", param->reg.status, gattc_if);
gattc_if_stored = gattc_if;
esp_ble_gap_set_scan_params(&ble_scan_params);
break;
case ESP_GATTC_CONNECT_EVT:
ESP_LOGI(TAG, "Connected, conn_id %d", param->connect.conn_id);
conn_id_stored = param->connect.conn_id;
is_connected = true;
/* Request MTU exchange */
esp_ble_gattc_send_mtu_req(gattc_if, param->connect.conn_id);
break;
case ESP_GATTC_OPEN_EVT:
if (param->open.status != ESP_GATT_OK) {
ESP_LOGE(TAG, "Open failed: %d", param->open.status);
is_connected = false;
start_scan();
}
break;
case ESP_GATTC_CFG_MTU_EVT:
ESP_LOGI(TAG, "MTU configured: %d", param->cfg_mtu.mtu);
break;
case ESP_GATTC_DIS_SRVC_CMPL_EVT:
ESP_LOGI(TAG, "Service discovery complete");
/* Search for GAP service that contains Key Material characteristic */
ESP_LOGI(TAG, "Searching for GAP service UUID 0x%04X", GAP_SERVICE_UUID);
esp_bt_uuid_t gap_uuid = {
.len = ESP_UUID_LEN_16,
.uuid = {.uuid16 = GAP_SERVICE_UUID},
};
esp_ble_gattc_search_service(gattc_if, param->dis_srvc_cmpl.conn_id, &gap_uuid);
break;
case ESP_GATTC_SEARCH_RES_EVT:
ESP_LOGI(TAG, "Service found, UUID 0x%04X, start_handle %d, end_handle %d",
param->search_res.srvc_id.uuid.uuid.uuid16,
param->search_res.start_handle, param->search_res.end_handle);
if (param->search_res.srvc_id.uuid.len == ESP_UUID_LEN_16 &&
param->search_res.srvc_id.uuid.uuid.uuid16 == GAP_SERVICE_UUID) {
get_server = true;
service_start_handle = param->search_res.start_handle;
service_end_handle = param->search_res.end_handle;
}
break;
case ESP_GATTC_SEARCH_CMPL_EVT:
ESP_LOGI(TAG, "Service search complete");
if (get_server) {
/* Get characteristics */
uint16_t count = 0;
esp_ble_gattc_get_attr_count(gattc_if, conn_id_stored,
ESP_GATT_DB_CHARACTERISTIC,
service_start_handle,
service_end_handle,
INVALID_HANDLE, &count);
if (count > 0) {
esp_gattc_char_elem_t *char_elem = malloc(sizeof(esp_gattc_char_elem_t) * count);
if (char_elem) {
esp_bt_uuid_t km_uuid = {
.len = ESP_UUID_LEN_16,
.uuid = {.uuid16 = KEY_MATERIAL_CHAR_UUID},
};
esp_ble_gattc_get_char_by_uuid(gattc_if, conn_id_stored,
service_start_handle,
service_end_handle,
km_uuid, char_elem, &count);
if (count > 0) {
key_material_char_handle = char_elem[0].char_handle;
ESP_LOGI(TAG, "Key Material characteristic found, handle %d", key_material_char_handle);
/* Read characteristic with encryption requirement
* GATT layer will automatically trigger encryption if needed */
ESP_LOGI(TAG, "Reading key material (will trigger encryption if needed)...");
esp_ble_gattc_read_char(gattc_if, conn_id_stored,
key_material_char_handle, ESP_GATT_AUTH_REQ_NO_MITM);
}
free(char_elem);
}
}
}
break;
case ESP_GATTC_READ_CHAR_EVT:
if (param->read.status == ESP_GATT_OK) {
ESP_LOGI(TAG, "Read characteristic success, handle %d, len %d",
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)) {
/* Store key material */
int peer_idx = find_peer(current_peer_addr);
if (peer_idx >= 0) {
memcpy(&peers[peer_idx].key_material, param->read.value,
sizeof(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));
}
/* Disconnect and resume scanning */
esp_ble_gattc_close(gattc_if, conn_id_stored);
}
} else {
ESP_LOGE(TAG, "Read failed: %d", param->read.status);
}
break;
case ESP_GATTC_DISCONNECT_EVT:
ESP_LOGI(TAG, "Disconnected, reason 0x%02x", param->disconnect.reason);
is_connected = false;
get_server = false;
key_material_char_handle = INVALID_HANDLE;
start_scan();
break;
default:
break;
}
}
void app_main(void)
{
esp_err_t ret;
/* Initialize NVS */
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
/* Release memory for Classic BT */
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
/* Initialize BT controller */
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(TAG, "initialize controller failed: %s", esp_err_to_name(ret));
return;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(TAG, "enable controller failed: %s", esp_err_to_name(ret));
return;
}
/* Initialize Bluedroid */
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
ret = esp_bluedroid_init_with_cfg(&cfg);
if (ret) {
ESP_LOGE(TAG, "init bluetooth failed: %s", esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(TAG, "enable bluetooth failed: %s", esp_err_to_name(ret));
return;
}
/* Register callbacks */
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret) {
ESP_LOGE(TAG, "gap register error: %x", ret);
return;
}
ret = esp_ble_gattc_register_callback(gattc_event_handler);
if (ret) {
ESP_LOGE(TAG, "gattc register error: %x", ret);
return;
}
ret = esp_ble_gattc_app_register(PROFILE_APP_ID);
if (ret) {
ESP_LOGE(TAG, "gattc app register error: %x", ret);
return;
}
/* Set MTU */
esp_ble_gatt_set_local_mtu(500);
/* Configure security parameters
* Using SC (Secure Connections) with bonding, no MITM (since IO_CAP is NONE)
*/
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_BOND; /* SC + Bond, no MITM */
esp_ble_io_cap_t io_cap = 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(auth_req));
esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &io_cap, sizeof(io_cap));
esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(key_size));
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(init_key));
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(rsp_key));
ESP_LOGI(TAG, "Encrypted Advertising Data Central started");
}
@@ -0,0 +1,239 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/**
* @brief BLE Encrypted Advertising Data Central Example - No Connection Version
*
* This simplified example demonstrates decrypting advertising data WITHOUT connecting.
* The key material is pre-configured (same as peripheral).
*
* Use case: When the key is pre-shared or provisioned out-of-band.
*/
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_bt_main.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "ble_ead.h"
#define TAG "ENC_ADV_CENT_SIMPLE"
/* Custom service UUID to identify target device */
#define CUSTOM_SERVICE_UUID 0x2C01
/*
* 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 = {
.session_key = {
0x19, 0x6a, 0x0a, 0xd1, 0x2a, 0x61, 0x20, 0x1e,
0x13, 0x6e, 0x2e, 0xd1, 0x12, 0xda, 0xa9, 0x57
},
.iv = {0x9E, 0x7a, 0x00, 0xef, 0xb1, 0x7a, 0xe7, 0x46},
};
/* Scan parameters */
static esp_ble_scan_params_t ble_scan_params = {
.scan_type = BLE_SCAN_TYPE_PASSIVE, /* Passive scan is enough */
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
.scan_interval = 0x50,
.scan_window = 0x30,
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE,
};
/**
* @brief Check if device advertises our target service UUID
*/
static bool is_target_device(const uint8_t *adv_data, uint8_t adv_len)
{
uint8_t offset = 0;
while (offset < adv_len) {
uint8_t len = adv_data[offset];
if (len == 0 || offset + len >= adv_len) {
break;
}
uint8_t type = adv_data[offset + 1];
if (type == ESP_BLE_AD_TYPE_16SRV_CMPL || type == ESP_BLE_AD_TYPE_16SRV_PART) {
for (int i = 0; i < len - 1; i += 2) {
uint16_t uuid = adv_data[offset + 2 + i] | (adv_data[offset + 3 + i] << 8);
if (uuid == CUSTOM_SERVICE_UUID) {
return true;
}
}
}
offset += len + 1;
}
return false;
}
/**
* @brief Decrypt encrypted advertising data using pre-shared key
*
* No connection required!
*/
static void decrypt_adv_data_no_connect(const uint8_t *adv_data, uint8_t adv_len,
const esp_bd_addr_t addr)
{
uint8_t offset = 0;
ESP_LOGI(TAG, "Processing advertising data from "ESP_BD_ADDR_STR"",
ESP_BD_ADDR_HEX(addr));
while (offset < adv_len) {
uint8_t len = adv_data[offset];
if (len == 0 || offset + len >= adv_len) {
break;
}
uint8_t type = adv_data[offset + 1];
/* Look for Encrypted Advertising Data (AD Type 0x31) */
if (type == ESP_BLE_AD_TYPE_ENC_ADV_DATA) {
const uint8_t *enc_data = &adv_data[offset + 2];
uint8_t enc_data_len = len - 1; /* Exclude type byte */
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) {
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);
int rc = ble_ead_decrypt(
pre_shared_key.session_key,
pre_shared_key.iv,
enc_data, enc_data_len,
dec_data);
if (rc == 0) {
ESP_LOGI(TAG, "✅ Decryption successful (no connection needed!)");
ESP_LOGI(TAG, "Decrypted data (%d bytes):", dec_len);
ESP_LOG_BUFFER_HEX(TAG, dec_data, dec_len);
/* Parse the decrypted advertising structure */
if (dec_len >= 2) {
uint8_t inner_len = dec_data[0];
uint8_t inner_type = dec_data[1];
if (inner_type == ESP_BLE_AD_TYPE_NAME_CMPL ||
inner_type == ESP_BLE_AD_TYPE_NAME_SHORT) {
char name[32] = {0};
size_t name_len = inner_len - 1;
if (name_len < sizeof(name) && name_len <= dec_len - 2) {
memcpy(name, &dec_data[2], name_len);
ESP_LOGI(TAG, "📛 Decrypted device name: \"%s\"", name);
}
}
}
} else {
ESP_LOGE(TAG, "❌ Decryption failed (rc=%d) - wrong key?", rc);
}
return; /* Found and processed encrypted data */
}
offset += len + 1;
}
ESP_LOGW(TAG, "No encrypted advertising data found in this packet");
}
/**
* @brief GAP event handler
*/
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
ESP_LOGI(TAG, "Scan parameters set, starting scan...");
esp_ble_gap_start_scanning(0); /* Scan indefinitely */
break;
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);
} else {
ESP_LOGE(TAG, "Scan start failed: %d", param->scan_start_cmpl.status);
}
break;
case ESP_GAP_BLE_SCAN_RESULT_EVT: {
esp_ble_gap_cb_param_t *scan_result = param;
if (scan_result->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
uint8_t *adv_data = scan_result->scan_rst.ble_adv;
uint8_t adv_len = scan_result->scan_rst.adv_data_len;
/* Check if this is our target device */
if (is_target_device(adv_data, adv_len)) {
/* Decrypt without connecting! */
decrypt_adv_data_no_connect(adv_data, adv_len, scan_result->scan_rst.bda);
}
}
break;
}
default:
break;
}
}
void app_main(void)
{
esp_err_t ret;
ESP_LOGI(TAG, "========================================");
ESP_LOGI(TAG, " EAD Central - No Connection Mode");
ESP_LOGI(TAG, "========================================");
/* Initialize NVS */
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_bt_controller_init(&bt_cfg));
ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE));
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_bluedroid_init_with_cfg(&cfg));
ESP_ERROR_CHECK(esp_bluedroid_enable());
ESP_ERROR_CHECK(esp_ble_gap_register_callback(gap_event_handler));
/* 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_LOGI(TAG, " IV:");
ESP_LOG_BUFFER_HEX(TAG, pre_shared_key.iv, 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.");
}
@@ -0,0 +1,13 @@
# Enable BLE
CONFIG_BT_ENABLED=y
CONFIG_BT_BLUEDROID_ENABLED=y
CONFIG_BT_BLE_ENABLED=y
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set
# Enable SMP for security
CONFIG_BT_BLE_SMP_ENABLE=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
@@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(enc_adv_data_prph)
@@ -0,0 +1,172 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- |
# BLE Encrypted Advertising Data Peripheral Example (Bluedroid)
This example demonstrates how to use BLE Encrypted Advertising Data (EAD) feature with Bluedroid stack.
## 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:
- Privacy protection for sensitive advertising data
- Selective disclosure of advertising data to authorized devices
- Enhanced security for BLE advertising
## System Architecture
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ PERIPHERAL (This Example) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────────┐ │
│ │ Original Data │───▶│ AES-CCM Encrypt │───▶│ Encrypted Adv Data │ │
│ │ "prph" (name) │ │ (Session Key+IV)│ │ (Randomizer+Cipher │ │
│ └─────────────────┘ └──────────────────┘ │ +MIC) │ │
│ └──────────┬──────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ BLE Advertising Packet │ │
│ ├──────────┬─────────────┬────────────────┬────────────────────────────┤ │
│ │ Flags │ Name "key" │ UUID 0x2C01 │ Encrypted Data (AD 0x31) │ │
│ │ (3B) │ (5B) │ (4B) │ (16B) │ │
│ └──────────┴─────────────┴────────────────┴────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ GATT Server │ │
│ ├──────────────────────────────────────────────────────────────────────┤ │
│ │ GAP Service (0x1800) │ │
│ │ └── Key Material Characteristic (0x2B88) │ │
│ │ └── Value: [Session Key (16B)] [IV (8B)] │ │
│ │ └── Permission: Read (Encrypted Link Required) │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
## Encryption Flow
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Encryption Process │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Generate Random Randomizer (5 bytes) │
│ ┌─────────────────────────────────────────┐ │
│ │ XX XX XX XX [D|XX] │ D = Direction Bit = 1 │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 2. Build Nonce (13 bytes) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Randomizer (5B) │ IV (8B) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 3. AES-CCM Encryption │
│ ┌─────────────────┐ │
│ │ Plaintext │ + Session Key + Nonce + AAD (0xEA) │
│ │ [05 09 p r p h]│ │
│ └─────────────────┘ │
│ │ │
│ ▼ │
│ 4. Output: Encrypted Payload │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Randomizer (5B) │ Ciphertext (6B) │ MIC (4B) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
## Advertising Data Format
```
Complete Advertising Packet (29 bytes):
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
12 16 0x31 [Encrypted Payload] Encrypted Advertising Data
Encrypted Payload Detail:
┌───────────────────┬─────────────────────┬─────────────────┐
│ Randomizer │ Ciphertext │ MIC │
│ (5 bytes) │ (6 bytes) │ (4 bytes) │
│ Random + Dir=1 │ AES-CCM output │ Auth Tag │
└───────────────────┴─────────────────────┴─────────────────┘
```
## How to Use Example
### Hardware Required
* A development board with ESP32/ESP32-C2/ESP32-C3/ESP32-C6/ESP32-H2/ESP32-S3 SoC
* A USB cable for power supply and programming
### Configure the project
```bash
idf.py set-target <chip_name>
idf.py menuconfig
```
### Build and Flash
```bash
idf.py -p PORT flash monitor
```
(To exit the serial monitor, type `Ctrl-]`.)
### Example Output
```
I (XXX) ENC_ADV_PRPH: Encrypted Advertising Data Peripheral started
I (XXX) ENC_ADV_PRPH: Key Material (Session Key + IV):
I (XXX) ENC_ADV_PRPH: 19 6a 0a d1 2a 61 20 1e 13 6e 2e d1 12 da a9 57 9e 7a 00 ef b1 7a e7 46
I (XXX) ENC_ADV_PRPH: Data before encryption:
I (XXX) ENC_ADV_PRPH: 05 09 70 72 70 68
I (XXX) ENC_ADV_PRPH: Encryption of adv data done successfully
I (XXX) ENC_ADV_PRPH: Raw advertising data set complete
I (XXX) ENC_ADV_PRPH: Advertising start successfully
```
## Testing with Central
Use the `enc_adv_data_cent` example as the central device:
```
┌──────────────────┐ ┌──────────────────┐
│ PERIPHERAL │ │ CENTRAL │
│ (This Example) │ │ (enc_adv_data_ │
│ │ │ cent) │
└────────┬─────────┘ └────────┬─────────┘
│ │
│ 1. Broadcast Encrypted Adv │
│ ─────────────────────────────────────▶│
│ │
│ 2. Connect (first time only) │
│ ◀═══════════════════════════════════ │
│ │
│ 3. Read Key Material (0x2B88) │
│ ═══════════════════════════════════▶ │
│ │
│ 4. Return Session Key + IV │
│ ◀═══════════════════════════════════ │
│ │
│ 5. Disconnect │
│ ◀═══════════════════════════════════ │
│ │
│ 6. Future: Decrypt without connect │
│ ─────────────────────────────────────▶│
│ │
▼ ▼
```
## Troubleshooting
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub.
@@ -0,0 +1,2 @@
idf_component_register(SRCS "enc_adv_data_prph.c" "ble_ead.c"
INCLUDE_DIRS ".")
@@ -0,0 +1,14 @@
menu "Example Configuration"
config EXAMPLE_ENABLE_KEY_MATERIAL
bool "Enable Key Material characteristic in GAP Service"
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.
This is the standard-compliant approach as defined in Bluetooth
Core Specification Version 5.4.
endmenu
@@ -0,0 +1,387 @@
/*
* SPDX-FileCopyrightText: 2025-2026 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 "mbedtls/ccm.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)
mbedtls_ccm_context ctx = {0};
int ret;
mbedtls_ccm_init(&ctx);
/* Set encryption key */
ret = mbedtls_ccm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, key, BLE_EAD_KEY_SIZE * 8);
if (ret != 0) {
ESP_LOGE(TAG, "mbedtls_ccm_setkey failed: %d", ret);
mbedtls_ccm_free(&ctx);
return -1;
}
/* Encrypt and authenticate */
/* mbedtls_ccm_encrypt_and_tag outputs: ciphertext || tag */
ret = mbedtls_ccm_encrypt_and_tag(&ctx, plaintext_len,
nonce, BLE_EAD_NONCE_SIZE,
aad, aad_len,
plaintext, ciphertext,
ciphertext + plaintext_len, tag_len);
if (ret != 0) {
ESP_LOGE(TAG, "mbedtls_ccm_encrypt_and_tag failed: %d", ret);
mbedtls_ccm_free(&ctx);
return -1;
}
mbedtls_ccm_free(&ctx);
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)
{
#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;
/* 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)
mbedtls_ccm_context ctx = {0};
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;
mbedtls_ccm_init(&ctx);
/* Set decryption key */
ret = mbedtls_ccm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, key, BLE_EAD_KEY_SIZE * 8);
if (ret != 0) {
ESP_LOGE(TAG, "mbedtls_ccm_setkey failed: %d", ret);
mbedtls_ccm_free(&ctx);
return -1;
}
/* Decrypt and verify */
/* mbedtls_ccm_auth_decrypt expects: ciphertext || tag */
ret = mbedtls_ccm_auth_decrypt(&ctx, plaintext_len,
nonce, BLE_EAD_NONCE_SIZE,
aad, aad_len,
ciphertext, plaintext,
ciphertext + plaintext_len, tag_len);
if (ret != 0) {
ESP_LOGE(TAG, "mbedtls_ccm_auth_decrypt failed: %d", ret);
mbedtls_ccm_free(&ctx);
return -1;
}
mbedtls_ccm_free(&ctx);
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)
{
int ret;
uint8_t nonce[BLE_EAD_NONCE_SIZE];
const uint8_t *randomizer;
const uint8_t *ciphertext;
size_t ciphertext_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;
}
/* 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 mbedTLS 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);
return ret;
}
@@ -0,0 +1,95 @@
/*
* 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
* Size must be at least 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);
#ifdef __cplusplus
}
#endif
#endif /* BLE_EAD_H */
@@ -0,0 +1,353 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/**
* @brief BLE Encrypted Advertising Data Peripheral Example
*
* This example demonstrates how to:
* 1. Encrypt advertising data using AES-CCM
* 2. Broadcast encrypted advertising data
* 3. Provide Key Material characteristic for central devices to read
*
* Based on Bluetooth Core Specification Version 5.4 - Encrypted Advertising Data
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "esp_gatt_common_api.h"
#include "ble_ead.h"
#define TAG "ENC_ADV_PRPH"
/* Device name */
#define DEVICE_NAME "ENC_ADV_PRPH"
#define GAP_SERVICE_UUID 0x1800 /* GAP Service UUID */
/* Profile configuration */
#define PROFILE_NUM 1
#define PROFILE_APP_ID 0
/* Unencrypted advertising pattern to be encrypted */
static uint8_t unencrypted_adv_pattern[] = {
0x05, 0x09, 'p', 'r', 'p', 'h' /* Complete Local Name: "prph" */
};
/* Session key and IV for encryption - in real application, generate securely! */
static 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
},
.iv = {0x9E, 0x7a, 0x00, 0xef, 0xb1, 0x7a, 0xe7, 0x46},
};
/* GATT state */
static esp_gatt_if_t gatts_if_stored = ESP_GATT_IF_NONE;
static uint16_t conn_id_stored = 0;
static bool is_connected = false;
/* Advertising parameters */
static esp_ble_adv_params_t adv_params = {
.adv_int_min = 0x20,
.adv_int_max = 0x40,
.adv_type = ADV_TYPE_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
/* Calculate encrypted payload size */
#define ENCRYPTED_ADV_DATA_LEN BLE_EAD_ENCRYPTED_PAYLOAD_SIZE(sizeof(unencrypted_adv_pattern))
/**
* @brief Encrypt advertising data and set raw advertising data
*/
static void set_encrypted_adv_data(void)
{
esp_err_t ret;
uint8_t encrypted_adv_data[ENCRYPTED_ADV_DATA_LEN];
int 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) {
ESP_LOGE(TAG, "Encryption of adv data failed: %d", rc);
return;
}
ESP_LOGI(TAG, "Encryption of adv data done successfully");
ESP_LOGI(TAG, "Data after encryption:");
ESP_LOG_BUFFER_HEX(TAG, encrypted_adv_data, sizeof(encrypted_adv_data));
/*
* Build raw advertising data:
* - Flags (3 bytes)
* - Complete Local Name (device name)
* - Complete 16-bit Service UUIDs (for central to recognize)
* - Encrypted Advertising Data
*/
uint8_t raw_adv_data[31];
uint8_t pos = 0;
/* Flags */
raw_adv_data[pos++] = 0x02; /* Length */
raw_adv_data[pos++] = ESP_BLE_AD_TYPE_FLAG;
raw_adv_data[pos++] = ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT;
/* Complete Local Name - "key" (short name for recognition) */
raw_adv_data[pos++] = 0x04; /* Length */
raw_adv_data[pos++] = ESP_BLE_AD_TYPE_NAME_CMPL;
raw_adv_data[pos++] = 'k';
raw_adv_data[pos++] = 'e';
raw_adv_data[pos++] = 'y';
/* Complete 16-bit Service UUIDs - GAP Service (0x1800) */
raw_adv_data[pos++] = 0x03; /* Length */
raw_adv_data[pos++] = ESP_BLE_AD_TYPE_16SRV_CMPL;
raw_adv_data[pos++] = GAP_SERVICE_UUID & 0xFF;
raw_adv_data[pos++] = (GAP_SERVICE_UUID >> 8) & 0xFF;
/* Encrypted Advertising Data */
raw_adv_data[pos++] = ENCRYPTED_ADV_DATA_LEN + 1; /* Length */
raw_adv_data[pos++] = ESP_BLE_AD_TYPE_ENC_ADV_DATA;
memcpy(&raw_adv_data[pos], encrypted_adv_data, ENCRYPTED_ADV_DATA_LEN);
pos += ENCRYPTED_ADV_DATA_LEN;
/* Set raw advertising data */
ret = esp_ble_gap_config_adv_data_raw(raw_adv_data, pos);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "config raw adv data failed: %s", esp_err_to_name(ret));
}
}
/**
* @brief Start advertising
*/
static void start_advertising(void)
{
esp_err_t ret = esp_ble_gap_start_advertising(&adv_params);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "start advertising failed: %s", esp_err_to_name(ret));
}
}
/**
* @brief GAP event handler
*/
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
ESP_LOGI(TAG, "Raw advertising data set complete");
start_advertising();
break;
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(TAG, "Advertising start failed: %d", param->adv_start_cmpl.status);
} else {
ESP_LOGI(TAG, "Advertising start successfully");
}
break;
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(TAG, "Advertising stop failed: %d", param->adv_stop_cmpl.status);
} else {
ESP_LOGI(TAG, "Advertising stop successfully");
}
break;
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
ESP_LOGI(TAG, "Connection params update, status %d, conn_int %d, latency %d, timeout %d",
param->update_conn_params.status,
param->update_conn_params.conn_int,
param->update_conn_params.latency,
param->update_conn_params.timeout);
break;
case ESP_GAP_BLE_SEC_REQ_EVT:
ESP_LOGI(TAG, "Security request received");
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
break;
case ESP_GAP_BLE_AUTH_CMPL_EVT:
ESP_LOGI(TAG, "Authentication complete, addr_type %d, addr "ESP_BD_ADDR_STR"",
param->ble_security.auth_cmpl.addr_type,
ESP_BD_ADDR_HEX(param->ble_security.auth_cmpl.bd_addr));
if (param->ble_security.auth_cmpl.success) {
ESP_LOGI(TAG, "Authentication success, auth_mode %d", param->ble_security.auth_cmpl.auth_mode);
} else {
ESP_LOGW(TAG, "Authentication failed, reason 0x%x", param->ble_security.auth_cmpl.fail_reason);
}
break;
default:
break;
}
}
/**
* @brief GATTS profile event handler
*/
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param)
{
switch (event) {
case ESP_GATTS_REG_EVT:
ESP_LOGI(TAG, "GATT server register, status %d, app_id %d, gatts_if %d",
param->reg.status, param->reg.app_id, gatts_if);
gatts_if_stored = gatts_if;
/* Set device name */
esp_ble_gap_set_device_name(DEVICE_NAME);
/* Set encrypted advertising data */
set_encrypted_adv_data();
/* Set Key Material in GAP service
* The Key Material characteristic is part of the built-in GAP service
*/
ESP_LOGI(TAG, "Setting Key Material in GAP service");
esp_ble_gap_set_key_material(key_material.session_key, key_material.iv);
break;
case ESP_GATTS_CONNECT_EVT:
ESP_LOGI(TAG, "Connected, conn_id %d, remote "ESP_BD_ADDR_STR"",
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda));
conn_id_stored = param->connect.conn_id;
is_connected = true;
/* Update connection parameters */
esp_ble_conn_update_params_t conn_params = {0};
memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
conn_params.latency = 0;
conn_params.max_int = 0x20;
conn_params.min_int = 0x10;
conn_params.timeout = 400;
esp_ble_gap_update_conn_params(&conn_params);
break;
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason);
is_connected = false;
/* Re-encrypt and restart advertising with new randomizer */
set_encrypted_adv_data();
break;
case ESP_GATTS_MTU_EVT:
ESP_LOGI(TAG, "MTU exchange, MTU %d", param->mtu.mtu);
break;
default:
break;
}
}
void app_main(void)
{
esp_err_t ret;
/* Initialize NVS */
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
/* Release memory for Classic BT */
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
/* Initialize BT controller */
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(TAG, "initialize controller failed: %s", esp_err_to_name(ret));
return;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(TAG, "enable controller failed: %s", esp_err_to_name(ret));
return;
}
/* Initialize Bluedroid */
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
ret = esp_bluedroid_init_with_cfg(&cfg);
if (ret) {
ESP_LOGE(TAG, "init bluetooth failed: %s", esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(TAG, "enable bluetooth failed: %s", esp_err_to_name(ret));
return;
}
/* Register callbacks */
ret = esp_ble_gatts_register_callback(gatts_event_handler);
if (ret) {
ESP_LOGE(TAG, "gatts register error: %x", ret);
return;
}
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret) {
ESP_LOGE(TAG, "gap register error: %x", ret);
return;
}
/* Register GATT application */
ret = esp_ble_gatts_app_register(PROFILE_APP_ID);
if (ret) {
ESP_LOGE(TAG, "gatts app register error: %x", ret);
return;
}
/* Set MTU */
esp_ble_gatt_set_local_mtu(500);
/* Configure security parameters for Key Material characteristic access
* Using SC (Secure Connections) with bonding, no MITM (since IO_CAP is NONE)
*/
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_BOND; /* SC + Bond, no MITM */
esp_ble_io_cap_t io_cap = 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(auth_req));
esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &io_cap, sizeof(io_cap));
esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(key_size));
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(init_key));
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(rsp_key));
ESP_LOGI(TAG, "Encrypted Advertising Data Peripheral started");
ESP_LOGI(TAG, "Key Material (Session Key + IV):");
ESP_LOG_BUFFER_HEX(TAG, &key_material, sizeof(key_material));
}
@@ -0,0 +1,13 @@
# Enable BLE
CONFIG_BT_ENABLED=y
CONFIG_BT_BLUEDROID_ENABLED=y
CONFIG_BT_BLE_ENABLED=y
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set
# Enable SMP for security
CONFIG_BT_BLE_SMP_ENABLE=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