乐于分享
好东西不私藏

沁恒微 Ble 蓝牙配对绑定说明测试

沁恒微 Ble 蓝牙配对绑定说明测试

 Ble 配对绑定的说明与测试   ...... 矜辰所致

前言

我们在使用 Ble 蓝牙的时候,有些设备需要数据加密,防止陌生设备读写数据,我们就需要进行配对绑定操作。但什么是配对,什么是绑定,和之前我们普通的连接通信有什么区别?实际应用中我们如何设置配对绑定?

本文我们就来说明一下 Ble 配对绑定的基础知识以及在 沁恒微 Ble 上配对绑定的代码实现。

相关博文:CH58x/CH59x 系列蓝牙芯片从机示例解析沁恒微蓝牙 GATT 应用框架说明沁恒 RISC-V 蓝牙芯片 Flash 分区管理及操作

我是矜辰所致,全网同名,尽量用心写好每一系列文章,不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开!

📑 本文目录前言一、🎯基础介绍├─ 1.1 什么是配对绑定├─ 1.2 为什么需要配对绑定├─ 1.3 什么决定需不需要配对绑定├─ 1.4 配对流程├─ 1.5 细节补充说明二、🔍 EVT 示例说明├─ 2.1 与配对有关的代码说明├─ 2.2 示例修改测试│ ├─ 2.2.1 增加配对相关回调函数│ ├─ 2.2.2 关于回连│ ├─ 2.2.3 主机示例配对绑定测试├─ 2.3 其他说明结语

一、 基础介绍

1.1 什么是配对绑定

先简单介绍一下什么是配对,什么是绑定?

我们用简短的术语表达:

  • • 配对两个设备当场交换密钥,建立加密连接 (一次性)。
  • • 绑定两个设备记住密钥,下次自动加密连接 (永久)。

我们之前很多基本的示例测试,数据传输,都是没有经过配对绑定的明文,可以通过蓝牙分析仪获取控制通讯的数据包,可以直接解析出来数据内容。

经过配对绑定之后,我们抓取到的数据包就是经过加密的报文,无法直接解析。

对于连接,配对,绑定的关联:

  • • 配对一定要先连接,没有连接,就没法配对;
  • • 绑定一定要先配对,没有配对,就没法绑定。

1.2 为什么需要配对绑定

原因概括来说就两点:

  1. 1. 安全性没有配对绑定的空中报文是明文,能够被第三方抓包(比比如一般测试情况下蓝牙分析仪抓包),很多场合下为了保证两个设备通信的安全性,必须防窃听、防篡改、防冒充,所以需要配对绑定。
  2. 2. 便捷性绑定过后,设备自动回连,不需要再次手动点击配对,自动进行加密回连,安全便捷。

1.3 什么决定需不需要配对绑定

需不需要配对绑定由从机的特征值权限决定!

从机的特征值权限,我们以前在学习 GATT 应用框架时的博文《 沁恒微蓝牙 GATT 应用框架说明 》有过说明,如下图:

具备上述属性的特征值必须要配对绑定才能进行正常的数据交互。

但是对于普通属性的特征值,不需要配对绑定就能正常读写的特征值,我们主动进行配对绑定,也是没有问题的,配对过后就变成加密通讯 ,对于普通属性的特征值配不配对都不影响通信。

1.4 配对流程

说明:只要初始化的时候设定好了配对绑定参数,配对流程是协议栈自动进行的,对于应用来说,本小节简单了解即可,不影响使用。

配对开始都是由”主机开始的“,这里指的是 启动配对 肯定是由主机发起,但是从机可以发 Security Request 请求配对,使得主机发起配对流程。

“谁输入密码”由双方 IO 能力协商决定——可能是主机、从机、双方确认,或无密码。

配对流程可以概括如下:

主机发起 → 特性交换(IO / 认证方式)→ 认证执行(显示 / 输入 / 确认)→ 密钥生成 → 链路加密 → 可选绑定保存。

  1. 1. 主机发起主机发 Pairing Request 开始配对;
  2. 2. 特性交换(IO / 认证方式)主机 ↔ 从机互相告诉对方 3 件事:①、IO 能力:有没有屏幕,有没有键盘 / 输入,决定后面谁输密码、谁显示密码。②、MITM:中间人攻击,MITM = TRUE 时要求防中间人,必须输密码、或数字比对,不能随便就连。MITM = FALSE 时可以用 Just Works 直接配对。

