第4章 特征读写权限与安全边界:属性权限位、加密需求、配对绑定等级、MITM保护

4.1 原理概述

BLE GATT 协议栈按照核心规范(Core Spec 5.x)强制要求链路层(LL)与属性层(ATT)的安全协作。每个特征(Characteristic)的读写操作不仅由应用层业务逻辑决定,还受一组 属性权限位 约束,这组权限位最终映射到设备的安全策略——加密、配对绑定等级及中间人保护(MITM)。

  • 属性权限位(Attribute Permissions):GATT 服务器在声明特征时设置的比特掩码,决定是否允许读/写、是否需要加密、是否需要授权。典型位域包括:读权限(READ)、写权限(WRITE)、加密读(READ_ENCRYPTED)、加密写(WRITE_ENCRYPTED)、授权读(READ_AUTHENTICATED)、授权写(WRITE_AUTHENTICATED)。
  • 加密需求:当权限位要求加密时,GATT 客户端必须在加密后的链路层(LE Secure Connections 或 Legacy Pairing)上发起操作;若无有效加密密钥,协议栈自动拒绝。
  • 配对绑定等级:分为 Just Works、Passkey Entry、Numeric Comparison、OOB。高等级(如 Passkey Entry + MITM)提供更强的身份验证,但增加配对交互成本。
  • MITM 保护:指在配对过程中防止中间人篡改临时密钥(TK)。使能 MITM 意味着配对必须采用 LE Secure Connections(LESC)或 Legacy Passkey Entry,并关闭 Just Works。

4.2 权限与安全矩阵

权限组合加密需求配对等级MITM 需求典型场景
Read / Write Only否无(或 Just Works)否广播数据、电池服务
Read Encrypted / Write Encrypted是至少 Just Works否健康传感器原始数据
Read Authenticated / Write Authenticated是Passkey Entry / Numeric Comparison是门锁、支付令牌
Read Signed / Write Signed否(但需签名验证)无否低功耗广播型传感器
⚠️ 常见陷阱:在 Zephyr/STM32WB/ESP32 等常见 SDK 中,若只设置 BT_GATT_PERM_WRITE_ENCRYPT 而未在安全请求回调中声明最小加密密钥大小(Key Size ≥ 7),部分协议栈会直接拒绝所有写入。必须同时配置 BT_SEC_LVL_ENC 或更高安全级别。

4.3 实现流程与代码配置

标准步骤:

  1. 定义特征 UUID 与属性(读/写/通知)。
  2. 在特征声明中按需设置 权限位掩码。
  3. 实现 GATT 安全回调(auth_req_cb / pair_accept),在回调中返回所需配对等级。
  4. 配置配对参数:IO 能力(KeyboardOnly、DisplayYesNo 等)、MITM 标志、Key Size。
  5. 在客户端发起读/写前调用 bt_gatt_subscribe 或 bt_gatt_read 并监听安全错误(ATT ERROR 0x05、0x08)。
// 示例:Zephyr GATT 特征声明(带加密写与 MITM 保护)
BT_GATT_SERVICE_DEFINE(my_svc,
    BT_GATT_PRIMARY_SERVICE(BT_UUID_DIS),
    BT_GATT_CHARACTERISTIC(BT_UUID_DIS_MODEL_NUMBER,
                           BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
                           BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_AUTHENTICATED,
                           read_model_number, write_model_number, &model_number_val),
);
🔑 关键配置点:
- BT_GATT_PERM_READ_ENCRYPT:强制读请求前必须建立加密连接。
- BT_GATT_PERM_WRITE_AUTHENTICATED:要求写入配对必须通过 MITM 保护(即 Authenticated Pairing)。若配对时选择 Just Works 且未设置 MITM,协议栈将返回 ATT 错误 0x08(Insufficient Encryption Key Size 或 Not Authorized)。
// 配对参数示例:启用 MITM + Display YesNo(用于 Numeric Comparison)
struct bt_le_adv_param adv_param = BT_LE_ADV_PARAM_INIT(
    BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_ONE_TIME,
    160, 240, NULL);

struct bt_conn_auth_cb auth_cb = {
    .pairing_confirm = pairing_confirm_cb,
    .passkey_display = passkey_display_cb,
    .passkey_entry  = NULL,
};
bt_conn_auth_cb_register(&auth_cb);

