第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 实现流程与代码配置
标准步骤:
- 定义特征 UUID 与属性(读/写/通知)。
- 在特征声明中按需设置 权限位掩码。
- 实现 GATT 安全回调(
auth_req_cb/pair_accept),在回调中返回所需配对等级。 - 配置配对参数:IO 能力(KeyboardOnly、DisplayYesNo 等)、MITM 标志、Key Size。
- 在客户端发起读/写前调用
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)。
- 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);
}
📌 实战建议:为减少配对失败次数,可在 GATT 服务初始化时统一设置最小安全等级为
BT_SECURITY_L2(加密),对敏感特征使用时再动态提升至 BT_SECURITY_L4。这样既保证兼容性,又能在安全要求场景下启用 MITM。