ESP-ADF wifi_service子模块esp_wifi_setting配网之blufi_config详解

ESP-ADF wifi_service子模块esp_wifi_setting配网之blufi_config详解

版本信息: v2.7-65-gcf908721

本章节分析的源码位于 /components/wifi_service/blufi_config/blufi_config.c 文件、/components/wifi_service/blufi_config/blufi_security.c 文件和 /components/wifi_service/include/blufi_config.h 文件

模块概览

Blufi Config在ESP-ADF的wifi_service组件架构中处于配网接口实现层,下图展示了其在整体架构中的位置:

wifi_service组件架构
esp_wifi_setting
(配网接口抽象层)
wifi_service
(WiFi服务核心)
wifi_ssid_manager
(SSID管理器)
smart_config
(ESP Touch配网)
blufi_config
(蓝牙配网)
airkiss_config
(微信配网)
softap_config
(软AP配网)
NVS存储
(永久保存WiFi信息)
智能选择
(最佳网络选择算法)

从图中可以看出,blufi_config是esp_wifi_setting抽象接口的一种具体实现,与其他配网方式(smart_config、airkiss_config和softap_config)并列。配网完成后,获取的WiFi凭证会被保存到wifi_ssid_manager模块中进行管理。

ESP-ADF 的 blufi_config 模块是基于蓝牙 BLE 技术实现的 WiFi 配网方式,它同样遵循 ESP-ADF 中的 esp_wifi_setting 接口规范,实现了通过蓝牙向 ESP 设备传输 WiFi 配置信息的功能。

BLUFI (Bluetooth Low Energy + WiFi) 是乐鑫推出的一种通过蓝牙配置 WiFi 的方案,它的优势是可以在无需外部显示设备的情况下,通过移动设备的蓝牙连接向 ESP 设备传输 WiFi 的 SSID 和密码等配置信息。

与SmartConfig和AirKiss配网相比,BLUFI配网具有以下特点:

  1. 使用蓝牙而非WiFi进行初始通信,不依赖设备处于同一WiFi网络
  2. 安全性更高,采用标准加密算法保护传输数据
  3. 可靠性更好,通信过程有确认机制
  4. 支持双向通信,可以获取配网状态和发送自定义数据
  5. 耗电相对较低,特别是使用BLE技术

文件结构

blufi_config 模块主要由以下文件组成:

  1. /components/wifi_service/include/blufi_config.h - 对外接口定义
  2. /components/wifi_service/blufi_config/blufi_config.c - 核心实现代码
  3. /components/wifi_service/blufi_config/blufi_security.c - 安全加密相关代码

关键数据结构

BLUFI配置结构体

typedef struct wifi_blufi_config {
    uint8_t                 ble_server_if;      // BLE 服务接口号
    uint16_t                ble_conn_id;        // BLE 连接 ID
    wifi_config_t           sta_config;         // WiFi 站点配置
    bool                    sta_connected_flag; // WiFi 站点连接状态标志
    bool                    ble_connected_flag; // BLE 连接状态标志
    void                    *user_data;         // 用户自定义数据
    int                     user_data_length;   // 用户自定义数据长度
} wifi_blufi_config_t;

蓝牙广播数据

static uint8_t blufi_service_uuid128[32] = {
    /* LSB <--------------------------------------------------------------------------------> MSB */
    // BLUFI服务的128位UUID值,用于唯一标识BLE服务类型
    // 实际上这里只使用了16字节,对应于乐鑫为BLUFI定义的标准UUID
    // 注意数组中存储顺序是从最低有效字节(LSB)到最高有效字节(MSB)
    0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
};

static esp_ble_adv_data_t blufi_adv_data = {
    .set_scan_rsp = false,           // 是否在扫描响应中设置此广播数据,false表示在广播包中
    .include_name = true,            // 是否在广播数据中包含设备名称
    .include_txpower = true,         // 是否在广播数据中包含发射功率
    .min_interval = 0x0006,          // 最小连接间隔,实际值 = min_interval * 1.25ms,这里约7.5ms
    .max_interval = 0x0010,          // 最大连接间隔,实际值 = max_interval * 1.25ms,这里约20ms
    .appearance = 0x00,              // 设备外观特征值,0x00表示未指定类型
    .manufacturer_len = 0,           // 制造商特定数据长度
    .p_manufacturer_data = NULL,     // 制造商特定数据,这里未使用
    .service_data_len = 0,           // 服务数据长度
    .p_service_data = NULL,          // 服务数据,这里未使用
    .service_uuid_len = 16,          // 服务UUID长度,使用16字节UUID
    .p_service_uuid = blufi_service_uuid128,  // 指向服务UUID数组的指针,用于标识BLUFI服务
    .flag = 0x6,                     // 广播标志,0x6表示LE通用可发现模式且仅支持BR/EDR
};

蓝牙广播参数

static esp_ble_adv_params_t blufi_adv_params = {
    .adv_int_min        = 0x100,     // 广播间隔最小值,实际值 = adv_int_min * 0.625ms,这里约160ms
    .adv_int_max        = 0x100,     // 广播间隔最大值,设置为与最小值相同以确保固定间隔
    .adv_type           = ADV_TYPE_IND, // 广播类型,指定为可连接的不定向广播(最常用类型)
    .own_addr_type      = BLE_ADDR_TYPE_PUBLIC, // 地址类型,使用公共地址(固定的MAC地址)
    .channel_map        = ADV_CHNL_ALL, // 使用所有广播信道(37、3839信道)
    .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, // 广播过滤策略,允许任何设备扫描和连接
};

BLUFI回调函数结构体

/**
 * @brief 定义BLUFI协议的全部回调函数集
 * 
 * 该结构体定义了BLUFI协议需要的全部回调函数,包括:
 * 1. 事件处理函数 - 处理BLUFI协议不同状态的事件
 * 2. 密钥协商处理函数 - 负责DH密钥交换
 * 3. 加密/解密函数 - 提供数据加密保护
 * 4. 校验和函数 - 确保数据完整性
 */
static esp_blufi_callbacks_t wifi_ble_callbacks = {
    .event_cb = wifi_ble_event_callback,                // BLUFI事件回调函数,处理各种BLUFI事件
    .negotiate_data_handler = blufi_dh_negotiate_data_handler, // DH密钥协商数据处理函数
    .encrypt_func = blufi_aes_encrypt,                 // 数据加密函数,使用AES-128-CFB加密
    .decrypt_func = blufi_aes_decrypt,                 // 数据解密函数,使用AES-128-CFB解密
    .checksum_func = blufi_crc_checksum,               // 校验和计算函数,使用CRC-16确保数据完整性
};

关键函数分析

创建和初始化

