安全机制

经典蓝牙与 BLE 如何保证数据传输安全,包括配对、加密、认证机制详解。

🔐 经典蓝牙 vs BLE 安全对比

flowchart TB subgraph Classic["经典蓝牙安全"] C1["PIN Code 配对"] C2["SAFER+ 加密"] C3["E0 流密码"] C4["8-16 位 PIN"] end subgraph BLE["BLE 安全"] B1["LE Legacy AES-CCM"] B2["LE SC P-256 ECDH"] B3["6 位 OOB Numeric"] B4["AES-128 加密"] end style Classic fill:#fff3cd,stroke:#ffc107 style BLE fill:#e3f2fd,stroke:#007bff
特性 经典蓝牙 (BR/EDR) BLE (低功耗)
配对方式 PIN Code (8-16 位) Passkey (6 位)/OOB/Numeric
加密算法 E0 流密码 / SAFER+ AES-128 CCM
密钥长度 8-16 bytes 7-16 bytes
密钥生成 基于 PIN + BD_ADDR ECDH 密钥交换
MITM 保护 可选 LE SC 强制
安全版本 SSP (2.1+) LE SC (4.2+)

📡 经典蓝牙安全机制

1. 配对流程 (Pairing)

sequenceDiagram participant A as 设备 A 手机主机 participant B as 设备 B 耳机从机 Note over A,B: 场景 1 固定 PIN 码 最常见 Note over B: 设备 B 有固定 PIN 如 0000 A->>A: 用户输入 PIN 0000 A->>A: 生成 Kinit = E PIN BD_ADDR_B B->>B: 使用固定 PIN 生成 Kinit Note over A,B: 场景 2 设备 B 显示 PIN Note over B: 设备 B 显示 PIN 如 123456 A->>A: 用户输入显示的 PIN A->>A: 生成 Kinit = E PIN BD_ADDR_B B->>B: 使用已知 PIN 生成 Kinit Note over A,B: 阶段 2 链路密钥生成 A->>B: 随机数 AU_RAND_A B->>A: 随机数 AU_RAND_B A->>A: 生成 Klink = E2 Kinit AU_RAND_A BD_ADDR_A B->>B: 生成 Klink = E2 Kinit AU_RAND_B BD_ADDR_B Note over A,B: 阶段 3 认证 A->>B: 认证请求 AU_RAND_A B->>A: 认证响应 SRES = E1 Klink AU_RAND_A BD_ADDR_B A->>A: 验证 SRES Note over A,B: 阶段 4 加密启用 A->>B: 加密请求 A->>A: 生成加密密钥 Kc = E3 Klink COF_RAND BD_ADDR B->>B: 生成加密密钥 Kc A->>B: 加密数据 E0 流密码

常见 PIN 码场景:

2. 加密流程

// 经典蓝牙加密密钥生成

// 步骤 1: 生成初始化密钥 Kinit
// 使用 SAFER+ 算法,基于 PIN 码和设备地址
Kinit = SAFER+(PIN, BD_ADDR, 128)

// 步骤 2: 生成链路密钥 Klink
// 双方交换随机数,生成共享密钥
Klink = E(Kinit, AU_RAND_A XOR AU_RAND_B)

// 步骤 3: 生成加密密钥 Kc (8-16 bytes)
Kc = E(Klink, COF_RAND, BD_ADDR)

// 步骤 4: E0 流密码加密
// 使用 Kc 初始化 E0 流密码生成器
// 每时隙生成 1 bit 密钥流
ciphertext = plaintext XOR keystream

// E0 流密码特点:
// - 基于 4 个 LFSR (线性反馈移位寄存器)
// - 密钥长度可变 (8-16 bytes)
// - 每时隙更新密钥流

3. 安全模式

模式 说明 安全级别
Mode 1 非安全模式 无安全
Mode 2 服务级安全 按需加密
Mode 3 链路级安全 强制加密
SSP (2.1+) 安全简单配对 Numeric/Passkey/OOB

📶 BLE 安全机制

1. LE Legacy (BLE 4.0-4.1)