当从设备的 IO 能力 = NO_INPUT_NO_OUTPUT(无输入无输出),即使 MITM = TRUE 也只能走 Just Works 免密配对③、是否绑定(Bonding)

  1. 3. 认证执行(显示 / 输入 / 确认)根据第二部谈好的规则,走一种配对方式:①、Just Works:无密码、无显示、无输入,直接配对②、Passkey Entry:一方显示 6 位密码,另一方输入③、Numeric Comparison:两边都显示一样的 6 位密码/两边都点 “确认”
  2. 4. 密钥生成 → 生成 STKSTK : Short Term Key 短期密钥,只对本次连接有效,用来给当前链路做 AES 加密 。
  3. 5. 链路加密双方用 STK 启动加密,所有数据变成 AES 密文
  4. 6. 可选绑定保存如果第二步里 bonding = TRUE:就需要分发长期密钥 LTK + RAND + EDIV + IRK(可选)+ CSRK(可选,极少用),保存到 Flash。对于上述几种长期密钥,说明见下面表格:
密钥
全称
作用
是否必须保存
LTK
Long Term Key
加密链路的核心密钥重连直接用它生成会话密钥,无需重新配对。
✅ 必须
EDIV
Encrypted Diversifier
LTK 的索引/标识,配合 Rand 使用
✅ 必须(与 LTK 配对)
Rand
Random Number
随机数,与 EDIV 一起唯一标识 LTKRAND + EDIV = 钥匙编号
✅ 必须(与 LTK 配对)
IRK
Identity Resolving Key
解析私有随机地址(RPA)
⚠️ 需要隐私功能则必须
CSRK
Connection Signature Resolving Key
数据签名验证(ATT 层)
❌ 一般不用

1.5 细节补充说明

  • • 蓝牙两个设备建立连接的过程(广从机广播、手机扫描、手机发起连接、建立物理连接成功)是明文,可以通过抓包工具获取通讯包。加密在配对后才启用!
  • • 配对绑定不阻止别人连接,但阻止别人访问加密特征值;明文特征值仍可读写,除非配对模式设为拒绝新连接。
  • • 从机多连接:从机同时跟多个主机建立物理连接,但每个主机能不能读写、能读哪些,由 特征值权限 + 绑定状态 单独控制。从机可以绑定多个主机,每个绑定都是独立密钥;从机 Flash 里存一个绑定列表,可以存多组 LTK 密钥,对应多个手机。绑定多个后,所有已绑定手机都能自动回连、加密通信。绑定信息需要存放到 Flash ,需要占用内存空间,沁恒微EVT 里面决定绑定数量的在 CONFIG.h 里面的宏定义BLE_SNV_NUM 。

二、 EVT 示例说明

本文以 CH58x EVT 为示例说明,CH58x CH59x 系列芯片大体一致,具体以实际 EVT 为准

2.1 与配对有关的代码说明

配对绑定配置代码:

在官方 EVT 从机示例中,关于配对绑定有关的配置在Peripheral_Init() 里面,如下:

再次说明,只要完成上面配置,配对绑定流程由 BLE 协议栈自动执行,自动完成配对、加密、保存 LTK/EDIV/Rand 到 Flash,下次回连自动加密。应用层只需配置 + 监听回调。