/**
 * @brief 创建BLUFI配网实例
 * 
 * 该函数完成以下主要操作:
 * 1. 创廾sp_wifi_setting接口实例
 * 2. 分配并初始化BLUFI配置结构体
 * 3. 初始化蓝牙协议栈和回调函数
 * 4. 注册配网启动和停止函数
 * 
 * @param info 可选参数,本函数未使用该参数,可传入NULL
 * @return esp_wifi_setting_handle_t 配网接口句柄,失败时返回NULL
**/
esp_wifi_setting_handle_t blufi_config_create(void *info)
{
    // 1. 创建WiFi配置设置处理句柄,并命名为"blufi_config"
    bc_setting_handle = esp_wifi_setting_create("blufi_config");
    AUDIO_MEM_CHECK(TAG, bc_setting_handle, return NULL);  // 内存检查,失败则返回NULL
    
    // 2. 分配并初始化BLUFI配置结构体
    wifi_blufi_config_t *cfg = audio_calloc(1, sizeof(wifi_blufi_config_t));
    AUDIO_MEM_CHECK(TAG, cfg, {  // 内存分配失败处理
        audio_free(bc_setting_handle);
        return NULL;
    });
    
    // 3. 将配置结构体设置到WiFi配置处理句柄中
    esp_wifi_setting_set_data(bc_setting_handle, cfg);
    
    // 4. 初始化BLUFI协议栈和回调函数
    // 将已定义的回调函数注册到BLUFI协议栈,并初始化蓝牙主机和GAP协议
    ESP_ERROR_CHECK(esp_blufi_host_and_cb_init(&wifi_ble_callbacks));
    
    // 5. 注册配网模块的启动和停止函数
    esp_wifi_setting_register_function(bc_setting_handle, _ble_config_start, _ble_config_stop, NULL);
    
    return bc_setting_handle;  // 返回创建的处理句柄
}

该函数是BLUFI配网模块的入口点,主要功能包括:

  1. 创建WiFi配置句柄
  2. 分配并初始化BLUFI配置结构体内存
  3. 初始化蓝牙主机和回调函数
  4. 注册配网模块的启动和停止函数
BLUFI蓝牙协议栈初始化

esp_blufi_host_and_cb_init函数是初始化BLUFI蓝牙协议栈的关键函数,它会根据不同的蓝牙协议栈选择(经典Bluedroid或轻量级NimBLE)进行相应初始化。

Bluedroid协议栈实现(ESP-IDF默认)
/**
 * @brief 初始化BLUFI蓝牙协议栈和回调函数
 * 
 * 该函数完成三个主要初始化工作:
 * 1. 初始化蓝牙主机(host)
 * 2. 注册BLUFI协议回调函数
 * 3. 注册通用访问配置(GAP)回调函数
 * 
 * @param example_callbacks BLUFI回调函数结构体指针
 * @return esp_err_t 成功返回ESP_OK,失败返回错误码
 */
esp_err_t esp_blufi_host_and_cb_init(esp_blufi_callbacks_t *example_callbacks)
{
    // 1. 初始化蓝牙主机
    ESP_ERROR_CHECK(esp_blufi_host_init());
    // 2. 注册BLUFI应用层回调函数
    ESP_ERROR_CHECK(esp_blufi_register_callbacks(example_callbacks));
    // 3. 注册GAP层回调函数并初始化BLUFI配置文件
    ESP_ERROR_CHECK(esp_blufi_gap_register_callback());
    return ESP_OK;
}

蓝牙主机初始化函数esp_blufi_host_init是BLUFI协议栈初始化的核心。ESP-IDF支持两种不同的蓝牙协议栈实现:Bluedroid(默认,资源占用较大)和NimBLE(轻量级替代方案)。以下是基于NimBLE的实现:

/**
 * @brief 初始化BLUFI蓝牙主机(NimBLE协议栈实现)
 * 
 * 该函数完成NimBLE蓝牙协议栈的初始化工作,包括:
 * 1. 初始化NimBLE主机控制接口(HCI)
 * 2. 配置回调函数(重置、同步、GATT服务注册等)
 * 3. 初始化GATT服务器
 * 4. 设置设备名称
 * 5. 初始化BLUFI BTC(蓝牙控制器)接口
 * 6. 启动NimBLE主机任务
 * 
 * @return esp_err_t 成功返回ESP_OK
 */
esp_err_t esp_blufi_host_init(void)
{
    // 初始化NimBLE HCI接口
    ESP_ERROR_CHECK(esp_nimble_hci_init());
    nimble_port_init();

    // 配置NimBLE主机回调函数
    ble_hs_cfg.reset_cb = blufi_on_reset;            // 主机重置回调
    ble_hs_cfg.sync_cb = blufi_on_sync;              // 主机同步回调
    ble_hs_cfg.gatts_register_cb = esp_blufi_gatt_svr_register_cb;  // GATT服务注册回调
    ble_hs_cfg.store_status_cb = ble_store_util_status_rr;  // 存储状态回调
    ble_hs_cfg.sm_io_cap = 4;                        // 安全管理器I/O能力(无输入、无输出)
    ble_hs_cfg.sm_sc = 0;                            // 禁用安全连接(Secure Connections)

    // 初始化GATT服务器
    int rc;
    rc = esp_blufi_gatt_svr_init();
    assert(rc == 0);  // 断言初始化成功
    
    // 设置设备名称(用于广播)
    rc = ble_svc_gap_device_name_set(BLUFI_DEVICE_NAME);
    assert(rc == 0);  // 断言设置成功
    
    // 初始化BLUFI BTC接口
    esp_blufi_btc_init();
    
    // 初始化NimBLE主机任务
    nimble_port_freertos_init(bleprph_host_task);

    return ESP_OK;
}

GAP注册回调过程(esp_blufi_gap_register_callback):

esp_err_t esp_blufi_gap_register_callback(void)
{
    int rc;
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0))
    // ESP-IDF 4.3及以上版本使用新的回调处理函数
    rc = esp_ble_gap_register_callback(esp_blufi_gap_event_handler);
#else
    // 较早版本使用原始的回调处理函数
    rc = esp_ble_gap_register_callback(blufi_config_gap_event_handler);
#endif

    if(rc){
        return rc;
    }
    // 初始化BLUFI配置文件
    return esp_blufi_profile_init();
}

配网启动

/**
 * @brief 启动BLUFI配网过程
 * 
 * 该函数的主要功能是启动蓝牙广播,使设备可被手机扫描到。
 * 根据ESP-IDF版本不同,使用不同的API启动蓝牙广播:
 * - ESP-IDF 4.3及以上版本使用简化API直接调用esp_blufi_adv_start()
 * - 早期版本需要手动设置设备名称、配置广播数据并启动广播
 * 
 * @param self WiFi配置接口句柄,由blufi_config_create创建
 * @return esp_err_t 成功返回ESP_OK,失败返回错误码
**/
esp_err_t _ble_config_start(esp_wifi_setting_handle_t self)
{
    ESP_LOGI(TAG, "blufi_config_start");  // 记录开始配网的日志
    
    // 根据ESP-IDF版本使用不同的API启动蓝牙广播
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0))
    // ESP-IDF 4.3及以上版本使用新的简化API
    esp_blufi_adv_start();
#else
    // ESP-IDF 4.3以下版本需要手动设置设备名称、配置广播数据并启动广播
    esp_ble_gap_set_device_name(BLUFI_DEVICE_NAME);  // 设置蓝牙设备名称
    esp_ble_gap_config_adv_data(&blufi_adv_data);    // 配置蓝牙广播数据
    esp_ble_gap_start_advertising(&blufi_adv_params); // 开始蓝牙广播