sequenceDiagram participant P as 外设 Peripheral participant C as 中心 Central Note over P,C: Phase 1 配对特性交换 P->>C: 配对请求 IO 能力、认证要求 C->>P: 配对响应 Note over P,C: Phase 2 临时密钥生成 P->>P: 生成 TK Temporary Key C->>C: 生成 TK Temporary Key Note over P,C: 配对方式 Note over P,C: Just Works TK = 0 Note over P,C: Passkey TK = 6 位数字 Note over P,C: OOB TK = 带外数据 Note over P,C: Phase 3 短期密钥 STK 生成 P->>C: 随机数 Ra C->>P: 随机数 Rb P->>P: STK = AES TK Ra XOR Rb C->>C: STK = AES TK Ra XOR Rb Note over P,C: Phase 4 长期密钥 LTK 分发 P->>C: 加密传输 LTK C->>C: 存储 LTK 用于重连

2. LE Secure Connections (BLE 4.2+)

sequenceDiagram participant P as 外设 Peripheral participant C as 中心 Central Note over P,C: Phase 1 配对特性交换 P->>C: 配对请求 LE SC 支持 C->>P: 配对响应 Note over P,C: Phase 2 公钥交换 ECDH P-256 P->>P: 生成私钥 SKa 公钥 PKa C->>C: 生成私钥 SKb 公钥 PKb P->>C: 发送公钥 PKa C->>P: 发送公钥 PKb Note over P,C: Phase 3 DHKey 计算 P->>P: DHKey = ECDH SKa PKb C->>C: DHKey = ECDH SKb PKa Note over P,C: Phase 4 认证 Numeric Comparison P->>P: 计算确认值 Va C->>C: 计算确认值 Vb P->>P: 显示 6 位数字 Va mod 1000000 C->>C: 显示 6 位数字 Vb mod 1000000 Note over P,C: 用户确认数字相同 Note over P,C: Phase 5 密钥生成 P->>P: LTK = f DHKey 随机数 C->>C: LTK = f DHKey 随机数 Note over P,C: 加密连接建立

3. BLE 加密密钥层次

// BLE 密钥层次结构

// ========== LE Legacy (BLE 4.0-4.1) ==========
// 1. TK (Temporary Key) - 临时密钥
//    - Just Works: TK = 0
//    - Passkey: TK = 6 位数字 (20 bits)
//    - OOB: TK = 128 位带外数据

// 2. STK (Short Term Key) - 短期密钥
//    STK = AES128(TK, S1(Ra || Rb))
//    - Ra, Rb: 双方生成的 128 位随机数
//    - S1: 取低 64 位
//    - 用于初始加密连接

// 3. LTK (Long Term Key) - 长期密钥
//    - 通过加密连接分发
//    - 存储用于后续重连
//    - 8-16 bytes

// 4. IRK (Identity Resolving Key) - 身份解析密钥
//    - 用于生成私有地址
//    - 保护设备隐私

// 5. CSRK (Connection Signature Resolving Key)
//    - 用于数据签名

// ========== LE Secure Connections (BLE 4.2+) ==========
// 1. DHKey (Diffie-Hellman Key) - 256 位
//    DHKey = ECDH_P256(SKa, PKb) = ECDH_P256(SKb, PKa)
//    - SKa/SKb: 私钥
//    - PKa/PKb: 公钥

// 2. MacKey (128 位) - 用于认证
//    MacKey = f(DHKey, N1 || N2, "btle")

// 3. LTK (Long Term Key) - 128 位
//    LTK = f(DHKey, N1 || N2, "btlk")
//    - N1, N2: 双方随机数
//    - f: AES-CMAC 函数

// 4. IRK, CSRK (同上)

🔑 配对流程详解

BLE 配对方式对比

配对方式 IO 能力要求 MITM 保护 安全性 典型场景
Just Works 无显示/无输入 ❌ 无 手环、传感器
Passkey Entry 键盘或显示 ✅ 有 键盘、电视
Numeric Comparison 双方都有显示 ✅ 有 手机对手机
OOB (Out of Band) NFC/其他通道 ✅ 有 最高 NFC 配对

配对代码示例

// Nordic SDK - BLE 配对配置

void ble_gap_sec_params_init(void) {
    ble_gap_sec_params_t sec_params = {0};
    
    // 配对配置
    sec_params.bond = 1;           // 允许绑定 (存储 LTK)
    sec_params.mitm = 1;           // 需要 MITM 保护
    sec_params.lesc = 1;           // 使用 LE Secure Connections
    sec_params.keypress = 0;       // 不支持按键确认
    sec_params.io_caps = BLE_GAP_IO_CAPS_DISPLAY_YESNO;  // 有显示,可确认
    sec_params.oob = 0;            // 无 OOB 数据
    sec_params.min_key_size = 16;  // 最小密钥长度 16 bytes
    sec_params.max_key_size = 16;  // 最大密钥长度 16 bytes
    
    // 密钥分发配置
    sec_params.kdist_own.enc = 1;  // 分发加密密钥 (LTK)
    sec_params.kdist_own.id = 1;   // 分发身份密钥 (IRK)
    sec_params.kdist_peer.enc = 1; // 接收对端加密密钥
    sec_params.kdist_peer.id = 1;  // 接收对端身份密钥
    
    // 回复配对请求
    sd_ble_gap_sec_params_reply(m_conn_handle,
                                 BLE_GAP_SEC_STATUS_SUCCESS,
                                 &sec_params,
                                 NULL);
}