其中每个配置参数说明如下:

  1. 1. GAPBOND_PERI_DEFAULT_PASSCODE密码 ,6 位数字
  2. 2. GAPBOND_PERI_PAIRING_MODE其中有三个配置:GAPBOND_PAIRING_MODE_NO_PAIRING禁止配对GAPBOND_PAIRING_MODE_WAIT_FOR_REQ被动等待配对,等主机自己发起才配对GAPBOND_PAIRING_MODE_INITIATE连接成功 瞬间,就发送 Security Request 请求主机发起配对
  3. 3. GAPBOND_PERI_MITM_PROTECTION前面讲过的 MITM:中间人攻击,MITM = TRUE 时要求防中间人,必须输密码、或数字比对,不能随便就连。MITM = FALSE 时可以用 Just Works 直接配对。
  4. 4. GAPBOND_PERI_IO_CAPABILITIESIO 能力配置,5 种配置如下:GAPBOND_IO_CAP_DISPLAY_ONLY只有显示屏,没有输入,不能确认,按键(智能手环、电子标签)配对方式 Passkey Entry:显示6位数字,对方输入GAPBOND_IO_CAP_DISPLAY_YES_NO有显示屏 + 有确认键(带按键的显示器)配对方式 Numeric Comparison:双方显示数字,用户确认是否相同GAPBOND_IO_CAP_KEYBOARD_ONLY只有键盘输入,没有屏幕(蓝牙键盘)配对方式 Passkey Entry:对方显示数字,本机输入GAPBOND_IO_CAP_NO_INPUT_NO_OUTPUT无显示无输入无按键(耳机,传感器)配对方式 Just Works:无密码,直接配对GAPBOND_IO_CAP_KEYBOARD_DISPLAY既能显示又能输入(手机,平板)配对方式 所有方式都支持,协议自动选择最优
  5. 5. GAPBOND_PERI_BONDING_ENABLED是否需要绑定,= TRUE 需要绑定

上面 IO 能力配置,我感觉还是表格查询比较直观,补充一个表格:

能力
典型设备
配对方式
GAPBOND_IO_CAP_DISPLAY_ONLY
0x00
只有显示屏,没有输入,不能确认,按键
智能手环、电子标签
Passkey Entry

:显示6位数字,对方输入
GAPBOND_IO_CAP_DISPLAY_YES_NO
0x01
有显示屏 + 有确认键
带按键的显示器
Numeric Comparison

:双方显示数字,用户确认是否相同
GAPBOND_IO_CAP_KEYBOARD_ONLY
0x02
只有键盘输入,没有屏幕
蓝牙键盘
Passkey Entry

:对方显示数字,本机输入
GAPBOND_IO_CAP_NO_INPUT_NO_OUTPUT
0x03
无显示无输入无按键
耳机、传感器
Just Works

:无密码,直接配对
GAPBOND_IO_CAP_KEYBOARD_DISPLAY
0x04
既能显示又能输入
手机、平板
所有方式都支持,协议自动选择最优

配对绑定数据存储:

我们数据保存存放在哪里,这个在之前博文《沁恒 RISC-V 蓝牙芯片 Flash 分区管理及操作》 中有过说明:

在工程目录 HAL —> include —> CONFIG.h 文件里面有关于配对绑定存储地址,数量有关的宏定义配置:

具体细节一点大家可以自行查看或者查看之前的 Flash 分区使用说明文章,但是要记得,之前文章讲过我们修改宏定义,尽量在 MRS 工程配置里面修改:

2.2 示例修改测试

2.2.1 增加配对相关回调函数

官方从机示例中,只有配对绑定的配置,没有注册回调函数,我们为了观察效果,需要把回调函数加上,可参考主机例程 Central 的配对绑定回调函数 。

配对绑定状态回调相关的结构体如下:

typedefstruct{    pfnPasscodeCB_t passcodeCB;     //!< Passcode callback    pfnPairStateCB_t pairStateCB;   //!< Pairing state callback    pfnOobCB_t  oobCB;              //!< oob callback} gapBondCBs_t;

在从机示例种,我们先只加一个状态回调:

// GAP Bond Manager Callbacksstatic gapBondCBs_t Peripheral_BondMgrCBs = {NULL// Passcode callback (not used by application)    My_Peripheral_PairStateCB,  // Pairing / Bonding state Callback (not used by application)NULL// oob callback};

实现一下回调函数 :

staticvoidMy_Peripheral_PairStateCBuint16_t connectionHandle, uint8_t state, uint8_t status){switch(state)    {case GAPBOND_PAIRING_STATE_STARTED:        PRINT("配对开始\r\n");break;case GAPBOND_PAIRING_STATE_COMPLETE:  if(status == SUCCESS)        {            PRINT("配对成功\r\n");        }else        {            PRINT("配对失败\n");        }break;case GAPBOND_PAIRING_STATE_BONDED:if(status == SUCCESS)        {            PRINT("绑定成功\r\n");        }break;case GAPBOND_PAIRING_STATE_BOND_SAVED:if(status == SUCCESS)        {            PRINT("绑定信息已保存到Flash\r\n");        }else        {            PRINT("绑定信息保存失败: %d\n", status);        }     break;           }}