#endif
    
    ESP_LOGI(TAG, "BLUFI VERSION %04x", esp_blufi_get_version());  // 记录BLUFI协议版本
    return ESP_OK;
}

配网停止

/**
 * @brief 停止BLUFI配网过程
 * 
 * 该函数停止蓝牙广播,一般在以下情况下调用:
 * 1. 当配网完成后,无需继续广播
 * 2. 当配网失败需要停止当前广播时
 * 3. 当进入低功耗模式需要关闭蓝牙时
 * 
 * 根据ESP-IDF版本不同,使用不同的API停止蓝牙广播。
 * 
 * @param self WiFi配置接口句柄,由blufi_config_create创建
 * @return esp_err_t 成功返回ESP_OK,失败返回错误码
**/
static esp_err_t _ble_config_stop(esp_wifi_setting_handle_t self)
{
    // 根据ESP-IDF版本使用不同的API停止蓝牙广播
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0))
    // ESP-IDF 4.3及以上版本使用新的简化API
    esp_blufi_adv_stop();
#else
    // ESP-IDF 4.3以下版本使用原始的蓝牙API
    esp_ble_gap_stop_advertising();
#endif

    ESP_LOGI(TAG, "blufi_config_stop");  // 记录停止配网的日志
    return ESP_OK;
}

该函数停止BLUFI配网流程,主要是停止蓝牙广播。

4. BLUFI事件处理

/**
 * @brief BLUFI事件回调函数,处理所有BLUFI协议事件
 * 
 * 该函数是BLUFI配网过程的核心,负责处理从手机端发来的各种指令和配置数据,
 * 并完成相应的WiFi配置和连接操作。
 * 
 * @param event BLUFI事件类型
 * @param param 事件参数,根据事件类型有不同的数据结构
 */
static void wifi_ble_event_callback(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param)
{
    // 获取BLUFI模块的配置数据
    wifi_blufi_config_t *cfg = esp_wifi_setting_get_data(bc_setting_handle);
    switch (event) {
        case ESP_BLUFI_EVENT_INIT_FINISH:
            // BLUFI协议栈初始化完成
            ESP_LOGI(TAG, "BLUFI init finish, func:%s, line:%d ", __func__, __LINE__);
            break;
            
        case ESP_BLUFI_EVENT_DEINIT_FINISH:
            // BLUFI协议栈取消初始化完成
            ESP_LOGI(TAG, "BLUFI deinit finish, func:%s, line:%d ", __func__, __LINE__);
            break;
            
        case ESP_BLUFI_EVENT_BLE_CONNECT:
            // 蓝牙设备已连接(手机与ESP设备连接成功)
            ESP_LOGI(TAG, "BLUFI ble connect, func:%s, line:%d ", __func__, __LINE__);
            cfg->ble_connected_flag = true;  // 设置蓝牙连接标志
            
            // 停止蓝牙广播,因为已经建立了连接
            esp_blufi_adv_stop();  // 或者esp_ble_gap_stop_advertising()
            
            // 初始化安全模块,准备密钥交换
            blufi_security_init();
            break;
            
        case ESP_BLUFI_EVENT_BLE_DISCONNECT:
            // 蓝牙连接断开
            ESP_LOGI(TAG, "BLUFI ble disconnect, func:%s, line:%d ", __func__, __LINE__);
            cfg->ble_connected_flag = false;  // 清除蓝牙连接标志
            
            // 清理安全模块资源
            blufi_security_deinit();
            break;
            
        case ESP_BLUFI_EVENT_SET_WIFI_OPMODE:
            // 设置WiFi工作模式(站点/AP/混合模式)
            ESP_LOGI(TAG, "BLUFI Set WIFI opmode %d", param->wifi_mode.op_mode);
            // 调用ESP-IDF API设置WiFi模式
            ESP_ERROR_CHECK(esp_wifi_set_mode(param->wifi_mode.op_mode));
            break;
            
        case ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP:
            // 手机请求设备连接到WiFi接入点
            ESP_LOGI(TAG, "BLUFI requset wifi connect to AP");
            esp_wifi_disconnect();  // 先断开当前连接(如果有)
            esp_wifi_connect();     // 连接到新配置的AP
            _ble_config_stop(NULL); // 停止配网过程,因为已收到连接请求
            break;
            
        case ESP_BLUFI_EVENT_REQ_DISCONNECT_FROM_AP:
            // 手机请求设备断开WiFi连接
            ESP_LOGI(TAG, "BLUFI requset wifi disconnect from AP");
            esp_wifi_disconnect();
            break;
            
        case ESP_BLUFI_EVENT_REPORT_ERROR:
            // 报告错误状态
            ESP_LOGE(TAG, "BLUFI report error, error code %d", param->report_error.state);
            esp_blufi_send_error_info(param->report_error.state);
            break;
            
        case ESP_BLUFI_EVENT_GET_WIFI_STATUS:
            // 手机端请求获取WiFi状态
            {
                wifi_mode_t mode;
                esp_blufi_extra_info_t info = {0};
                esp_wifi_get_mode(&mode);  // 获取当前WiFi模式
                
                if (cfg->sta_connected_flag) {
                    // 已经连接到AP,发送成功状态和SSID信息
                    memset(&info, 0, sizeof(esp_blufi_extra_info_t));
                    info.sta_ssid = cfg->sta_config.sta.ssid;
                    info.sta_ssid_len = strlen((char *)cfg->sta_config.sta.ssid);
                    esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS, 0, &info);
                } else {
                    // 未连接到AP,发送失败状态
                    esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_FAIL, 0, NULL);
                }
                ESP_LOGI(TAG, "BLUFI get wifi status from AP");
            }
            break;
            
        case ESP_BLUFI_EVENT_RECV_SLAVE_DISCONNECT_BLE:
            // 收到指令,要求断开蓝牙连接
            esp_blufi_disconnect();  // 执行断开指令
            ESP_LOGI(TAG, "BLUFI close a gatt connection, func:%s, line:%d ", __func__, __LINE__);
            break;
            
        case ESP_BLUFI_EVENT_RECV_STA_BSSID:
            // 收到WiFi AP的BSSID(MAC地址)
            memcpy(cfg->sta_config.sta.bssid, param->sta_bssid.bssid, 6);
            cfg->sta_config.sta.bssid_set = 1;
            esp_wifi_set_config(WIFI_IF_STA, &cfg->sta_config);
            ESP_LOGI(TAG, "Recv STA BSSID %s\n", cfg->sta_config.sta.ssid);
            break;
            
        case ESP_BLUFI_EVENT_RECV_STA_SSID:
            // 收到WiFi AP的SSID(网络名称)
            strncpy((char *)cfg->sta_config.sta.ssid, (char *)param->sta_ssid.ssid, param->sta_ssid.ssid_len);
            cfg->sta_config.sta.ssid[param->sta_ssid.ssid_len] = '\0';
            esp_wifi_set_config(WIFI_IF_STA, &cfg->sta_config);
            ESP_LOGI(TAG, "Recv STA SSID %s\n", cfg->sta_config.sta.ssid);
            break;
            
        case ESP_BLUFI_EVENT_RECV_STA_PASSWD:
            // 收到WiFi AP的密码
            strncpy((char *)cfg->sta_config.sta.password, (char *)param->sta_passwd.passwd, param->sta_passwd.passwd_len);
            cfg->sta_config.sta.password[param->sta_passwd.passwd_len] = '\0';
            esp_wifi_set_config(WIFI_IF_STA, &cfg->sta_config);
            ESP_LOGI(TAG, "Recv STA PASSWORD %s\n", cfg->sta_config.sta.password);
            break;
            
        case ESP_BLUFI_EVENT_GET_WIFI_LIST:
            // 手机端请求获取附近的WiFi列表
            {
                wifi_scan_config_t scanConf = {
                    .ssid = NULL,
                    .bssid = NULL,
                    .channel = 0,
                    .show_hidden = false
                };
                // 启动WiFi扫描
                esp_wifi_scan_start(&scanConf, true);
            }
            break;
            
        case ESP_BLUFI_EVENT_RECV_CUSTOM_DATA:
            // 收到自定义数据(用于扩展使用)
            ESP_LOGI(TAG, "Recv Custom Data %" PRIu32 "\n", param->custom_data.data_len);
            esp_log_buffer_hex("Custom Data", param->custom_data.data, param->custom_data.data_len);
            break;
            
        default:
            break;
    }
}