// 配对事件处理
void on_ble_gap_evt_auth_status(ble_gap_evt_auth_status_t *p_auth_status) {
    if (p_auth_status->auth_status == BLE_GAP_SEC_STATUS_SUCCESS) {
        // 配对成功
        // p_auth_status->bonded: 是否已绑定
        // p_auth_status->enc_mode: 加密模式
        // p_auth_status->key_size: 密钥长度 (7-16 bytes)
        
        // 存储 LTK 到 Flash
        if (p_auth_status->bonded) {
            store_ltk_to_flash(&p_auth_status->keys);
        }
    } else {
        // 配对失败
        // BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP: 不支持配对
        // BLE_GAP_SEC_STATUS_PASSKEY_ENTRY_FAILED: 密码错误
        // BLE_GAP_SEC_STATUS_OOB_NOT_AVAILABLE: OOB 数据不可用
    }
}

🛡️ 防止其他手机应用读取数据

BLE 设备如何防止同一手机上的其他未授权应用读取数据。

1. 连接级别保护 (加密绑定)

sequenceDiagram participant Device as BLE 设备 participant App1 as 授权 App participant App2 as 其他 App participant Phone as 手机系统 Note over Device,App1: 阶段 1 首次配对 App1->>Device: 配对请求 Device->>App1: 显示 6 位数字 App1->>App1: 用户确认数字 App1->>Device: 确认完成 Device->>App1: 加密连接建立 Device->>Device: 存储 App1 的 IRK LTK Note over Device,App2: 阶段 2 其他 App 尝试连接 App2->>Device: 扫描发现设备 App2->>Device: 连接请求 Device->>Device: 检查绑定列表 Device->>App2: 拒绝 未绑定 Note over Device,Phone: 阶段 3 系统级保护 Phone->>Phone: 每个 App 独立的蓝牙权限 Phone->>App2: 无权限访问已配对设备

2. GATT 权限控制

保护级别 实现方式 效果
加密访问 要求加密连接才能读取特征值 未配对 App 无法读取
绑定检查 检查设备是否在绑定列表 只有配对的手机可访问
权限分级 不同特征值不同安全级别 敏感数据需要更高权限
应用认证 自定义应用层认证 二次验证确保授权

3. 固件实现示例

// Nordic SDK - GATT 权限控制

// 配置特征值安全级别
ble_gatts_char_md_t char_md = {0};
ble_gatts_attr_md_t attr_md = {0};

// 安全级别配置
attr_md.vloc = BLE_GATTS_VLOC_STACK;  // 存储在协议栈
attr_md.vlen = 1;                      // 可变长度

// 读取权限:需要加密连接
attr_md.read_perm.sm = 1;              // 安全模式 1
attr_md.read_perm.lv = 4;              // 安全级别 4 (需要 MITM + 加密)

// 写入权限:需要绑定
attr_md.write_perm.sm = 1;
attr_md.write_perm.lv = 4;

// 应用特征值
char_md.char_props.read = 1;
char_md.char_props.write = 1;
char_md.char_props.notify = 1;

// 添加特征值
sd_ble_gatts_characteristic_add(service_handle, 
                                 &char_md, 
                                 &attr, 
                                 &char_handle);

// 检查连接是否加密
bool is_connection_encrypted(uint16_t conn_handle) {
    ble_gap_conn_sec_t conn_sec;
    sd_ble_gap_conn_sec_get(conn_handle, &conn_sec);
    
    // 检查加密模式
    return (conn_sec.enc_mode.encryption > 0);
}

// 检查是否已绑定
bool is_bonded(ble_gap_addr_t *p_addr) {
    ble_gap_sec_params_t sec_params;
    // 检查地址是否在绑定列表中
    return check_bonding_list(p_addr);
}

