我写博文的目的大多数情况下都只是为研发工作复盘,记录研发工作中碰到的一些问题及最终解决方案或处理方式,也正因为如此,很多博文会显得很杂,甚至没有明显的主体。这篇博文的目的更是如此,说白了就是给自己看的,如果能对碰到类似问题的朋友提供那怕一点点的思路,那就是意外之喜了。
配对及加密,对沁恒的蓝牙的应用开发者来说,能做的事情其实很少,毕竟这部分内容都已打包在库里,我们几乎没办法修改。能做的不过是修改相关设置来满足我们的需要。尽管如此,我们还是先了解一下相关的知识。
沁恒CH582作为国产低功耗蓝牙SOC芯片,其配对与加密流程符合蓝牙4.2/5.0核心规范,本文将从实践角度解析其安全机制实现。
一、配对流程三阶段
-
配对特征交换
-
通过
GAP Bonding
过程交换IO能力(如是否支持显示屏/键盘) -
典型配对方式:PIN码配对(默认6位数字)或Just Works(无用户交互)
-
-
临时密钥生成
-
支持LE Legacy Pairing和LE Secure Connections
-
基于DHKey的ECDH椭圆曲线加密(当启用Secure Connections时)
-
-
长期密钥分发
-
生成并交换LTK(Long Term Key)用于后续加密
-
可选的IRK(Identity Resolving Key)和CSRK(Connection Signature Key)
-
二、加密配置实践
uint32_t passkey = DEFAULT_PASSCODE;
uint8_t pairMode = GAPBOND_PAIRING_MODE_INITIATE;
uint8_t mitm = DEFAULT_MITM_MODE;
uint8_t ioCap = DEFAULT_IO_CAPABILITIES;
uint8_t bonding = DEFAULT_BONDING_MODE;
uint8_t key = 0x33;
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_KEY_DIST_LIST, sizeof(uint8_t), &key );
GAPBondMgr_SetParameter( GAPBOND_CENT_BONDING_ENABLED, sizeof(uint8_t), &bonding );
很明显,GAPBOND_CENT_DEFAULT_PASSCODE,从名称就知道用于设置缺省密码。GAPBOND_CENT_PAIRING_MODE用于设置配对方式,从官方SDK中我们知道,有三种配对方式 :
GAPBOND_CENT_MITM_PROTECTION简单说就是用于设置是否需要密码,GAPBOND_CENT_IO_CAPABILITIES简单说就是设定配对绑定时,是否有输入输出相关设备。GAPBOND_CENT_KEY_DIST_LIST设置SM Pairing Request中的InitiatorKey(发起方密钥)及ResKey(响应方密钥)。
GAPBOND_CENT_BONDING_ENABLED用于设置配对时是否绑定,如果设置为TRUE,最好修改config.h中的BLE_SNV 为 TRUE。否则可能在重连已配对过的设备时,会出现连接上后立即断开等现象。
有关BLE 安全SM协议,有兴趣的可以参考:BLE安全之SM剖析(1)_ble sm-优快云博客及BLE安全之SM剖析(2)_蓝牙just works的配对流程-优快云博客
三、重连的一些特殊情况处理
这一小节记录的只是简单记录过往开发过程中碰到的情况及处理方法。
1、第一种情况 :
第一次配对时,过程与我们前面博文介绍的差不多,但在断开或重启后重连的时候,从机发送的是定向广播,主机不需要枚举从机的服务等信息,只是简单建立链接。
case GAP_LINK_ESTABLISHED_EVENT:
{
tmos_stop_task(centralTaskId, ESTABLISH_LINK_TIMEOUT_EVT);
if(pEvent->gap.hdr.status == SUCCESS)
{
#if(defined(HAL_SLEEP)) && (HAL_SLEEP == TRUE)
SleepTimer = 0;
#endif
centralState = BLE_STATE_CONNECTED;
centralConnHandle = pEvent->linkCmpl.connectionHandle;
centralProcedureInProgress = TRUE;
// Initiate service discovery
if (Device_Ctrl.IsDirectConnect == FALSE)
{
tmos_memcpy (Device_Ctrl.addr,pEvent->linkCmpl.devAddr,B_ADDR_LEN);
// Update MTU
attExchangeMTUReq_t req = {
.clientRxMTU = BLE_BUFF_MAX_LEN - 4,
};
GATT_ExchangeMTU(centralConnHandle, &req, centralTaskId);
tmos_start_task(centralTaskId, START_SVC_DISCOVERY_EVT, DEFAULT_SVC_DISCOVERY_DELAY);
// See if initiate connect parameter update
if(centralParamUpdate)
{
tmos_start_task(centralTaskId, START_PARAM_UPDATE_EVT, DEFAULT_PARAM_UPDATE_DELAY);
}
// See if initiate phy update
if(centralPhyUpdate)
{
tmos_start_task(centralTaskId, START_PHY_UPDATE_EVT, DEFAULT_PHY_UPDATE_DELAY);
}
// See if start RSSI polling
if(centralRssi)
{
tmos_start_task(centralTaskId, START_READ_RSSI_EVT, DEFAULT_RSSI_PERIOD);
}
}
else {
BLEConnected = BLE_CONECTED;
}
// Start_Key_Event_Task();
PRINT("Connected...\n");
}
else
{
PRINT("Connect Failed...Reason:%X\n", pEvent->gap.hdr.status);
PRINT("Discovering...\n");
centralScanRes = 0;
GAPRole_CentralStartDiscovery(DEFAULT_DISCOVERY_MODE,
DEFAULT_DISCOVERY_ACTIVE_SCAN,
DEFAULT_DISCOVERY_WHITE_LIST);
}
}
break;
case GAP_DIRECT_DEVICE_INFO_EVENT:
{
// Display device addr
PRINT("Recv direct adv \n");
>deviceDirectInfo.addr,B_ADDR_LEN))
if (tmos_memcmp(Device_Ctrl.addr,pEvent->deviceDirectInfo.addr,B_ADDR_LEN))
{
Device_Ctrl.IsDirectConnect = TRUE;
centralAddDeviceInfo(pEvent->deviceDirectInfo.addr, pEvent->deviceDirectInfo.addrType);
}
else {
centralAddDeviceInfo(pEvent->deviceDirectInfo.addr, pEvent->deviceDirectInfo.addrType);
Device_Ctrl.IsDirectConnect = FALSE;
}
}
break;
2、第二种情况:
第一次配对时,过程与我们前面博文介绍的差不多,但在断开或重启后重连的时候,从机发送的是非定向广播,但主机不需要枚举从机的服务等信息,只是简单建立链接。
case GAP_DEVICE_DISCOVERY_EVENT :
{
if ( IsPaired )
{
uint8_t i;
// See if peer device has been discovered
for ( i = 0; i < centralScanRes; i++ )
{
if ( tmos_memcmp( Device_Ctrl.addr, centralDevList[i].addr, B_ADDR_LEN ) )
{
Device_Ctrl.IsDirectConnect = TRUE;
break;
}
}
if ( Device_Ctrl.IsDirectConnect == FALSE )
{
PRINT( "Device not found...\n" );
centralScanRes = 0;
GAPRole_CentralStartDiscovery( DEFAULT_DISCOVERY_MODE,
DEFAULT_DISCOVERY_ACTIVE_SCAN,
DEFAULT_DISCOVERY_WHITE_LIST );
PRINT( "Discovering...\n" );
}
}
if ( ( !IsPaired ) || ( Device_Ctrl.IsDirectConnect == TRUE ) )
{
PRINT( "Device found...\n" );
GAPRole_CentralEstablishLink( DEFAULT_LINK_HIGH_DUTY_CYCLE,
DEFAULT_LINK_WHITE_LIST,
centralDevList[0].addrType, centralDevList[0].addr );
// BLEConnected = BLE_CONNECTING;
// Start establish link timeout event
tmos_start_task( centralTaskId, ESTABLISH_LINK_TIMEOUT_EVT, ESTABLISH_LINK_TIMEOUT );
PRINT( "Connecting...\n" );
}
}
break;
case GAP_LINK_ESTABLISHED_EVENT :
{
tmos_stop_task( centralTaskId, ESTABLISH_LINK_TIMEOUT_EVT );
if ( pEvent->gap.hdr.status == SUCCESS )
{
#if(defined(HAL_SLEEP)) && (HAL_SLEEP == TRUE)
SleepTimer = 0;
#endif
centralState = BLE_STATE_CONNECTED;
centralConnHandle = pEvent->linkCmpl.connectionHandle;
// Initiate service discovery
if ( Device_Ctrl.IsDirectConnect == FALSE )
{
centralProcedureInProgress = TRUE;
tmos_memcpy( Device_Ctrl.addr, pEvent->linkCmpl.devAddr, B_ADDR_LEN );
// Update MTU
attExchangeMTUReq_t req = { .clientRxMTU = BLE_BUFF_MAX_LEN - 4, };
GATT_ExchangeMTU( centralConnHandle, &req, centralTaskId );
// Initiate service discovery
tmos_start_task( centralTaskId, START_SVC_DISCOVERY_EVT, DEFAULT_SVC_DISCOVERY_DELAY );
// See if initiate connect parameter update
// See if start RSSI polling
if ( centralRssi )
{
tmos_start_task( centralTaskId, START_READ_RSSI_EVT, DEFAULT_RSSI_PERIOD );
}
}
#if 1
else
{
BLEConnected = BLE_CONECTED;
}
#endif
if ( centralParamUpdate )
{
tmos_start_task( centralTaskId, START_PARAM_UPDATE_EVT, DEFAULT_PARAM_UPDATE_DELAY );
}
PRINT( "Connected...\n" );
}
else
{
PRINT( "Connect Failed...Reason:%X\n", pEvent->gap.hdr.status );
PRINT( "Discovering...\n" );
centralScanRes = 0;
GAPRole_CentralStartDiscovery( DEFAULT_DISCOVERY_MODE,
DEFAULT_DISCOVERY_ACTIVE_SCAN,
DEFAULT_DISCOVERY_WHITE_LIST );
}
}
break;
主机根据从机的广播信息和保存在FLASH中的从机信息判断是否是已绑定的设备。
以上两种情况都需要我们初始化阶段判断主机是否有绑定过设备。
uint16_t bonding = 0;
GAPBondMgr_GetParameter( GAPBOND_BOND_COUNT, &bonding );
if ( bonding != 0 )
{
gapBondRec_t bond_info;
uint8_t Adv_Direct_Addr[B_ADDR_LEN + 1];
tmos_snv_read( mainRecordNvID( 0 ), sizeof(gapBondRec_t), &bond_info );
}
3、第三种情况
重连时与第一次配对没有区别。
四、对从机发送的Read By Type Request的响应
最近的一个项目,主机与从机配对时,枚举从机的服务、characteristic后,从机发送Read By Type Request,等待主机响应,求助百度、deepseek都没有找到解决的办法。(这里顺带提一下deepseek在提供有关沁恒蓝牙芯片的解决方案的代码时,提供的代码根本就没办法与官方SDK兼容,不知道是什么原因?)
抓包分析了一下,从机想要获取的是主机的Device Name,于是我们想是否是没有设置主机的Device Name导致主机没有响应从机的Read By Type Request?
void Central_Init()
{
centralTaskId = TMOS_ProcessEventRegister( Central_ProcessEvent );
// Setup GAP
GAP_SetParamValue( TGAP_DISC_SCAN, DEFAULT_SCAN_DURATION );
GAP_SetParamValue( TGAP_CONN_EST_INT_MIN, DEFAULT_MIN_CONNECTION_INTERVAL );
GAP_SetParamValue( TGAP_CONN_EST_INT_MAX, DEFAULT_MAX_CONNECTION_INTERVAL );
GAP_SetParamValue( TGAP_CONN_EST_SUPERV_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT );
// Setup the GAP Bond Manager
uint32_t passkey = DEFAULT_PASSCODE;
uint8_t pairMode = DEFAULT_PAIRING_MODE;
uint8_t mitm = DEFAULT_MITM_MODE;
uint8_t ioCap = DEFAULT_IO_CAPABILITIES;
uint8_t bonding = DEFAULT_BONDING_MODE;
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);
GATT_InitClient();
// Register to receive incoming ATT Indications/Notifications
GATT_RegisterForInd( centralTaskId );
HAL_KEY_RegisterForKeys( centralTaskId );
GGS_AddService( GATT_ALL_SERVICES ); // GAP
GATTServApp_AddService( GATT_ALL_SERVICES ); // GATT attributes
GGS_SetParameter( GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN, ( uint8_t * ) centralDeviceName );
// Setup a delayed profile startup
tmos_set_event( centralTaskId, START_DEVICE_EVT );
}
加入这行代码: GGS_SetParameter( GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN, ( uint8_t * ) centralDeviceName );
重新测试,主机能正常响应从机的Read By Type Request。