BLUFI事件交互时序图

下图展示BLUFI配网过程中的主要事件交互顺序,清晰地描述了ESP设备、手机APP和WiFi接入点之间的通信流程:

手机APP ESP设备 WiFi接入点 blufi_config_create() ESP_BLUFI_EVENT_INIT_FINISH _ble_config_start() - 启动蓝牙广播 扫描并连接BLE设备 ESP_BLUFI_EVENT_BLE_CONNECT blufi_security_init() - 初始化安全模块 DH密钥协商过程 发送DH参数 blufi_dh_negotiate_data_handler 发送ESP的DH公钥 建立加密通道 ESP_BLUFI_EVENT_SET_WIFI_OPMODE esp_wifi_set_mode() ESP_BLUFI_EVENT_RECV_STA_SSID 保存WiFi SSID ESP_BLUFI_EVENT_RECV_STA_PASSWD 保存WiFi密码 ESP_BLUFI_EVENT_RECV_STA_BSSID 保存WiFi BSSID opt [还可能发送BSSID] ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP esp_wifi_disconnect() 首先断开现有连接 esp_wifi_connect() 连接到AP _ble_config_stop() 停止广播 WiFi连接成功/失败 ESP_BLUFI_EVENT_GET_WIFI_STATUS 发送ESP_BLUFI_STA_CONN_SUCCESS 发送ESP_BLUFI_STA_CONN_FAIL alt [WiFi连接成功] [WiFi连接失败] ESP_BLUFI_EVENT_RECV_CUSTOM_DATA 可能发送响应的自定义数据 opt [可选:自定义数据交换] ESP_BLUFI_EVENT_RECV_SLAVE_DISCONNECT_BLE esp_blufi_disconnect() ESP_BLUFI_EVENT_BLE_DISCONNECT blufi_security_deinit() 清理安全资源 opt [可选:断开蓝牙连接] 手机APP ESP设备 WiFi接入点

如上图所示,BLUFI配网的完整交互流程可以分为以下几个核心阶段:

  1. 初始化阶段:初始化BLUFI协议栈并启动蓝牙广播
  2. 蓝牙连接阶段:手机连接到ESP设备并初始化安全模块
  3. 安全协商阶段:建立加密通道,保护数据传输
  4. WiFi配置阶段:接收并保存WiFi配置信息(SSID、密码等)
  5. WiFi连接阶段:连接到指定WiFi接入点
  6. 状态报告阶段:向手机报告WiFi连接状态
  7. 完成阶段:可能交换自定义数据或断开蓝牙连接

这个交互流程清晰地展示了各类ESP_BLUFI_EVENT事件的触发顺序和处理逻辑,以及wifi_ble_event_callback函数在整个配网过程中的关键作用。

5. 扩展功能

/**
 * @brief 设置WiFi站点模式连接状态标志
 * 
 * 此函数用于更新BLUFI配置结构体中的站点连接状态标志。
 * 当WiFi连接或断开时,应用程序应调用此函数更新状态,
 * 这样当手机APP查询WiFi状态时,可以返回正确的连接状态。
 * 
 * @param handle BLUFI配网模块句柄
 * @param flag true表示已连接,false表示未连接
 * @return esp_err_t 成功返回ESP_OK,失败返回ESP_FAIL
 */
esp_err_t blufi_set_sta_connected_flag(esp_wifi_setting_handle_t handle, bool flag)
{
    AUDIO_NULL_CHECK(TAG, handle, return ESP_FAIL);
    wifi_blufi_config_t *blufi_cfg = esp_wifi_setting_get_data(handle);
    blufi_cfg->sta_connected_flag = flag;
    return ESP_OK;
}

/**
 * @brief 设置BLUFI自定义数据
 * 
 * 此函数允许应用程序存储自定义数据到BLUFI配置结构体中,
 * 之后可以通过blufi_send_customized_data函数将此数据发送给手机APP。
 * 常用于在配网成功后向手机传递额外信息,如设备ID、IP地址等。
 * 
 * @param handle BLUFI配网模块句柄
 * @param data 自定义数据指针
 * @param data_len 数据长度
 * @return esp_err_t 成功返回ESP_OK,失败返回ESP_FAIL
 */
esp_err_t blufi_set_customized_data(esp_wifi_setting_handle_t handle, char *data, int data_len)
{
    AUDIO_NULL_CHECK(TAG, handle, return ESP_FAIL);
    AUDIO_NULL_CHECK(TAG, data, return ESP_FAIL);
    wifi_blufi_config_t *blufi_cfg = esp_wifi_setting_get_data(handle);
    blufi_cfg->user_data = audio_calloc(1, data_len + 1);
    blufi_cfg->user_data_length = data_len;
    AUDIO_MEM_CHECK(TAG, blufi_cfg->user_data, return ESP_FAIL);
    memcpy(blufi_cfg->user_data, data, data_len);
    ESP_LOGI(TAG, "Set blufi customized data: %s, length: %d", data, data_len);
    return ESP_OK;
}

/**
 * @brief 发送自定义数据到手机APP
 * 
 * 此函数将之前通过blufi_set_customized_data设置的自定义数据
 * 通过BLUFI协议发送给已连接的手机APP。
 * 只有在蓝牙已连接状态下才能发送数据。
 * 
 * 应用场景:
 * - 配网成功后发送设备信息(如IP地址、设备ID等)
 * - 传输设备状态或配置参数
 * - 实现自定义控制协议
 * 
 * @param handle BLUFI配网模块句柄
 * @return esp_err_t 成功返回ESP_OK,失败返回ESP_FAIL或ESP_ERR_INVALID_STATE
 */