// 拒绝未授权访问
void on_gatt_read_request(ble_gatts_evt_read_t *p_evt) {
    if (!is_connection_encrypted(p_evt->conn_handle)) {
        // 连接未加密,拒绝访问
        sd_ble_gatts_rw_authority_reply(p_evt->conn_handle,
                                         BLE_GATTS_AUTHORIZE_TYPE_READ,
                                         BLE_GAP_AUTHORIZATION_STATUS_REJECT);
        return;
    }
    
    if (!is_bonded(&p_evt->peer_addr)) {
        // 设备未绑定,拒绝访问
        sd_ble_gatts_rw_authority_reply(p_evt->conn_handle,
                                         BLE_GATTS_AUTHORIZE_TYPE_READ,
                                         BLE_GAP_AUTHORIZATION_STATUS_REJECT);
        return;
    }
    
    // 授权访问
    sd_ble_gatts_rw_authority_reply(p_evt->conn_handle,
                                     BLE_GATTS_AUTHORIZE_TYPE_READ,
                                     BLE_GAP_AUTHORIZATION_STATUS_ACCEPT);
}

4. 手机系统级保护

Android

  • 蓝牙权限: BLUETOOTH_CONNECT (Android 12+)
  • 配对设备: 只有配对的 App 可访问
  • 系统隔离: 每个 App 独立的连接句柄
  • 位置权限: 扫描需要位置权限

iOS

  • 权限控制: NSBluetoothAlwaysUsageDescription
  • 配对绑定: 系统管理配对信息
  • App 隔离: 每个 App 独立访问
  • 后台限制: 后台蓝牙访问受限

最佳实践

  • 启用 LE Secure Connections (MITM 保护)
  • 要求加密连接 (Security Level 4)
  • 启用绑定 (Bonding) 存储 LTK
  • 敏感特征值设置更高权限
  • 实现应用层认证 (可选)

5. 安全级别配置建议

数据类型 读取权限 写入权限 说明
公开数据 (设备名称) 无要求 N/A 广播中可获取
一般数据 (电量) 加密 加密 需要配对
敏感数据 (健康数据) 加密 + MITM 加密 + MITM 需要确认配对
控制命令 (固件升级) N/A 加密 + 绑定 + 认证 最高安全级别

📱 设备无显示屏的配对方案

BLE 设备没有显示屏时,如何实现安全配对。

方案对比

方案 硬件要求 安全性 用户体验 典型设备
Just Works ⚠️ 低 (无 MITM) ✅ 最简单 手环、传感器
固定 PIN ⚠️ 中 (PIN 可能泄露) ✅ 简单 耳机、车载
包装印刷 PIN ✅ 高 (唯一 PIN) ✅ 简单 智能锁、医疗设备
二维码/NFC NFC 或二维码标签 ✅ 高 (OOB 认证) ✅ 扫码即可 智能家居
物理按钮确认 一个按钮 ✅ 高 (用户确认) ⚠️ 需按按钮 智能锁、开关

1. Just Works (最简单)

sequenceDiagram participant Device as BLE 设备 无屏 participant Phone as 手机 App Device->>Phone: 广播 可配对 Phone->>Device: 连接请求 Phone->>Device: 配对请求 Device->>Device: IO 能力 NoInputNoOutput Phone->>Phone: 自动确认 Just Works Device->>Phone: 配对完成 TK = 0 Device->>Phone: 加密连接建立 Note over Device,Phone: 无 MITM 保护,可能被中间人攻击
// Nordic SDK - Just Works 配置
// 适用于无显示屏、无输入能力的设备

void ble_gap_sec_params_init(void) {
    ble_gap_sec_params_t sec_params = {0};
    
    sec_params.bond = 1;           // 允许绑定
    sec_params.mitm = 0;           // ⚠️ 不需要 MITM 保护
    sec_params.lesc = 1;           // 使用 LE Secure Connections
    sec_params.io_caps = BLE_GAP_IO_CAPS_NONE;  // 无输入无输出
    
    // 密钥分发
    sec_params.kdist_own.enc = 1;
    sec_params.kdist_own.id = 1;
    sec_params.kdist_peer.enc = 1;
    sec_params.kdist_peer.id = 1;
    
    sd_ble_gap_sec_params_reply(m_conn_handle,
                                 BLE_GAP_SEC_STATUS_SUCCESS,
                                 &sec_params,
                                 NULL);
}

2. 固定 PIN 码 (设备出厂预设)