// 配对请求发起(客户端)
struct bt_conn *conn = bt_conn_create_le(&peer_addr);
bt_conn_set_security(conn, BT_SECURITY_L4); // L4 = Authenticated + LE Secure Connections

4.4 调试与验证

  • 使用 nRF Connect 验证安全层级:连接后点击 Security 标签,查看 Security Level(通常显示为 Encryption Key Size、Bonded、MITM Protected 等状态)。
  • ATT 错误排查:若收到 0x05 (Insufficient Authentication),说明特征需要认证写但配对无 MITM;若收到 0x0F (Insufficient Encryption),说明未建立加密链路。
  • 日志开启命令(Zephyr):
CONFIG_BT_DEBUG_LOG=y
CONFIG_BT_GATT_SMP_LOG_LEVEL_DBG=y
CONFIG_BT_CONN_LOG_LEVEL_DBG=y
  • hcitool 快速测试(Linux 主机):
# 建立加密连接
hcitool lecc --random 00:11:22:33:44:55
# 查看加密状态(MITM 标志位)
hcitool leinfo 00:11:22:33:44:55

4.5 常见错误与避坑指南

错误现象根本原因修复方法
写特征返回 0x05(Insufficient Authentication)特征权限设为 WRITE_AUTHENTICATED,但配对过程无 MITM。在配对参数中设置 BT_SECURITY_L4 或强制使能 MITM。
读特征返回 0x0F(Insufficient Encryption)特征要求 READ_ENCRYPTED,但链路尚未加密。调用 bt_conn_set_security(conn, BT_SECURITY_L2) 触发加密。
配对后 Key Size 显示为 0未在 GATT 初始化时设置最小密钥长度。通过 Kconfig CONFIG_BT_SMP_MIN_ENC_KEY_SIZE=16 设置。
MITM 提示框从未弹出客户端未注册 auth_cb 或 IO 能力设为 NoInputNoOutput。注册 bt_conn_auth_cb_register 并将 IO 能力改为 BT_IO_DISPLAY_YESNO。
💡 调试小技巧:若远程客户端(如手机 app)从未弹出配对确认框,多用 bt_conn_auth_cb_register 注册 pairing_confirm 回调并返回 true。部分 SDK(nRF5、STM32WB)将 MITM 绑定到 User Confirmation 回调,未实现时将降级为 Just Works。

4.6 性能与稳定性优化

  • 缓存安全策略:在 GATT 服务初始化阶段提前注册安全回调,避免运行时动态切换安全等级导致连接断开。
  • 连接间隔对加密影响:加密超时(Encrypted Connection Timeout)受连接间隔影响;若连接间隔过大(>100 ms),加密握手可能被 LL 中断。建议将最小连接间隔 ≤30 ms。
  • 减少 ATT 错误重传:在写特征前先检查 bt_conn_get_security 返回等级,若不足则主动发起 bt_conn_set_security,而不是让协议栈在操作时返回错误再重试。
  • 低功耗场景特化:对于仅需身份验证的场景,可启用 LE Secure Connections with SC Only Mode,关闭 Legacy Pairing 以缩短配对交互。
// 主动检查安全等级并提升示例(Zephyr 客户端)
static void write_secure_data(struct bt_conn *conn) {
    bt_security_t sec = bt_conn_get_security(conn);
    if (sec < BT_SECURITY_L3) {
        // 触发加密 + 身份验证
        bt_conn_set_security(conn, BT_SECURITY_L3);
        // 等待 security_changed 回调后执行写操作
        return;
    }
    bt_gatt_write(conn, &char_attr, data, len, write_cb, NULL);
}
特征权限 - 安全决策流程图 应用层定义特征 (UUID + 属性 + 权限位) 配置权限位掩码 READ_ENCRYPT / WRITE_AUTH 检查加密需求 最小 Key Size ≥ 7 配置配对绑定等级 JustWorks / Passkey / OOB 使能 MITM 保护 IO 能力 + User Confirmation 安全层拦截检查 ATT 权限验证 GATT 操作通过 / 返回错误 常见返回错误码 0x05→Insufficient Authentication 0x08→Insufficient Encryption Key Size 0x0F→Insufficient Encryption
📌 实战建议:为减少配对失败次数,可在 GATT 服务初始化时统一设置最小安全等级为 BT_SECURITY_L2(加密),对敏感特征使用时再动态提升至 BT_SECURITY_L4。这样既保证兼容性,又能在安全要求场景下启用 MITM。