为什么测试先只加一个回调函数,因为如果第一个回调函数 passcodeCB 也增加实现,主机需要输入的密码会出现变化,不再以初始化的密码为准。而是需要在回调函数中生成密码。 这个我们下文也会测试。

我们把密码修改一下:

uint32_t passkey = 123456// passkey "000000"

为了方便区分,我把蓝牙名称也修改了一下,这个大家随意。

然后烧录程序以后,通过手机蓝牙(不是 APP,不是 BLE 调试助手,nRF Connect 那些APP),是手机系统设置里面的蓝牙,选中从机设备,进行配对:

在从机 Debug 信息输出如下 :

如果我们要增加配对密码回调函数,再次说明,只要 Passcode callback 注册了,那么密码就不是初始设定的密码了:

我们可参考官方 EVT 中主机代码中 centralPasscodeCB 程序写密码回调函数,如下:

// 配对密码回调(如果需要自己显示密码就用)staticvoidMy_Peripheral_PasscodeCB(uint8_t *deviceAddr, uint16_t connHandle, uint8_t uiInputs, uint8_t uiOutputs){uint32_t passcode;// Create random passcode    passcode = tmos_rand();    passcode %= 1000000;// Display passcode to user // passcode = 123456; 我们也可以再这里固定密码if(uiOutputs != 0)    {        PRINT("Passcode:%06d\n", (int)passcode);    }// Send passcode response    GAPBondMgr_PasscodeRsp(connHandle, SUCCESS, passcode);}

回调函数设置如下:

// GAP Bond Manager Callbacksstatic gapBondCBs_t Peripheral_BondMgrCBs = {    My_Peripheral_PasscodeCB, // Passcode callback (not used by application)    My_Peripheral_PairStateCB,  // Pairing / Bonding state Callback (not used by application)NULL// oob callback};

我们再进行上面配对操作,就需要注意新生成的密码,手机输入的秘密以新生成的密码为准(当然,我们可以在密码回调中固定一个密码也可以),如下:

那在上文中,我们讲配对绑定配置的时候,讲了不同配置下的配对方式会有不同,这个大家都可以自己尝试一下,比如我们把示例中 ioCap 设置为无输入输出的情况:

uint8_t  ioCap = GAPBOND_IO_CAP_NO_INPUT_NO_OUTPUT;

我们手机进行配对的时候,就不需要输入密码,点击配对即可成功配对。

2.2.2 关于回连

只要 bond 成功,设备就具备自动回连的能力,但手机系统不会像对待耳机/HID 那样自动连接普通 BLE 设备。

所以我们上面的示例测试,断开并不能够实现它自动回连的效果,那么我们可以把我们自己的设备模拟成 HID 设备,测试一下自动回连。这里我们为了方便,直接使用 EVT 例程中的 HID 设备测试回连,关于 HID 设备后期会有专门的博文进行说明,本文不讨论 HID 设备实现与逻辑等问题。

比如我们找到 BLE 目录下面的 HID_Mouse 例程:

我们本次需要关注的点在于配对绑定这一块,这个根据上文说明就能看懂:

示例直接通过手机蓝牙测试 :

2.2.3 主机示例配对绑定测试

主机和从机需要配对绑定,会根据它们配置的 IO 能力进行配对方式的选择, 在配对的时候,是根据双方的能力共同决定的,所以只需要知道双方能力的组合,就能确定它们的配对方式,如下表格:

组合
配对方式
验证
KEYBOARD_DISPLAY

 + DISPLAY_ONLY
Passkey Entry
一方能输一方能显
KEYBOARD_DISPLAY

 + KEYBOARD_ONLY
Passkey Entry
一方能显一方能输
KEYBOARD_DISPLAY

 + KEYBOARD_DISPLAY
Numeric Comparison
双方都能显+确认
NO_INPUT_NO_OUTPUT

 + 任意