sequenceDiagram participant Device as BLE 设备 participant Phone as 手机 App participant User as 用户 Note over Device: 设备有固定 PIN Note over Device: 如 0000 或 123456 Phone->>Device: 连接请求 Phone->>User: 提示输入 PIN User->>Phone: 输入 PIN 0000 Phone->>Device: 配对请求 + PIN Device->>Device: 验证 PIN 与预设比较 Device->>Phone: 配对成功 Note over Device,Phone: PIN 可能通过产品泄露
// 固定 PIN 码配置
#define FIXED_PIN  "0000"  // 固定 PIN 码

void ble_gap_sec_params_init(void) {
    ble_gap_sec_params_t sec_params = {0};
    
    sec_params.bond = 1;
    sec_params.mitm = 0;           // ⚠️ 固定 PIN 无 MITM 保护
    sec_params.lesc = 0;           // 可使用 LE Legacy
    sec_params.io_caps = BLE_GAP_IO_CAPS_NONE;
    sec_params.oob = 0;
    
    // 设置固定 PIN
    // 注意:LE Legacy 支持固定 PIN,LE SC 需要其他方案
    
    sec_params.kdist_own.enc = 1;
    sec_params.kdist_peer.enc = 1;
    
    sd_ble_gap_sec_params_reply(m_conn_handle,
                                 BLE_GAP_SEC_STATUS_SUCCESS,
                                 &sec_params,
                                 NULL);
}

3. 包装印刷唯一 PIN (推荐)

sequenceDiagram participant Device as BLE 设备 participant Phone as 手机 App participant User as 用户 participant Box as 产品包装 Note over Box: 包装上印刷唯一 PIN Note over Box: 如 PIN A1B2C3D4 User->>User: 查看包装上的 PIN User->>Phone: 输入 PIN Phone->>Device: 配对请求 + PIN Device->>Device: 验证 PIN 与 Flash 中存储比较 Device->>Phone: 配对成功 Note over Device,Phone: 每个设备唯一 PIN,更安全
// 包装印刷唯一 PIN 实现

// 每个设备烧录唯一 PIN (生产时写入 Flash)
#define PIN_LENGTH  8
static const uint8_t device_pin[PIN_LENGTH] = {
    0xA1, 0xB2, 0xC3, 0xD4,  // 唯一 PIN 码
    0xE5, 0xF6, 0x07, 0x18
};

// 将 PIN 转换为 6 位数字用于配对
uint32_t get_passkey(void) {
    // 使用 PIN 的哈希值生成 6 位数字
    uint32_t hash = crc32(device_pin, PIN_LENGTH);
    return hash % 1000000;  // 6 位数字 (000000-999999)
}

void ble_gap_sec_params_init(void) {
    ble_gap_sec_params_t sec_params = {0};
    
    sec_params.bond = 1;
    sec_params.mitm = 1;           // ✅ 需要 MITM 保护
    sec_params.lesc = 1;           // 使用 LE Secure Connections
    sec_params.io_caps = BLE_GAP_IO_CAPS_NONE;
    sec_params.oob = 0;
    
    // 设置 OOB 数据 (使用唯一 PIN)
    sec_params.oob = 1;
    memcpy(sec_params.oob_data, device_pin, PIN_LENGTH);
    
    sec_params.kdist_own.enc = 1;
    sec_params.kdist_peer.enc = 1;
    
    sd_ble_gap_sec_params_reply(m_conn_handle,
                                 BLE_GAP_SEC_STATUS_SUCCESS,
                                 &sec_params,
                                 NULL);
}

4. 二维码/NFC (OOB 带外配对)

sequenceDiagram participant Device as BLE 设备 participant Phone as 手机 App participant User as 用户 participant QR as 二维码 NFC Note over QR: 二维码包含 Note over QR: 设备地址 Note over QR: OOB 数据 User->>Phone: 扫描二维码 Phone->>Phone: 解析 OOB 数据 Phone->>Device: 连接请求 Phone->>Device: 配对请求 + OOB 数据 Device->>Device: 验证 OOB 数据 Device->>Phone: 配对成功 Note over Device,Phone: 最高安全性,MITM 保护
// 二维码/NFC OOB 配对实现

// 生成二维码数据
// 格式:BLE:AA:BB:CC:DD:EE:FF:TK:12345678901234567890
void generate_qr_code(uint8_t *qr_data, size_t *len) {
    ble_gap_addr_t addr;
    sd_ble_gap_addr_get(&addr);
    
    // 获取 OOB 数据 (128 位 TK)
    uint8_t oob_tk[16];
    sd_rand_application_vector_get(oob_tk, 16);
    
    // 生成二维码字符串
    *len = sprintf((char*)qr_data, 
                   "BLE:%02X:%02X:%02X:%02X:%02X:%02X:TK:%032X",
                   addr.addr[5], addr.addr[4], addr.addr[3],
                   addr.addr[2], addr.addr[1], addr.addr[0],
                   oob_tk);
}

