Ble 配对绑定的说明与测试 ...... 矜辰所致前言
我们在使用 Ble 蓝牙的时候,有些设备需要数据加密,防止陌生设备读写数据,我们就需要进行配对绑定操作。但什么是配对,什么是绑定,和之前我们普通的连接通信有什么区别?实际应用中我们如何设置配对绑定?
本文我们就来说明一下 Ble 配对绑定的基础知识以及在 沁恒微 Ble 上配对绑定的代码实现。
相关博文:
CH58x/CH59x 系列芯片从机示例解析
沁恒微蓝牙 GATT 应用框架说明
沁恒 RISC-V 蓝牙芯片 Flash 分区管理及操作
.
我是矜辰所致,全网同名,尽量用心写好每一系列文章,不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开!
目录
- 前言
- 一、 基础介绍
- 1.1 什么是配对绑定
- 1.2 为什么需要配对绑定
- 1.3 什么决定需不需要配对绑定
- 1.4 配对流程
- 1.5 细节补充说明
- 二、 EVT 示例说明
- 2.1 与配对有关的代码说明
- 2.1.1 配对绑定配置代码
- 2.1.2 不同的配对方式
- 2.1.3 配对绑定数据存储
- 2.2 示例修改测试
- 2.2.1 增加配对相关回调函数
- 2.2.2 关于回连
- 2.2.3 主机示例配对绑定测试
- 2.3 其他说明
- 结语
一、 基础介绍
1.1 什么是配对绑定
先简单介绍一下什么是配对,什么是绑定?
我们用简短的术语表达:
配对
两个设备当场交换密钥,建立加密连接 (一次性)。绑定
两个设备记住密钥,下次自动加密连接 (永久)。
我们之前很多基本的示例测试,数据传输,都是没有经过配对绑定的明文,可以通过蓝牙分析仪获取控制通讯的数据包,可以直接解析出来数据内容。
经过配对绑定之后,我们抓取到的数据包就是经过加密的报文,无法直接解析。
对于连接,配对,绑定的关联:
- 配对一定要先连接,没有连接,就没法配对;
- 绑定一定要先配对,没有配对,就没法绑定。
1.2 为什么需要配对绑定
原因概括来说就两点:
- 安全性
没有配对绑定的空中报文是明文,能够被第三方抓包(比比如一般测试情况下蓝牙分析仪抓包),很多场合下为了保证两个设备通信的安全性,必须防窃听、防篡改、防冒充,所以需要配对绑定。 - 便捷性
绑定过后,设备自动回连,不需要再次手动点击配对,自动进行加密回连,安全便捷。
1.3 什么决定需不需要配对绑定
需不需要配对绑定由从机的特征值权限决定!
从机的特征值权限,我们以前在学习 GATT 应用框架时的博文《 沁恒微蓝牙 GATT 应用框架说明 》有过说明,如下图:
具备上述属性的特征值必须要配对绑定才能进行正常的数据交互。
但是对于普通属性的特征值,不需要配对绑定就能正常读写的特征值,我们主动进行配对绑定,也是没有问题的,配对过后就变成加密通讯 ,对于普通属性的特征值配不配对都不影响通信。
1.4 配对流程
说明:只要初始化的时候设定好了配对绑定参数,配对流程是协议栈自动进行的,对于应用来说,本小节简单了解即可,不影响使用。
配对开始都是由”主机开始的“,这里指的是 启动配对 肯定是由主机发起,但是从机可以发 Security Request 请求配对,使得主机发起配对流程。
"谁输入密码"由双方 IO 能力协商决定——可能是主机、从机、双方确认,或无密码。
配对流程可以概括如下:
主机发起 → 特性交换(IO / 认证方式)→ 认证执行(显示 / 输入 / 确认)→ 密钥生成 → 链路加密 → 可选绑定保存。
主机发起
主机发 Pairing Request 开始配对;特性交换(IO / 认证方式)
主机 ↔ 从机互相告诉对方 3 件事:
①、IO 能力:有没有屏幕,有没有键盘 / 输入,决定后面谁输密码、谁显示密码。
②、MITM:中间人攻击,MITM = TRUE 时要求防中间人,必须输密码、或数字比对,不能随便就连。MITM = FALSE 时可以用 Just Works 直接配对。
当从设备的 IO 能力 = NO_INPUT_NO_OUTPUT(无输入无输出),即使 MITM = TRUE 也只能走 Just Works 免密配对
③、是否绑定(Bonding)认证执行(显示 / 输入 / 确认)
根据第二部谈好的规则,走一种配对方式:
①、Just Works:无密码、无显示、无输入,直接配对
②、Passkey Entry:一方显示 6 位密码,另一方输入
③、Numeric Comparison:两边都显示一样的 6 位密码/两边都点 “确认”密钥生成 → 生成 STK
STK : Short Term Key 短期密钥,只对本次连接有效,用来给当前链路做 AES 加密 。链路加密
双方用 STK 启动加密,所有数据变成 AES 密文可选绑定保存
如果第二步里 bonding = TRUE:
就需要分发长期密钥 LTK + RAND + EDIV + IRK(可选)+ CSRK(可选,极少用),保存到 Flash。
对于上述几种长期密钥,说明见下面表格:
| 密钥 | 全称 | 作用 | 是否必须保存 |
|---|---|---|---|
| LTK | Long Term Key | 加密链路的核心密钥 重连直接用它生成会话密钥,无需重新配对。 | ✅必须 |
| EDIV | Encrypted Diversifier | LTK 的索引/标识,配合 Rand 使用 | ✅必须(与 LTK 配对) |
| Rand | Random Number | 随机数,与 EDIV 一起唯一标识 LTK RAND + 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 与配对有关的代码说明
2.1.1 配对绑定配置代码
在官方 EVT 从机示例中,关于配对绑定有关的配置在Peripheral_Init()里面,如下:
再次说明,只要完成上面配置,配对绑定流程由 BLE 协议栈自动执行,自动完成配对、加密、保存 LTK/EDIV/Rand 到 Flash,下次回连自动加密。应用层只需配置 + 监听回调。
其中每个配置参数说明如下:
GAPBOND_PERI_DEFAULT_PASSCODE
密码 ,6 位数字GAPBOND_PERI_PAIRING_MODE
其中有三个配置:GAPBOND_PAIRING_MODE_NO_PAIRING
禁止配对GAPBOND_PAIRING_MODE_WAIT_FOR_REQ
被动等待配对,等主机自己发起才配对GAPBOND_PAIRING_MODE_INITIATE
连接成功 瞬间,就发送 Security Request 请求主机发起配对GAPBOND_PERI_MITM_PROTECTION
前面讲过的 MITM:中间人攻击,MITM = TRUE 时要求防中间人,必须输密码、或数字比对,不能随便就连。MITM = FALSE 时可以用 Just Works 直接配对。GAPBOND_PERI_IO_CAPABILITIES
IO 能力配置,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
既能显示又能输入(手机,平板)
配对方式 所有方式都支持,协议自动选择最优GAPBOND_PERI_BONDING_ENABLED
是否需要绑定,= TRUE 需要绑定
2.1.2 不同的配对方式
上面 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 | 既能显示又能输入 | 手机、平板 | 所有方式都支持,协议自动选择最优 |
2.1.3 配对绑定数据存储
我们数据保存存放在哪里,这个在之前博文《沁恒 RISC-V 蓝牙芯片 Flash 分区管理及操作》 中有过说明:
在工程目录 HAL —> include —> CONFIG.h 文件里面有关于配对绑定存储地址,数量有关的宏定义配置:
具体细节一点大家可以自行查看或者查看之前的 Flash 分区使用说明文章,但是要记得,之前文章讲过我们修改宏定义,尽量在 MRS 工程配置里面修改:
2.2 示例修改测试
2.2.1 增加配对相关回调函数
官方从机示例中,只有配对绑定的配置,没有注册回调函数,我们为了观察效果,需要把回调函数加上,可参考主机例程 Central 的配对绑定回调函数 。
配对绑定状态回调相关的结构体如下:
typedefstruct{pfnPasscodeCB_tpasscodeCB;//!< Passcode callbackpfnPairStateCB_tpairStateCB;//!< Pairing state callbackpfnOobCB_toobCB;//!< oob callback}gapBondCBs_t;在从机示例种,我们先只加一个状态回调:
// GAP Bond Manager CallbacksstaticgapBondCBs_tPeripheral_BondMgrCBs={NULL,// Passcode callback (not used by application)My_Peripheral_PairStateCB,// Pairing / Bonding state Callback (not used by application)NULL// oob callback};实现一下回调函数 :
staticvoidMy_Peripheral_PairStateCB(uint16_tconnectionHandle,uint8_tstate,uint8_tstatus){switch(state){caseGAPBOND_PAIRING_STATE_STARTED:PRINT("配对开始\r\n");break;caseGAPBOND_PAIRING_STATE_COMPLETE:if(status==SUCCESS){PRINT("配对成功\r\n");}else{PRINT("配对失败\n");}break;caseGAPBOND_PAIRING_STATE_BONDED:if(status==SUCCESS){PRINT("绑定成功\r\n");}break;caseGAPBOND_PAIRING_STATE_BOND_SAVED:if(status==SUCCESS){PRINT("绑定信息已保存到Flash\r\n");}else{PRINT("绑定信息保存失败: %d\n",status);}break;}}为什么测试先只加一个回调函数,因为如果第一个回调函数passcodeCB也增加实现,主机需要输入的密码会出现变化,不再以初始化的密码为准。而是需要在回调函数中生成密码。这个我们下文也会测试。
我们把密码修改一下:
uint32_tpasskey=123456;// passkey "000000"为了方便区分,我把蓝牙名称也修改了一下,这个大家随意。
然后烧录程序以后,通过手机蓝牙(不是 APP,不是 BLE 调试助手,nRF Connect 那些APP),是手机系统设置里面的蓝牙,选中从机设备,进行配对:
在从机 Debug 信息输出如下 :
如果我们要增加配对密码回调函数,再次说明,只要 Passcode callback 注册了,那么密码就不是初始设定的密码了:
我们可参考官方 EVT 中主机代码中centralPasscodeCB程序写密码回调函数,如下:
// 配对密码回调(如果需要自己显示密码就用)staticvoidMy_Peripheral_PasscodeCB(uint8_t*deviceAddr,uint16_tconnHandle,uint8_tuiInputs,uint8_tuiOutputs){uint32_tpasscode;// Create random passcodepasscode=tmos_rand();passcode%=1000000;// Display passcode to user// passcode = 123456; 我们也可以再这里固定密码if(uiOutputs!=0){PRINT("Passcode:%06d\n",(int)passcode);}// Send passcode responseGAPBondMgr_PasscodeRsp(connHandle,SUCCESS,passcode);}回调函数设置如下:
// GAP Bond Manager CallbacksstaticgapBondCBs_tPeripheral_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_tioCap=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_tpasskey=0;uint8_tpairMode=GAPBOND_PAIRING_MODE_INITIATE;uint8_tmitm=TRUE;uint8_tioCap=GAPBOND_IO_CAP_NO_INPUT_NO_OUTPUT;uint8_tbonding=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_tpasskey=0;uint8_tpairMode=GAPBOND_PAIRING_MODE_INITIATE;uint8_tmitm=TRUE;uint8_tioCap=GAPBOND_IO_CAP_KEYBOARD_ONLY;uint8_tbonding=TRUE;当然还需要修改一下centralPasscodeCB函数,把从机对应的密码写上去,从机这个地方也要固定密码,要不然不好测试:
staticvoidcentralPasscodeCB(uint8_t*deviceAddr,uint16_tconnectionHandle,uint8_tuiInputs,uint8_tuiOutputs){uint32_tpasscode;// Create random passcodepasscode=tmos_rand();passcode%=1000000;passcode=520131;//和从机一样,如果有密码回调,就在密码回调里面设置密码// Display passcode to userif(uiOutputs!=0){PRINT("Passcode:%06d\n",(int)passcode);}// Send passcode responseGAPBondMgr_PasscodeRsp(connectionHandle,SUCCESS,passcode);}我们再来看看测试结果,会进入密码回调函数,使用的是 Passkey Entry 配对方式 :
如果密码不对,配对失败的情况(示例总,配对失败并不影响 notify 与数据读写,因为示例读写的特征值不需要加密读写):
2.3 其他说明
- HID 类型设备需要修改广播包,这个后面会有博文讨论 HID 类型设备;
- 开发板不方便输入密码,所以开发板模拟某些设备的时候需要注意一下 IO 能力的合理配置。
- 配对一般建议设置为示例默认的等待主机配对
GAPBOND_PAIRING_MODE_WAIT_FOR_REQ,防止部分手机不接收从机发起的配对请求。 - 示例中主机从机绑定过后,如果某一方重新烧录程序删除了 Flash 内容(删除了保存的绑定信息),需要将双方的绑定信息都删除再进行正常通信重新绑定,否则会出现问题。
结语
本文我们了解了一下配对绑定相关知识,也说明了沁恒微 EVT 代码中与配对绑定相关的代码, 希望能够帮助大家更好的理解 Ble 中的配对绑定相关问题。
好了,本文就到这里。谢谢大家!