esp_err_t blufi_send_customized_data(esp_wifi_setting_handle_t handle)
{
    AUDIO_NULL_CHECK(TAG, handle, return ESP_FAIL);
    wifi_blufi_config_t *blufi_cfg = esp_wifi_setting_get_data(handle);
    
    // 检查蓝牙是否已连接,未连接状态无法发送数据
    if (!blufi_cfg->ble_connected_flag) {
        ESP_LOGE(TAG, "BLE is not connected, cannot send customized data");
        return ESP_ERR_INVALID_STATE;
    }
    
    // 检查是否有自定义数据可发送
    if (blufi_cfg->user_data == NULL || blufi_cfg->user_data_length <= 0) {
        ESP_LOGE(TAG, "No customized data to send");
        return ESP_ERR_INVALID_STATE;
    }
    
    // 通过BLUFI协议发送自定义数据
    esp_err_t ret = esp_blufi_send_custom_data(blufi_cfg->user_data, blufi_cfg->user_data_length);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to send customized data, err=0x%x", ret);
        return ret;
    }
    
    ESP_LOGI(TAG, "Send customized data success, length: %d", blufi_cfg->user_data_length);
    return ESP_OK;
}

这些函数提供了扩展功能:

  1. blufi_set_sta_connected_flag:设置WiFi连接状态标志
  2. blufi_set_customized_data:设置自定义数据
  3. blufi_send_customized_data:发送自定义数据到蓝牙客户端

工作流程

BLUFI配网的工作流程如下:

ESP设备 手机APP 初始化BLUFI模块 启动BLE广播 扫描并连接BLE设备 建立BLE连接 DH密钥交换 建立加密通道 发送WiFi配置信息(SSID,密码) 保存WiFi配置 请求连接WiFi 停止BLE广播 连接WiFi网络 发送WiFi连接状态 可选: 发送自定义数据 发送自定义数据 ESP设备 手机APP

安全机制

BLUFI配网模块实现了多层安全保护:

  1. DH密钥交换:使用Diffie-Hellman密钥交换算法安全地在设备和手机间建立共享密钥
  2. AES-128加密:使用AES-128算法加密传输的数据
  3. CRC校验和:确保数据完整性

这些安全机制由blufi_security.c文件实现,主要包括:

  • blufi_dh_negotiate_data_handler:处理DH密钥交换
  • blufi_aes_encrypt:AES加密函数
  • blufi_aes_decrypt:AES解密函数
  • blufi_crc_checksum:CRC校验和计算

适配不同版本

代码包含了对不同ESP-IDF版本的适配:

#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0))
    esp_blufi_adv_start();
#else
    esp_ble_gap_set_device_name(BLUFI_DEVICE_NAME);
    esp_ble_gap_config_adv_data(&blufi_adv_data);
    esp_ble_gap_start_advertising(&blufi_adv_params);
#endif

同时也支持不同的蓝牙协议栈实现:

  • BLUEDROID:ESP-IDF默认的蓝牙协议栈
  • NimBLE:内存占用更小的轻量级蓝牙协议栈

使用示例

以下是使用BLUFI配网模块的简单示例:

#include "esp_wifi_setting.h"
#include "blufi_config.h"
#include "esp_log.h"

static const char *TAG = "BLUFI_EXAMPLE";

void app_main()
{
    // 初始化ESP-ADF音频内存管理
    audio_mem_init();
    
    // 初始化ESP-IDF WiFi
    esp_netif_init();
    esp_event_loop_create_default();
    esp_netif_create_default_wifi_sta();
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&cfg);
    
    // 创建WiFi服务
    wifi_service_config_t wifi_service_config = {
        .extern_stack = true,
        .evt_cb = wifi_service_cb,
        .cb_ctx = NULL,
        .setting_timeout_s = 60,
    };
    wifi_service_handle_t wifi_service = wifi_service_create(&wifi_service_config);
    
    // 创建BLUFI配网
    esp_wifi_setting_handle_t blufi_handle = blufi_config_create(NULL);
    
    // 注册BLUFI配网到WiFi服务
    wifi_service_register_setting_handle(wifi_service, blufi_handle, "blufi");
    
    // 启动WiFi服务
    wifi_service_set_sta_info(wifi_service, WIFI_CONFIG_RECONNECT);
    wifi_service_connect(wifi_service);
    
    // 设置并发送自定义数据(可选)
    char *custom_data = "Hello from ESP32";
    blufi_set_customized_data(blufi_handle, custom_data, strlen(custom_data));
    
    // WiFi连接成功后发送自定义数据
    esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, wifi_connected_handler, blufi_handle);
}

static void wifi_connected_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
    esp_wifi_setting_handle_t blufi_handle = (esp_wifi_setting_handle_t)arg;
    
    // 设置WiFi连接标志
    blufi_set_sta_connected_flag(blufi_handle, true);
    
    // 发送自定义数据
    blufi_send_customized_data(blufi_handle);
}

总结

ESP-ADF的BLUFI配网模块提供了一种安全、可靠的通过蓝牙配置WiFi的方法,特别适用于没有屏幕或按键的智能音箱、智能开关等设备。该模块具有以下优势:

  1. 安全性强:使用DH密钥交换和AES加密保护配网数据
  2. 可靠性高:BLE通信稳定,有完整的错误处理机制
  3. 用户体验好:无需切换WiFi网络,直接通过蓝牙配置
  4. 扩展性强:支持自定义数据传输,可用于设备注册、身份认证等

与其他配网方式相比,BLUFI是一种更现代、更安全的选择,特别适合需要高安全性的IoT设备。

blufi_security.c 安全机制详解

为了确保WiFi配网过程中的数据安全,BLUFI采用了多层安全机制,主要通过blufi_security.c文件来实现。下面将详细分析该文件中的关键组件和工作原理。

安全结构体

/**
 * @brief BLUFI安全机制结构体
 * 
 * 该结构体包含了BLUFI安全机制所需的各种密钥和加密上下文数据,
 * 用于实现DH密钥交换和AES加密通信。
 */
struct blufi_security {
#define DH_SELF_PUB_KEY_LEN     128        /* 自身公钥的长度(字节) */
#define DH_SELF_PUB_KEY_BIT_LEN (DH_SELF_PUB_KEY_LEN * 8)  /* 公钥长度(比特) */
    uint8_t  self_public_key[DH_SELF_PUB_KEY_LEN];   /* 存储本地生成的DH公钥 */
    
#define SHARE_KEY_LEN           128        /* 共享密钥长度(字节) */
#define SHARE_KEY_BIT_LEN       (SHARE_KEY_LEN * 8)  /* 共享密钥长度(比特) */
    uint8_t  share_key[SHARE_KEY_LEN];    /* DH协商后生成的共享密钥,用于加密数据 */
    size_t   share_len;                   /* 共享密钥的实际长度 */
    
#define PSK_LEN                 16         /* 预共享密钥长度,用于AES-128加密 */
    uint8_t  psk[PSK_LEN];                /* 由共享密钥生成的AES密钥 */
    
    uint8_t  *dh_param;                    /* DH参数,包含素数p和生成元g */
    int      dh_param_len;                /* DH参数的长度 */
    
    uint8_t  iv[16];                      /* AES-CFB模式的初始化向量(IV) */
    