// OOB 配对配置
void ble_gap_sec_params_init(void) {
    ble_gap_sec_params_t sec_params = {0};
    ble_gap_lesc_oob_data_t oob_data;
    
    sec_params.bond = 1;
    sec_params.mitm = 1;           // ✅ MITM 保护
    sec_params.lesc = 1;           // LE Secure Connections
    sec_params.io_caps = BLE_GAP_IO_CAPS_NONE;
    sec_params.oob = 1;            // ✅ 启用 OOB
    
    // 生成 OOB 数据
    sd_ble_gap_lesc_oob_data_generate(&oob_data);
    
    // 设置 OOB 数据
    sec_params.kdist_own.enc = 1;
    sec_params.kdist_peer.enc = 1;
    
    sd_ble_gap_sec_params_reply(m_conn_handle,
                                 BLE_GAP_SEC_STATUS_SUCCESS,
                                 &sec_params,
                                 &oob_data);
}

5. 物理按钮确认 (推荐用于智能锁等)

sequenceDiagram participant Device as BLE 设备 有按钮 participant Phone as 手机 App participant User as 用户 User->>Device: 长按按钮 3 秒 Device->>Device: 进入配对模式 Device->>Phone: 广播 可配对 Phone->>Device: 连接请求 Phone->>Device: 配对请求 Note over Device: 按钮灯闪烁 User->>Device: 短按按钮确认 Device->>Phone: 配对成功 Note over Device,Phone: 用户物理确认,MITM 保护
// 物理按钮确认实现

#define BUTTON_PIN    17
#define LONG_PRESS_MS 3000  // 长按 3 秒进入配对

void button_handler(uint8_t pin_no, uint8_t button_action) {
    static uint32_t press_start = 0;
    
    if (button_action == BUTTON_PUSH) {
        press_start = app_timer_cnt_get();
    } else if (button_action == BUTTON_RELEASE) {
        uint32_t press_duration = app_timer_cnt_get() - press_start;
        
        if (press_duration >= LONG_PRESS_MS) {
            // 长按 3 秒,进入配对模式
            enter_pairing_mode();
        } else {
            // 短按,确认配对
            confirm_pairing();
        }
    }
}

void enter_pairing_mode(void) {
    ble_gap_adv_params_t adv_params = {0};
    adv_params.properties.type = BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED;
    adv_params.interval.min = 160;  // 100ms
    adv_params.interval.max = 160;
    
    // 按钮灯闪烁提示
    led_blink(3);
    
    sd_ble_gap_adv_start(&adv_params, APP_BLE_CONN_CFG_TAG);
}

void confirm_pairing(void) {
    // 按钮短按确认配对
    sd_ble_gap_auth_key_reply(m_conn_handle, 
                               BLE_GAP_AUTH_KEY_TYPE_NONE, 
                               NULL);
    
    // 按钮灯常亮提示
    led_on();
}

方案选择建议

设备类型 推荐方案 理由
手环/传感器 Just Works 成本最低,用户体验好
智能锁 物理按钮 + 唯一 PIN 高安全性,用户确认
医疗设备 包装印刷唯一 PIN 唯一性,可追溯
智能家居 二维码/NFC 扫码即可,体验好
耳机/音箱 固定 PIN (0000) 行业惯例,简单

💾 配对信息存储详解

配对成功后保存什么信息?什么情况下会重新要求配对?

1. 配对成功后保存的信息

flowchart TB subgraph Bonding["绑定信息 Bonding Data"] LTK["LTK 长期密钥 16 bytes"] IRK["IRK 身份解析密钥 16 bytes"] ADDR["对端设备地址 6 bytes"] EDIV["EDIV 加密除数 2 bytes"] RAND["RAND 随机数 8 bytes"] CSRK["CSRK 签名密钥 16 bytes"] end subgraph Storage["存储位置"] Flash["设备 Flash"] PhoneFlash["手机 Flash"] end LTK --> Flash IRK --> Flash ADDR --> Flash EDIV --> Flash RAND --> Flash CSRK --> Flash LTK --> PhoneFlash IRK --> PhoneFlash ADDR --> PhoneFlash style Bonding fill:#e3f2fd,stroke:#007bff style Storage fill:#d4edda,stroke:#28a745