Just Works
只要有一方无能,只能无密码
KEYBOARD_ONLY

 + DISPLAY_ONLY
Passkey Entry
一方能输一方能显
DISPLAY_ONLY

 + DISPLAY_ONLY
Just Works
都能显但都不能输,降级
KEYBOARD_ONLY

 + KEYBOARD_ONLY
Just Works
都能输但都不能显,降级

对于我们的示例而言,从机默认为 GAPBOND_IO_CAP_DISPLAY_ONLY 所以当主机为 NO_INPUT_NO_OUTPUT 或者 DISPLAY_ONLY 时,它们配对是不需要密码的,不会进入密码回调,比如主机设置如下(注意主机要设置为 GAPBOND_PAIRING_MODE_INITIATE 连接后发起配对):

/ Setup the GAP Bond Manager{uint32_t passkey = 0;uint8_t  pairMode = GAPBOND_PAIRING_MODE_INITIATE;uint8_t  mitm = TRUE;uint8_t  ioCap = GAPBOND_IO_CAP_NO_INPUT_NO_OUTPUT;uint8_t  bonding = TRUE;    GAPBondMgr_SetParameter(GAPBOND_CENT_DEFAULT_PASSCODE, sizeof(uint32_t), &passkey);    GAPBondMgr_SetParameter(GAPBOND_CENT_PAIRING_MODE, sizeof(uint8_t), &pairMode);    GAPBondMgr_SetParameter(GAPBOND_CENT_MITM_PROTECTION, sizeof(uint8_t), &mitm);    GAPBondMgr_SetParameter(GAPBOND_CENT_IO_CAPABILITIES, sizeof(uint8_t), &ioCap);    GAPBondMgr_SetParameter(GAPBOND_CENT_BONDING_ENABLED, sizeof(uint8_t), &bonding);}

测试结果如下,从机不进入密码回调函数,使用的是 Just Works 配对模式:

如果我们把主机设置为:

uint32_t passkey = 0;uint8_t  pairMode = GAPBOND_PAIRING_MODE_INITIATE;uint8_t  mitm = TRUE;uint8_t  ioCap = GAPBOND_IO_CAP_KEYBOARD_ONLY;uint8_t  bonding = TRUE;

当然还需要修改一下 centralPasscodeCB 函数,把从机对应的密码写上去,从机这个地方也要固定密码,要不然不好测试:

staticvoidcentralPasscodeCB(uint8_t *deviceAddr, uint16_t connectionHandle,uint8_t uiInputs, uint8_t uiOutputs){uint32_t passcode;// Create random passcode    passcode = tmos_rand();    passcode %= 1000000;    passcode = 520131;  //和从机一样,如果有密码回调,就在密码回调里面设置密码// Display passcode to userif(uiOutputs != 0)    {        PRINT("Passcode:%06d\n", (int)passcode);    }// Send passcode response    GAPBondMgr_PasscodeRsp(connectionHandle, SUCCESS, passcode);}

我们再来看看测试结果,会进入密码回调函数,使用的是 Passkey Entry 配对方式 :

如果密码不对,配对失败的情况(示例总,配对失败并不影响 notify 与数据读写,因为示例读写的特征值不需要加密读写):

2.3 其他说明

  1. 1. HID 类型设备需要修改广播包,这个后面会有博文讨论 HID 类型设备;
  2. 2. 开发板不方便输入密码,所以开发板模拟某些设备的时候需要注意一下 IO 能力的合理配置。
  3. 3. 配对一般建议设置为示例默认的等待主机配对 GAPBOND_PAIRING_MODE_WAIT_FOR_REQ,防止部分手机不接收从机发起的配对请求。
  4. 4. 示例中主机从机绑定过后,如果某一方重新烧录程序删除了 Flash 内容(删除了保存的绑定信息),需要将双方的绑定信息都删除再进行正常通信重新绑定,否则会出现问题。

结语

本文我们了解了一下配对绑定相关知识,也说明了沁恒微 EVT 代码中与配对绑定相关的代码, 希望能够帮助大家更好的理解 Ble 中的配对绑定相关问题。

好了,本文就到这里。谢谢大家!