    mbedtls_dhm_context dhm;              /* mbedTLS的DH密钥协商上下文 */
    mbedtls_aes_context aes;              /* mbedTLS的AES加密上下文 */
};

这个结构体是BLUFI安全实现的核心,包含以下重要字段:

  1. self_public_key:设备端的DH公钥,长度为128字节
  2. share_key:通过DH算法生成的共享密钥,长度为128字节
  3. psk:从share_key派生的预共享密钥,用于AES加密,长度为16字节
  4. dh_param:临时存储DH参数的缓冲区
  5. iv:初始化向量,用于AES-CFB模式加密
  6. dhm:mbedtls提供的DH上下文
  7. aes:mbedtls提供的AES上下文

随机数生成函数

/**
 * @brief 安全随机数生成函数
 * 
 * 该函数为mbedTLS库提供安全的随机数生成服务,用于DH密钥生成和其他密码学操作。
 * 它遵循了mbedTLS的rng_callback函数原型,作为密码学函数的随机数源。
 * 
 * 安全性说明:
 * - 使用ESP-IDF的esp_random()函数,它是硬件生成的真随机数,比伪随机数更安全
 * - 在密码学中,随机数质量直接影响密钥的安全性,善意的攻击者可能通过预测伪随机数序列来破解密钥
 * 
 * @param rng_state 随机数生成器状态(本函数未使用)
 * @param output 输出缓冲区,存储生成的随机字节
 * @param len 需要生成的随机字节数
 * 
 * @return ESP_OK 表示随机数生成成功
 */
static int myrand(void *rng_state, unsigned char *output, size_t len)
{
    size_t i;
    for (i = 0; i < len; ++i) {
        output[i] = esp_random();  // 使用ESP-IDF提供的硬件随机数生成器
    }

    return ESP_OK;
}

该函数是一个自定义随机数生成器,用于DH密钥生成过程。它使用ESP-IDF的esp_random()函数生成安全随机数,这对于密码学应用至关重要。

DH密钥协商处理

/**
 * @brief DH密钥协商数据处理函数,实现安全的Diffie-Hellman密钥交换
 * 
 * @param data 输入的协商数据
 * @param len 输入数据的长度
 * @param output_data 输出数据的指针,用于存储要发送给对方的数据
 * @param output_len 输出数据的长度
 * @param need_free 指示输出数据是否需要释放
 */
void blufi_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_data, int *output_len, bool *need_free)
{
    int ret;
    uint8_t type = data[0];  // 第一个字节定义了协商数据的类型

    // 检查安全上下文是否初始化
    if (blufi_sec == NULL) {
        ESP_LOGE(BLUFI_SECURITY_TAG, "BLUFI Security is not initialized");
        return;
    }

    // 根据数据类型进行处理
    switch (type) {
        case SEC_TYPE_DH_PARAM_LEN:
            // 处理DH参数长度信息
            // 从数据中提取参数长度并分配内存缓冲区
            blufi_sec->dh_param_len = ((data[1] << 8) | data[2]);  // 组合第2、3字节为16位长度值
            
            // 释放旧的参数缓冲区(如果存在)
            if (blufi_sec->dh_param) {
                audio_free(blufi_sec->dh_param);
                blufi_sec->dh_param = NULL;
            }
            
            // 分配新的参数缓冲区
            blufi_sec->dh_param = (uint8_t *)audio_calloc(1, blufi_sec->dh_param_len);
            if (blufi_sec->dh_param == NULL) {
                ESP_LOGE(BLUFI_SECURITY_TAG, "%s, Malloc failed", __func__);
                return;
            }
            break;
            
        case SEC_TYPE_DH_PARAM_DATA:
            // 处理DH参数数据,包含素数p和生成器g
            if (blufi_sec->dh_param == NULL) {
                ESP_LOGE(BLUFI_SECURITY_TAG, "%s, Blufi_sec->dh_param == NULL", __func__);
                return;
            }
            
            // 复制DH参数数据
            uint8_t *param = blufi_sec->dh_param;
            memcpy(blufi_sec->dh_param, &data[1], blufi_sec->dh_param_len);
            
            // 读取参数到DH上下文中
            ret = mbedtls_dhm_read_params(&blufi_sec->dhm, &param, &param[blufi_sec->dh_param_len]);
            if (ret) {
                ESP_LOGE(BLUFI_SECURITY_TAG, "%s Read param failed %d", __func__, ret);
                return;
            }
            
            // 释放临时参数缓冲区
            audio_free(blufi_sec->dh_param);
            blufi_sec->dh_param = NULL;
            
            // 根据ESP-IDF版本选择适合的API生成DH公钥
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0))
            const int dhm_len = mbedtls_dhm_get_len(&blufi_sec->dhm);
            ret = mbedtls_dhm_make_public(&blufi_sec->dhm, dhm_len, blufi_sec->self_public_key, dhm_len, myrand, NULL);
#else
            ret = mbedtls_dhm_make_public(&blufi_sec->dhm, (int) mbedtls_mpi_size( &blufi_sec->dhm.P ), blufi_sec->self_public_key, blufi_sec->dhm.len, myrand, NULL);
#endif
            if (ret) {
                ESP_LOGE(BLUFI_SECURITY_TAG, "%s Make public failed %d", __func__, ret);
                return;
            }

            // 使用DH算法计算共享密钥
            mbedtls_dhm_calc_secret(&blufi_sec->dhm,
                                    blufi_sec->share_key,
                                    SHARE_KEY_BIT_LEN,
                                    &blufi_sec->share_len,
                                    NULL, NULL);

            // 使用MD5生成AES加密的密钥
            mbedtls_md5(blufi_sec->share_key, blufi_sec->share_len, blufi_sec->psk);

            // 初始化AES加密上下文,使用生成的PSK作为密钥
            mbedtls_aes_setkey_enc(&blufi_sec->aes, blufi_sec->psk, 128);

            // 设置输出数据为自己的公钥,返回给对方
            *output_data = &blufi_sec->self_public_key[0];
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0))
            *output_len = dhm_len;
#endif
            *need_free = false;  // 指示这里返回的数据不需要释放,因为使用的是结构体内的内存
            break;
            
        case SEC_TYPE_DH_P:  // 其他DH参数类型,当前未实现处理
            break;
        case SEC_TYPE_DH_G:
            break;
        case SEC_TYPE_DH_PUBLIC:
            break;
    }
}

DH密钥协商时序图

下图展示了BLUFI安全机制中的Diffie-Hellman密钥交换完整流程,直观描述了手机APP与ESP设备之间如何建立安全的通信通道:

手机APP ESP设备 已建立蓝牙连接 SEC_TYPE_DH_PARAM_LEN 分配参数缓冲区 SEC_TYPE_DH_PARAM_DATA 1. 读取参数(p,g) 2. 生成私钥a 3. 计算公钥A=g^a mod p ESP的公钥A 已知自己的私钥b 计算s = A^b mod p 计算s = B^a mod p 两端生成相同的共享密钥s 1. 对s做MD5生成PSK 2. 使用PSK初始化AES 相同操作 安全通道建立完成 AES加密的数据 AES加密的数据 手机APP ESP设备