保存的密钥信息

密钥 大小 用途 存储位置
LTK
Long Term Key
16 bytes 加密连接,重连时生成会话密钥 设备 + 手机
IRK
Identity Resolving Key
16 bytes 解析私有地址,识别设备 设备 + 手机
CSRK
Connection Signature Key
16 bytes 数据签名认证 设备 + 手机
EDIV + RAND 10 bytes 与 LTK 一起唯一标识密钥 设备 + 手机
设备地址 6 bytes 对端设备的 MAC 地址 设备 + 手机

2. 绑定信息存储示例

// Nordic SDK - 绑定信息存储结构

typedef struct {
    // 密钥信息
    uint8_t  ltk[16];         // 长期密钥
    uint8_t  irk[16];         // 身份解析密钥
    uint8_t  csrk[16];        // 签名密钥
    
    // 密钥标识
    uint16_t ediv;            // 加密除数
    uint8_t  rand[8];         // 随机数
    
    // 设备信息
    uint8_t  peer_addr[6];    // 对端设备地址
    uint8_t  addr_type;       // 地址类型 (公共/随机)
    
    // 标志位
    uint8_t  flags;           // 绑定标志
    // bit0: LTK 存在
    // bit1: IRK 存在
    // bit2: CSRK 存在
    // bit3: 已加密
    
} bond_info_t;

// 存储到 Flash
void store_bonding_info(bond_info_t *p_info) {
    // 擦除 Flash 扇区
    sd_flash_page_erase((uint32_t*)BONDING_FLASH_ADDR, 1);
    
    // 写入绑定信息
    sd_flash_write((uint32_t*)BONDING_FLASH_ADDR, 
                   (uint32_t*)p_info, 
                   sizeof(bond_info_t) / 4);
}

// 从 Flash 读取
void load_bonding_info(bond_info_t *p_info) {
    memcpy(p_info, 
           (void*)BONDING_FLASH_ADDR, 
           sizeof(bond_info_t));
}

// 检查是否已绑定
bool is_bonded(ble_gap_addr_t *p_peer_addr) {
    bond_info_t bond_info;
    load_bonding_info(&bond_info);
    
    // 比较设备地址
    if (memcmp(bond_info.peer_addr, 
               p_peer_addr->addr, 
               6) == 0) {
        return true;  // 已绑定
    }
    return false;  // 未绑定
}

3. 什么情况下会重新要求配对?

flowchart TB A[设备尝试连接] --> B{检查绑定列表} B -->|找到绑定 | C{密钥有效?} B -->|未找到 | D[要求配对] C -->|LTK 有效 | E[直接加密连接] C -->|LTK 失效 | D D --> F[用户确认] F --> G[生成新密钥] G --> H[更新绑定列表] H --> E style D fill:#ffc107,color:#000 style E fill:#28a745,color:#fff

需要重新配对的场景

场景 原因 解决方案
设备恢复出厂设置 Flash 中的绑定信息被清除 删除手机上的配对记录,重新配对
手机删除配对 手机清除了绑定信息 设备端也删除对应绑定,重新配对
密钥过期/失效 安全策略要求定期更新密钥 自动触发重新配对
设备地址变更 使用私有地址且 IRK 不匹配 重新配对更新 IRK
跨设备配对 设备 A 配对,用手机 B 连接 每个手机需要独立配对
固件升级后 安全协议变更或密钥格式变化 清除旧绑定,重新配对

4. 绑定管理最佳实践

// Nordic SDK - 绑定管理

// 配对完成回调
void on_ble_gap_evt_auth_status(ble_gap_evt_auth_status_t *p_auth_status) {
    if (p_auth_status->auth_status == BLE_GAP_SEC_STATUS_SUCCESS) {
        // 配对成功
        
        if (p_auth_status->bonded) {
            // 已绑定,存储密钥到 Flash
            bond_info_t bond_info;
            
            // 获取密钥信息
            ble_gap_sec_keys_t sec_keys;
            sd_ble_gap_sec_info_get(m_conn_handle, &sec_keys);
            
            // 填充绑定结构
            memcpy(bond_info.ltk, sec_keys.p_enc_key->ltk_info.ltk, 16);
            memcpy(bond_info.irk, sec_keys.p_id_key->id_info.irk, 16);
            memcpy(bond_info.csrk, sec_keys.p_sign_key->csrk, 16);
            bond_info.ediv = sec_keys.p_enc_key->ltk_info.ediv;
            memcpy(bond_info.rand, sec_keys.p_enc_key->ltk_info.rand, 8);
            
            // 获取对端地址
            ble_gap_addr_t peer_addr;
            sd_ble_gap_addr_get(&peer_addr);
            memcpy(bond_info.peer_addr, peer_addr.addr, 6);
            bond_info.addr_type = peer_addr.addr_type;
            
            // 设置标志位
            bond_info.flags = 0x07;  // LTK + IRK + CSRK 都存在
            
            // 存储到 Flash
            store_bonding_info(&bond_info);
            
            // 更新绑定计数
            increment_bond_count();
        }
    } else {
        // 配对失败处理
        handle_pairing_failure(p_auth_status->auth_status);
    }
}

// 重连时检查绑定
void on_ble_gap_evt_connected(ble_gap_evt_connected_t *p_connected) {
    ble_gap_addr_t *p_peer_addr = &p_connected->peer_addr;
    
    // 检查是否在绑定列表中
    if (is_bonded(p_peer_addr)) {
        // 已绑定,尝试加密连接
        bond_info_t bond_info;
        load_bonding_info(&bond_info);
        
        // 检查 LTK 是否有效
        if (bond_info.flags & 0x01) {  // LTK 存在
            // 使用存储的 LTK 加密连接
            ble_gap_enc_info_t enc_info;
            memcpy(enc_info.ltk, bond_info.ltk, 16);
            enc_info.ediv = bond_info.ediv;
            memcpy(enc_info.rand, bond_info.rand, 8);
            
            sd_ble_gap_sec_info_reply(m_conn_handle, &enc_info, NULL, NULL);
        } else {
            // LTK 无效,要求重新配对
            request_repairing();
        }
    } else {
        // 未绑定,要求配对
        request_pairing();
    }
}

// 删除绑定
void delete_bonding(ble_gap_addr_t *p_peer_addr) {
    bond_info_t bond_info;
    load_bonding_info(&bond_info);
    
    // 比较地址
    if (memcmp(bond_info.peer_addr, p_peer_addr->addr, 6) == 0) {
        // 清除绑定信息
        memset(&bond_info, 0, sizeof(bond_info_t));
        store_bonding_info(&bond_info);
        
        // 减少绑定计数
        decrement_bond_count();
    }
}

// 清除所有绑定
void delete_all_bondings(void) {
    // 擦除整个绑定 Flash 区域
    for (int i = 0; i < BONDING_FLASH_PAGES; i++) {
        sd_flash_page_erase((uint32_t*)(BONDING_FLASH_ADDR + i * 4096), 1);
    }
}

5. 绑定数量限制

设备类型 典型绑定数 Flash 占用 说明
手环/手表 1-2 个 ~128 bytes 通常只连接一个手机
蓝牙耳机 5-8 个 ~512 bytes 多设备切换
智能锁 10-20 个 ~1-2 KB 多家庭成员
蓝牙网关 50-100 个 ~5-10 KB 连接多个子设备

🛡️ 安全最佳实践

经典蓝牙

  • 使用 SSP (2.1+), 避免旧 PIN 配对
  • 启用 Mode 3 (链路级加密)
  • 使用 16 位 PIN 码
  • 定期更新密钥

BLE (推荐配置)

  • 启用 LE Secure Connections (4.2+)
  • 设置 mitm = 1 (MITM 保护)
  • 使用 Numeric Comparison 或 OOB
  • 密钥长度 16 bytes
  • 启用绑定 (Bonding) 存储 LTK

避免的安全问题

  • ❌ Just Works (无 MITM 保护)
  • ❌ 密钥长度 < 16 bytes
  • ❌ 不验证配对状态
  • ❌ 明文传输敏感数据
  • ❌ 使用 LE Legacy (4.0-4.1)

📊 安全级别对比

蓝牙版本 配对方式 加密算法 MITM 保护 推荐度
经典 2.0- PIN Code E0 / SAFER+ 可选 ❌ 不推荐
经典 2.1+ (SSP) Numeric/Passkey E0 / AES ✅ 支持 ⚠️ 可用
BLE 4.0-4.1 LE Legacy AES-CCM ⚠️ 部分 ⚠️ 可用
BLE 4.2+ (LE SC) ECDH P-256 AES-CCM ✅ 强制 ✅ 推荐
BLE 5.2+ (LE Audio) LE SC + LC3 AES-CCM ✅ 强制 ✅ 最佳