上图展示了BLUFI安全机制中的Diffie-Hellman密钥协商流程,它包含以下关键步骤:

  1. 参数准备:手机APP发送DH参数长度和参数数据(包括素数p和生成元g)
  2. 公钥交换
    • ESP设备生成私钥a和公钥A = g^a mod p
    • 设备将公钥A发送给手机APP
    • 手机APP有自己的私钥b和公钥B = g^b mod p(这一步在手机端完成)
  3. 共享密钥计算
    • ESP设备使用私钥a和收到的手机公钥B计算共享密钥s = B^a mod p
    • 手机APP使用私钥b和ESP公钥A计算共享密钥s = A^b mod p
    • 数学原理保证两边计算的s相同,而中间人无法预算出密钥
  4. AES加密密钥生成
    • 对共享密钥s进行MD5计算,生成128位AES密钥(PSK)
    • 初始化AES上下文以准备加密通信
  5. 建立安全通道
    • 使用协商好的AES密钥加密所有后续通信数据(WiFi配置等)
    • 确保全部数据传输过程的机密性和完整性

这个函数是BLUFI安全协商的核心,处理DH密钥交换过程中的各种数据类型:

  1. SEC_TYPE_DH_PARAM_LEN:处理DH参数长度信息,分配相应大小的内存
  2. SEC_TYPE_DH_PARAM_DATA:处理实际的DH参数数据,主要包括:
    • 读取DH参数(素数p和生成器g)
    • 生成本地的DH公钥
    • 计算共享密钥
    • 使用MD5从共享密钥派生PSK
    • 初始化AES加密上下文

AES加密与解密

/**
 * @brief 对数据进行AES-128-CFB模式加密
 * 
 * 该函数使用AES-128-CFB模式对数据进行加密,以保证传输过程中的数据安全。
 * 主要特点如下:
 * 1. 使用基于mbedTLS的AES加密实现
 * 2. 采用CFB模式,支持任意长度数据的加密
 * 3. 每次加密可使用不同的iv8实现动态IV,增强安全性
 * 4. 支持原地加密,即加密结果替换原始数据
 * 
 * @param iv8 初始化向量的首字节值,用于增强安全性,每次加密应使用不同值
 * @param crypt_data 要加密的数据缓冲区,加密结果也存放在此缓冲区
 * @param crypt_len 要加密的数据长度,字节单位
 * @return int 成功返回加密后的数据长度,失败返回ESP_FAIL
 **/
int blufi_aes_encrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len)
{
    int ret;
    size_t iv_offset = 0;  // CFB模式的IV偏移量,初始为0
    uint8_t iv0[16];       // 初始化向量缓冲区,AES-128需要128位(16字节)的IV

    // 复制默认IV到临时缓冲区
    memcpy(iv0, blufi_sec->iv, sizeof(blufi_sec->iv));
    /* 设置iv8作为IV的第一个字节,从而使每次加密使用不同的IV */
    iv0[0] = iv8;

    // 使用mbedTLS的AES-CFB128模式进行加密
    // 参数说明:
    // 1. &blufi_sec->aes - AES上下文
    // 2. MBEDTLS_AES_ENCRYPT - 指定加密操作
    // 3. crypt_len - 要加密的数据长度
    // 4. &iv_offset - IV偏移量的指针
    // 5. iv0 - 初始化向量
    // 6. crypt_data - 输入数据
    // 7. crypt_data - 输出数据(与输入相同,原地加密)
    ret = mbedtls_aes_crypt_cfb128(&blufi_sec->aes, MBEDTLS_AES_ENCRYPT, crypt_len, &iv_offset, iv0, crypt_data, crypt_data);
    if (ret) {
        return ESP_FAIL;  // 加密失败时返回错误码
    }

    return crypt_len;  // 成功时返回加密后的数据长度
}

/**
 * @brief 对数据进行AES-128-CFB模式解密
 * 
 * 该函数与blufi_aes_encrypt对应,用于解密通过BLUFI协议收到的加密数据。
 * 该函数具有的特点:
 * 1. 使用与加密相同的AES-128-CFB模式
 * 2. 要求iv8参数必须与加密时使用的相同,否则无法正确解密
 * 3. 同样支持原地解密,减少内存使用
 * 
 * @param iv8 初始化向量的首字节值,必须与加密时使用的相同
 * @param crypt_data 要解密的数据缓冲区,解密结果也存放在此缓冲区
 * @param crypt_len 要解密的数据长度,字节单位
 * @return int 成功返回解密后的数据长度,失败返回ESP_FAIL
**/
int blufi_aes_decrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len)
{
    int ret;
    size_t iv_offset = 0;  // CFB模式的IV偏移量,初始为0
    uint8_t iv0[16];       // 初始化向量缓冲区

    // 复制默认IV到临时缓冲区
    memcpy(iv0, blufi_sec->iv, sizeof(blufi_sec->iv));
    /* 设置iv8作为IV的第一个字节,必须与加密时使用的相同 */
    iv0[0] = iv8;

    // 使用mbedTLS的AES-CFB128模式进行解密,参数与加密函数类似
    // 不同的是使用MBEDTLS_AES_DECRYPT指定解密操作
    ret = mbedtls_aes_crypt_cfb128(&blufi_sec->aes, MBEDTLS_AES_DECRYPT, crypt_len, &iv_offset, iv0, crypt_data, crypt_data);
    if (ret) {
        return ESP_FAIL;  // 解密失败时返回错误码
    }

    return crypt_len;  // 成功时返回解密后的数据长度
}

这两个函数分别实现了数据的加密和解密:

  1. 使用AES-128-CFB模式进行加密/解密
  2. 使用动态IV机制,其中第一个字节会被替换为传入的iv8参数,增强安全性
  3. 提供原地加密/解密,即输入和输出使用相同的缓冲区
  4. 返回处理的数据长度或失败状态

CRC校验和计算

/**
 * @brief 计算数据的CRC16校验和
 * 
 * 该函数计算数据的CRC16校验和,用于验证BLUFI协议传输的数据完整性。
 * 主要特点如下:
 * 1. 使用标准的CRC16-BE(大端序)算法
 * 2. 初始值为0
 * 3. 接口兼容其他安全函数,包含iv8参数但不使用
 * 
 * @param iv8 本函数不使用此参数,仅为了保持接口一致性
 * @param data 需要计算校验和的数据指针
 * @param len 数据长度,字节单位
 * @return uint16_t 返回16位CRC校验和值
**/
uint16_t blufi_crc_checksum(uint8_t iv8, uint8_t *data, int len)
{
    /* 这里忽略iv8参数,不使用 */
    // 使用ESP-IDF提供的大端序CRC16算法
    // 参数说明:
    // 1. 0 - CRC初始值
    // 2. data - 要计算的数据指针
    // 3. len - 数据长度
    return crc16_be(0, data, len);  // 返回计算出的CRC16校验和
}

该函数计算数据的CRC-16校验和,用于验证数据完整性。它使用ESP-IDF提供的crc16_be函数,计算大端序CRC值。

安全模块初始化与销毁

/**
 * @brief 初始化BLUFI安全模块
 * 
 * 该函数初始化BLUFI安全组件,为DH密钥交换和AES加密做准备。
 * 主要操作包括:
 * 1. 分配安全结构体内存并清零
 * 2. 初始化mbedTLS库的DH和AES上下文
 * 3. 初始化IV向量
 * 
 * 该函数会在蓝牙连接建立时被调用。
 * 
 * @return esp_err_t 成功返回ESP_OK,失败返回ESP_FAIL
**/
esp_err_t blufi_security_init(void)
{
    // 1. 分配并清零安全结构体内存
    blufi_sec = (struct blufi_security *)audio_calloc(1, sizeof(struct blufi_security));
    if (blufi_sec == NULL) {
        return ESP_FAIL;  // 内存分配失败则返回错误
    }

    // 2. 初始化mbedTLS的DH和AES上下文
    mbedtls_dhm_init(&blufi_sec->dhm);  // 初始化DH上下文
    mbedtls_aes_init(&blufi_sec->aes);   // 初始化AES上下文

    // 3. 初始化IV向量为全零
    memset(blufi_sec->iv, 0x0, 16);  // 设置16字节的IV初始值为0
    return ESP_OK;  // 初始化成功
}

/**
 * @brief 销毁BLUFI安全模块
 * 
 * 该函数负责清理并释放BLUFI安全模块使用的资源,主要操作包括:
 * 1. 释放DH参数缓冲区(如果存在)
 * 2. 清理mbedTLS的DH和AES上下文
 * 3. 清零敏感数据(密钥等)
 * 4. 释放安全结构体内存
 * 
 * 该函数会在蓝牙连接断开或配网结束时被调用。
 * 
 * @return esp_err_t 成功返回ESP_OK,失败返回ESP_FAIL
**/
esp_err_t blufi_security_deinit(void)
{
    // 检查安全上下文是否存在
    if (blufi_sec == NULL) {
        return ESP_FAIL;  // 上下文不存在,返回错误
    }
    
    // 释放DH参数缓冲区(如果存在)
    if (blufi_sec->dh_param) {
        audio_free(blufi_sec->dh_param);
        blufi_sec->dh_param = NULL;
    }
    
    // 清理mbedTLS的DH和AES上下文
    mbedtls_dhm_free(&blufi_sec->dhm);  // 释放DH上下文资源
    mbedtls_aes_free(&blufi_sec->aes);   // 释放AES上下文资源

    // 清零并释放安全结构体
    memset(blufi_sec, 0x0, sizeof(struct blufi_security));  // 清零敏感数据

    audio_free(blufi_sec);  // 释放结构体内存
    blufi_sec = NULL;      // 重置指针

    return ESP_OK;  // 清理成功
}

这两个函数分别用于初始化和销毁BLUFI安全模块:

  1. blufi_security_init

    • 分配安全结构体内存
    • 初始化DH和AES上下文
    • 初始化IV向量
  2. blufi_security_deinit

    • 释放DH参数内存
    • 清理DH和AES上下文
    • 释放安全结构体内存

安全协议流程

BLUFI安全协议的完整流程如下:

ESP设备 手机APP blufi_security_init() 连接BLE 开始DH密钥交换过程 发送SEC_TYPE_DH_PARAM_LEN 分配DH参数缓冲区 发送SEC_TYPE_DH_PARAM_DATA (含素数p和生成器g) 读取DH参数 生成本地DH公钥 计算共享密钥 派生PSK (使用MD5) 初始化AES加密 发送设备端DH公钥 加密通道建立完成 发送加密的WiFi配置信息 解密配置信息 发送加密的状态信息 ESP设备 手机APP

安全措施分析

BLUFI安全机制融合了多种密码学技术,提供了较强的安全保障:

  1. 密钥交换安全

    • 使用DH算法安全地在不安全信道上建立共享密钥
    • 使用1024位DH密钥,具有足够的安全强度
    • 动态生成参数,避免固定密钥被破解
  2. 数据加密安全

    • 使用AES-128-CFB模式提供数据机密性
    • 动态IV机制增强安全性,防止重放攻击
    • 每次加密使用不同的IV首字节
  3. 数据完整性保护

    • 使用CRC-16校验和验证数据完整性
    • 防止数据被篡改或损坏
  4. 内存安全

    • 使用动态内存分配和适当的错误处理
    • 及时释放敏感数据所占用的内存
    • 初始化过程中使用安全的清零操作

与标准库的集成

BLUFI安全模块充分利用了mbedTLS库提供的密码学功能,而不是自行实现密码算法:

  1. 使用mbedtls_dhm进行DH密钥交换
  2. 使用mbedtls_aes进行AES加密
  3. 使用mbedtls_md5进行密钥派生

这种方式充分利用了经过安全审计和验证的密码库,减少了潜在的安全漏洞。

版本兼容性处理

代码中包含了对不同ESP-IDF版本的适配:

#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0))
    const int dhm_len = mbedtls_dhm_get_len(&blufi_sec->dhm);
    ret = mbedtls_dhm_make_public(&blufi_sec->dhm, dhm_len, blufi_sec->self_public_key, dhm_len, myrand, NULL);
#else
    ret = mbedtls_dhm_make_public(&blufi_sec->dhm, (int) mbedtls_mpi_size( &blufi_sec->dhm.P ), blufi_sec->self_public_key, blufi_sec->dhm.len, myrand, NULL);
#endif

这种适配确保了代码在不同版本的ESP-IDF上的兼容性,主要针对:

  1. ESP-IDF 4.0及以上版本中CRC函数位置的变化
  2. ESP-IDF 5.0及以上版本中mbedTLS API的变化
  3. 不同版本中随机数生成API的变化

安全实现的优势

BLUFI安全实现的主要优势包括:

  1. 安全性:使用成熟的DH+AES加密方案,提供较高级别的安全保障
  2. 可移植性:通过使用mbedTLS库,代码具有良好的可移植性
  3. 灵活性:支持自定义数据传输,可用于设备绑定等高级场景
  4. 兼容性:处理不同ESP-IDF版本之间的API变化
  5. 资源效率:针对ESP32等资源受限设备进行了优化

安全最佳实践

从BLUFI安全实现中可以总结出以下安全最佳实践:

  1. 使用标准密码学库而非自行实现
  2. 实现动态密钥协商而非硬编码密钥
  3. 加密前对数据进行校验和保护
  4. 密钥派生使用单向散列函数
  5. 适当处理内存分配和释放,避免泄露
  6. 安全相关操作增加错误检查和日志

潜在安全风险

尽管BLUFI实现了较完善的安全机制,但仍存在一些潜在风险:

  1. DH参数如果选择不当可能导致弱密钥
  2. CRC-16提供完整性检查但不能抵御有针对性的篡改
  3. 没有明确的会话管理机制
  4. 缺乏身份认证环节,可能面临中间人攻击

针对这些风险,在实际应用中可以考虑:

  1. 结合设备绑定流程增强身份认证
  2. 考虑增加消息认证码(MAC)以增强数据完整性保护
  3. 实现更完善的会话管理机制
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

omnibots

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值