OOB配对原理及应用

news/2025/2/7 6:54:34/

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

  • 前言
  • 一、OOB是啥?
  • 二、OOB配对实践
  • 总结


前言

  本文先简单介绍OOB配对的流程,然后结合CC2652蓝牙芯片调试OOB配对

一、OOB是啥?

  OOB就是Out of Band的缩写,指安全数据不经过自己通信的信道进行传输。OOB允许两个蓝牙设备通过设备的带外通道发送认证信息,比如:串口、NFC、UWB等。OOB的目的是允许两个设备在没有输入输出能力的情况下创建认证配对,认证需要发送3个认证参数:device addressrandom numberconfirm value。它这种认证配对方式能很好的反MITM攻击。方法就是在主机或者从机中产生一个密钥,然后OOB数据通过蓝牙通信之外的方式发送给其他设备。OOB数据可以选择使用椭圆曲线加密算法生成(也可以不使用)。流程大致是这样的:
  1.创建存放OOB数据的变量,有两个,一个是用于存放设备自己本身的OOB数据,另一个是用于存放对端设备的OOB数据。
  2.定义配对状态的回调函数。
  3.要用ECC密钥产生OOB数据,先调用GAPBondMgr_GenerateEccKeys()产生ECC密钥,然后在ECC密钥产生的事件中再调用GAPBondMgr_SCGetLocalOOBParameters()获取OOB数据。当协议栈的ECC密钥准备好了之后,协议栈会发送事件GAPBOND_GENERATE_ECC_DONE给应用层,该事件在配对状态回调里面处理。上面提到的两个API函数可以在蓝牙连接建立前或者后调用,但是必须是在配对开始前被调用。如果不调用GAPBondMgr_GenerateEccKeys(),那么OOB数据是没使用ECC密钥生成的。
  4.在OOB数据生成之后,设备应该将它发送到对端设备,对端设备的配对特性必须是使能了OOB配对绑定的。如果两个设备都产生和分享OOB数据,那么这两个设备的配对特性都必须使能OOB配对绑定。
  5.对端设备接收到OOB数据(Confirm值和随机数)之后,应使用函数接口GAPBondMgr_SCSetRemoteOOBParameters()记录下接收到的OOB数据到绑定管理器中。
  6.设备发起配对请求,走配对流程,整个过程两设备不存在输入输出显示操作,配对完成之后两设备BLE协议栈会向应用层发出事件GAPBOND_PAIRING_STATE_COMPLETE

二、OOB配对实践

  下面我们先做两个实验:
  (1)一个设备产生OOB数据,另一个设备不产生OOB数据,实现OOB配对。
  (2)两个设备都产生OOB数据,实现OOB配对。
  在(1)中,做法可以是这样:
  在IAR中导入CC26XX的SimpleCentral例程(不要在CCS导入,因为调用ECC密钥产生会报错,修改比较麻烦)。
-1- 先定义两个变量:

#ifdef USING_OOB_PAIR
// This is needed for the device that generates OOB data
gapBondOOBData_t localOobData;
// This is needed to store the OOB data this device receives
gapBondOOBData_t remoteOobData;
#endif

-2- 在GAP_DEVICE_INIT_DONE_EVENT事件中添加ECC密钥产生函数,该函数调用成功之后会产生一个事件:GAPBOND_GENERATE_ECC_DONE

GAPBondMgr_GenerateEccKeys();

-3- 在SimpleCentral_processPairState函数里面添加一个事件:

static void SimpleCentral_processPairState(uint8_t state,scPairStateData_t* pPairData)
{uint8_t status = pPairData->status;uint8_t pairMode = 0;if (state == GAPBOND_GENERATE_ECC_DONE){if (status == SUCCESS){//After we get the ECC key, we can get the OOB data which is generated by ECCGAPBondMgr_SCGetLocalOOBParameters(&localOobData);uint8_t i;for (i = 0; i < KEYLEN; i++){Display_printf(dispHandle, SC_ROW_CUR_CONN+i+5, 0, "OOB data confirm[%d]: 0x%2x", i, localOobData.confirm[i]);Display_printf(dispHandle, SC_ROW_CUR_CONN+KEYLEN+i+5, 0, "OOB data rand[%d]: 0x%2x", i, localOobData.rand[i]);}}}else if (state == GAPBOND_PAIRING_STATE_STARTED)...
}

-4- 编译运行Central例程,使用串口助手(我使用的是Xshell)查看Central打印出的OOB数据,包括了Confirm值和随机数。
-5- 至此,Central例程的代码已经改完了,现在改从机代码,从机代码我使用的是ProjectZero,同样,建一个变量:

#ifdef USING_OOB_PAIR
// This is needed for the device that generates OOB data
gapBondOOBData_t localOobData;
// This is needed to store the OOB data this device receives
gapBondOOBData_t remoteOobData;//we'll use
#endif

-6- 直接将Central串口打印出来的Confirm值和随机数赋值到remoteOobData,然后存储到配对管理器中:

#ifdef USING_OOB_PAIR
void oob_ready(void)
{uint8_t u8OOB_Enable = true;GAPBondMgr_SetParameter(GAPBOND_OOB_ENABLED, sizeof(uint8_t), &u8OOB_Enable);remoteOobData.confirm[0]  = 0xe0;remoteOobData.confirm[1]  = 0xa6;remoteOobData.confirm[2]  = 0xb5;remoteOobData.confirm[3]  = 0x76;remoteOobData.confirm[4]  = 0x3d;remoteOobData.confirm[5]  = 0x30;remoteOobData.confirm[6]  = 0xe6;remoteOobData.confirm[7]  = 0xa1;remoteOobData.confirm[8]  = 0x9f;remoteOobData.confirm[9]  = 0xa1;remoteOobData.confirm[10] = 0x9f;remoteOobData.confirm[11] = 0x89;remoteOobData.confirm[12] = 0xb0;remoteOobData.confirm[13] = 0xa2;remoteOobData.confirm[14] = 0x1f;remoteOobData.confirm[15] = 0x94;remoteOobData.rand[0]  = 0xec;remoteOobData.rand[1]  = 0x04;remoteOobData.rand[2]  = 0xf8;remoteOobData.rand[3]  = 0xa4;remoteOobData.rand[4]  = 0xd8;remoteOobData.rand[5]  = 0x69;remoteOobData.rand[6]  = 0x39;remoteOobData.rand[7]  = 0x94;remoteOobData.rand[8]  = 0xce;remoteOobData.rand[9]  = 0xf9;remoteOobData.rand[10] = 0xf0;remoteOobData.rand[11] = 0x1d;remoteOobData.rand[12] = 0x1e;remoteOobData.rand[13] = 0xfb;remoteOobData.rand[14] = 0x45;remoteOobData.rand[15] = 0x1c;GAPBondMgr_SCSetRemoteOOBParameters(&remoteOobData, 1);
}
#endif

-7- 在ProjectZero的GAP_DEVICE_INIT_DONE_EVENT事件之中添加该函数

static void ProjectZero_processGapMessage(gapEventHdr_t *pMsg)
{...case GAP_DEVICE_INIT_DONE_EVENT:{...oob_ready();break;
...

-8- 注意,这个期间,Central不要复位,复位后OOB数据就不一样啦。编译运行ProjectZero,这里建议把ProjectZero的蓝牙地址模式设置成Public,方便Central那边扫描连接。使用Central连接ProjectZero,如果不知道怎么操作的话,可以看下工程里边的Readme。
在这里插入图片描述
-9- 扫描到从机设备之后connect to它,由于Central是配对请求发起端,在建立连接之后马上发起配对,这个时候我们可以在Central的串口助手上看到“Pairing success”和“Bond save success”信息,表示OOB配对成功。

-10- 断开连接,重新扫描连接,你可以看到“Encryption success”信息,表示之后的连接都是加密的,也从侧面表示配对成功了。

  上面介绍了一个设备产生OOB数据,另一个设备不产生OOB数据实现OOB配对,下面就继续介绍双方都产生OOB数据实现配对应该怎么做。这里先用SmartRF flash program擦除下两个芯片,因为我们前面已经实现配对绑定了,需要擦除绑定信息,好让我们做第二个实验测试。
  在(2)中,我们需要考虑两个问题,从机怎么把自己的OOB数据发送给主机呢?主机需要在什么时候才发起配对请求?第一个问题,在这里我使用最简单的方法,就是L2CAP传输OOB数据,虽然也是带内,不过没关系,只要把数据传过去就行了。第二个问题,我们肯定是让OOB数据准备好了之后再走配对流程。
在这里插入图片描述

-1- 先把L2CAP Coc通信打通,我们需要做四件事:
在这里插入图片描述
  双方都把L2CAP Connection Oriented Channel…勾选上
在这里插入图片描述
  对于Central,需要把配对模式改成不允许配对,这样在建立连接之后就不会立即配对了。
在这里插入图片描述
  下面是两个设备注册SPSM的代码段:

#define APP_SPSM   0x0080
void L2CAP_vidConfig_Init( void )
{l2capPsm_t     psm;l2capPsmInfo_t psmInfo;if(L2CAP_PsmInfo(APP_SPSM, &psmInfo) == INVALIDPARAMETER){psm.initPeerCredits = 0xFFFF;psm.maxNumChannels = MAX_NUM_BLE_CONNS;psm.mtu = MAX_PDU_SIZE;psm.peerCreditThreshold = 0;psm.pfnVerifySecCB = NULL;psm.psm = APP_SPSM;psm.taskId = ICall_getLocalMsgEntityId(ICALL_SERVICE_CLASS_BLE_MSG, selfEntity);L2CAP_RegisterPsm(&psm);}else{//Failed}
}

-1.1- 在Central一方的建立连接事件里添加L2CAP连接请求

// Send out L2CAP_LE_CREDIT_BASED_CONNECTION_REQ
L2CAP_ConnectReq(connHandle, App_SPSM, App_SPSM);

  两个设备都可以发送L2CAP Coc建立连接请求,但是不需要两个设备都发起。
-1.2- 在Central一方添加下面的代码段,"…"是我省略了一些例程本来就有的代码。

#if defined(USING_L2CAP)
// 在原有的connRec_t类型里添加cocCID
// Connected device information
typedef struct
{uint16_t connHandle;        // Connection Handleuint16_t charHandle;        // Characteristic Handleuint8_t  addr[B_ADDR_LEN];  // Peer Device AddressClock_Struct *pRssiClock;   // pointer to clock structuint16_t cocCID;            // CID for the L2CAP channel
} connRec_t;
static void L2CAP_vidConfig_Init( void );
static bStatus_t Application_sendL2capData(uint8_t connHandle, uint16_t datalen, uint8_t *pdata);
static void Application_processL2CAPDataEvent(l2capDataEvent_t *pMsg);
static void Application_processL2CAPSignalEvent(l2capSignalEvent_t *pMsg);
#endifstatic uint8_t SimpleCentral_processStackMsg(ICall_Hdr *pMsg)
{switch (pMsg->event){...case L2CAP_SIGNAL_EVENT:// place holder for L2CAP Connection Parameter ReplyApplication_processL2CAPSignalEvent((l2capSignalEvent_t *)pMsg);break;case L2CAP_DATA_EVENT://L2CAP接收数据处理Application_processL2CAPDataEvent((l2capDataEvent_t *)pMsg);break;default:break;}...
}#if defined(USING_L2CAP)
#define APP_SPSM   0x0080
void L2CAP_vidConfig_Init( void )//在GAP_DEVICE_INIT_DONE_EVENT事件里面调用
{l2capPsm_t     psm;l2capPsmInfo_t psmInfo;if(L2CAP_PsmInfo(APP_SPSM, &psmInfo) == INVALIDPARAMETER){psm.initPeerCredits = 0xFFFF;psm.maxNumChannels = MAX_NUM_BLE_CONNS;psm.mtu = MAX_PDU_SIZE;psm.peerCreditThreshold = 0;psm.pfnVerifySecCB = NULL;psm.psm = APP_SPSM;psm.taskId = ICall_getLocalMsgEntityId(ICALL_SERVICE_CLASS_BLE_MSG, selfEntity);L2CAP_RegisterPsm(&psm);}else{//Failed}
}bStatus_t Application_sendL2capData(uint8_t connHandle, uint16_t datalen, uint8_t *pdata)
{l2capPacket_t pkt;bStatus_t status = SUCCESS;uint8_t connIndex;if(datalen != 0){connIndex = SimpleCentral_getConnIndex(connHandle);pkt.CID = connList[connIndex].cocCID;pkt.pPayload = pdata;pkt.len = datalen;status = L2CAP_SendSDU(&pkt);}else{/*not need to do*/status = FAILURE;}return (status);
}/******************************************************************************
* @fn          Application_processL2CAPDataEvent
*
* @brief       This function is used to handle the L2CAP data extraction.
*
* @param       pMsg - pointer to the signal that was received
*
* @return      None.
*/
#define OOB_DATA  0
void Application_processL2CAPDataEvent(l2capDataEvent_t *pMsg)
{if (!pMsg){// Caller needs to figure out by himself that pMsg is NULLreturn;}// The data locates under pMsg->pkt.pPayload// Extract the data and do what you want to doswitch(pMsg->pkt.pPayload[0]){case OOB_DATA:memcpy(remoteOobData.confirm, &(pMsg->pkt.pPayload[1]), KEYLEN);memcpy(remoteOobData.rand, &(pMsg->pkt.pPayload[16+1]), KEYLEN);GAPBondMgr_SCSetRemoteOOBParameters(&remoteOobData, 1);uint8_t oobEnabled = TRUE;GAPBondMgr_SetParameter(GAPBOND_OOB_ENABLED, sizeof(uint8_t), &oobEnabled);uint8_t pairMode = GAPBOND_PAIRING_MODE_INITIATE;GAPBondMgr_SetParameter(GAPBOND_PAIRING_MODE, sizeof(uint8_t), &pairMode);GAPBondMgr_Pair(pMsg->connHandle);break;default:break;}// Free the payload (must use BM_free here)BM_free(pMsg->pkt.pPayload);
}/******************************************************************************
* @fn          Application_processL2CAPSignalEvent
*
* @brief       This function is used to handle all the L2CAP signal events.
*
* @param       pMsg - pointer to the signal that was received
*
* @return      None.
*/
static void Application_processL2CAPSignalEvent(l2capSignalEvent_t *pMsg)
{uint8_t SendOOBData_BUFF[33] = {(uint8_t)OOB_DATA};//1st byte is 0, and others are OOBDATAif (!pMsg){return;}switch (pMsg->opcode){case L2CAP_CHANNEL_ESTABLISHED_EVT:{l2capChannelEstEvt_t *pEstEvt = &(pMsg->cmd.channelEstEvt);if (pMsg->connHandle != LINKDB_CONNHANDLE_INVALID && pMsg->connHandle < MAX_NUM_BLE_CONNS){// Successfully establish link over L2CAP// Extract te CIO and store in the application layer// This will be useful when sending data over L2CAP channelsconnList[pMsg->connHandle].cocCID = pEstEvt->CID;// Give max credits to the other sideL2CAP_FlowCtrlCredit(pEstEvt->CID, 0xFFFF);//make sure you have got the local OOB data from GAPBOND_GENERATE_ECC_DONE eventmemcpy(&SendOOBData_BUFF[1], (uint8_t *)&localOobData.confirm[0], 32);Application_sendL2capData((uint8_t)pMsg->connHandle, 33, SendOOBData_BUFF);}else{// Could not establish an L2CAP link}}break;case L2CAP_SEND_SDU_DONE_EVT:{if (pMsg->hdr.status == SUCCESS){// Successfully sending data over L2CAP}else{}}break;case L2CAP_CHANNEL_TERMINATED_EVT:{}break;}
}
#endif

-2- 在ProjectZero一方,同样添加L2CAP和OOB的相关代码:

void L2CAP_vidConfig_Init( void )//在GAP_DEVICE_INIT_DONE_EVENT事件里面调用
{l2capPsm_t     psm;l2capPsmInfo_t psmInfo;if(L2CAP_PsmInfo(APP_SPSM, &psmInfo) == INVALIDPARAMETER){psm.initPeerCredits = 0xFFFF;psm.maxNumChannels = MAX_NUM_BLE_CONNS;psm.mtu = MAX_PDU_SIZE;psm.peerCreditThreshold = 0;psm.pfnVerifySecCB = NULL;psm.psm = APP_SPSM;psm.taskId = ICall_getLocalMsgEntityId(ICALL_SERVICE_CLASS_BLE_MSG, selfEntity);L2CAP_RegisterPsm(&psm);}else{//Failed}
}/******************************************************************************
* @fn          Application_processL2CAPSignalEvent
*
* @brief       This function is used to handle all the L2CAP signal events.
*
* @param       pMsg - pointer to the signal that was received
*
* @return      None.
*/
void Application_processL2CAPSignalEvent(l2capSignalEvent_t *pMsg)
{// Sanity checkif (!pMsg){return;}switch (pMsg->opcode){case L2CAP_CHANNEL_ESTABLISHED_EVT:{l2capChannelEstEvt_t *pEstEvt = &(pMsg->cmd.channelEstEvt);if (pMsg->connHandle != LINKDB_CONNHANDLE_INVALID && pMsg->connHandle < MAX_NUM_BLE_CONNS){// Successfully establish link over L2CAP// Extract te CIO and store in the application layer// This will be useful when sending data over L2CAP channelsconnList[pMsg->connHandle].cocCID = pEstEvt->CID;// Give max credits to the other sideL2CAP_FlowCtrlCredit(pEstEvt->CID, 0xFFFF);}else{// Could not establish an L2CAP link}}break;case L2CAP_SEND_SDU_DONE_EVT:{if (pMsg->hdr.status == SUCCESS){// Successfully sending data over L2CAP}else{}}break;case L2CAP_CHANNEL_TERMINATED_EVT:{}break;}
}bStatus_t Application_sendL2capData(uint8_t connHandle, uint16_t datalen, uint8_t *pdata)
{l2capPacket_t pkt;bStatus_t status = SUCCESS;uint8_t connIndex;if(datalen != 0){connIndex = ProjectZero_getConnIndex(connHandle);pkt.CID = connList[connIndex].cocCID;pkt.pPayload = pdata;pkt.len = datalen;status = L2CAP_SendSDU(&pkt);}else{/*not need to do*/status = FAILURE;}return (status);
}/******************************************************************************
* @fn          Application_processL2CAPDataEvent
*
* @brief       This function is used to handle the L2CAP data extraction.
*
* @param       pMsg - pointer to the signal that was received
*
* @return      None.
*/
#define OOB_DATA  0
void Application_processL2CAPDataEvent(l2capDataEvent_t *pMsg)
{uint8_t SendOOBData_BUFF[33] = {0};//1st byte is 0, and others are OOBDATAif (!pMsg){// Caller needs to figure out by himself that pMsg is NULLreturn;}// The data locates under pMsg->pkt.pPayload// Extract the data and do what you want to doswitch(pMsg->pkt.pPayload[0]){case OOB_DATA:uint8_t u8OOB_Enable            =    true;GAPBondMgr_SetParameter(GAPBOND_OOB_ENABLED, sizeof(uint8_t), &u8OOB_Enable);memcpy(remoteOobData.confirm, &(pMsg->pkt.pPayload[1]), KEYLEN);memcpy(remoteOobData.rand, &(pMsg->pkt.pPayload[16+1]), KEYLEN);GAPBondMgr_SCSetRemoteOOBParameters(&remoteOobData, 1);GAPBondMgr_SCGetLocalOOBParameters(&localOobData);memcpy(&SendOOBData_BUFF[1], (uint8_t *)&localOobData.confirm[0], 32);Application_sendL2capData(pMsg->connHandle, 33, SendOOBData_BUFF);break;default:break;}// Free the payload (must use BM_free here)BM_free(pMsg->pkt.pPayload);
}static void ProjectZero_taskFxn(UArg a0, UArg a1)
{...case HCI_GAP_EVENT_EVENT:ProjectZero_processHCIMsg(pMsg);break;case L2CAP_SIGNAL_EVENT:Application_processL2CAPSignalEvent((l2capSignalEvent_t *)pMsg);break;case L2CAP_DATA_EVENT:Application_processL2CAPDataEvent((l2capDataEvent_t *)pMsg);break;default:// do nothingbreak;...
}

-3- 两个工程编译没报错之后分别下载到两个板子上,开始测试OOB配对,主机连上从机之后,待L2CAP建立连接之后,主机将自己的OOB数据通过L2CAP发送到从机,从机接收到主机OOB数据之后将自己的OOB数据通过L2CAP发给主机,主机接收到从机OOB数据之后发起配对。结果如下:

在这里插入图片描述

总结

  OOB配对主要是获取双方的3个参数,BDAddr,Confirm值,rand值,它比其它的配对(数值比较,Passkey Entry)简单,无需IO能力。
  在调试OOB的时候注意配对请求发起端的配对发起条件,需要明白在什么时候才发起配对。
  本文只是记录我调试OOB配对绑定的过程,如果有错误的地方,希望大伙们可以提出来,Thx~


http://www.ppmy.cn/news/77301.html

相关文章

windows11下系统睡眠状态被UpdateOrchestrator唤醒的解决方案

windows11下系统睡眠状态被UpdateOrchestrator唤醒的解决方案 一、问题排查二、问题解决 一、问题排查 最近win11更新后发现会偶尔在睡眠状态下唤醒&#xff0c;CMD中输入powercfg -lastwake命令可以查看唤醒源程序 这里显示唤醒是按下了电源按钮&#xff0c;符合我此次唤醒操…

AI五大神经网络模型

1.多层感知器 多层感知器(MLP)是一类前馈人工神经网络。感知器这个术语具体是指单个神经元模型&#xff0c;它是大型神经网络的前体。 MLP包括节点的三个主要层&#xff1a;输入层、隐藏层和输出层。在隐藏层和输出层中&#xff0c;每个节点都被视为使用非线性激活函数的神经元…

华为OD机试真题 Java 实现【统计匹配的二元组个数】【2023Q2 200分】

一、题目描述 给定两个数组A和B&#xff0c;若数组A的某个元素A[i]与数组B中的某个元素B[j]满足 A[i] B[j]&#xff0c;则寻找到一个值匹配的二元组(i, j)。 请统计在这两个数组A和B中&#xff0c;一共存在多少个这样的二元组。 二、输入描述 第一行输入数组A的长度M&…

K8s日志组件-Loki是如何存储数据的?

文章目录 为什么需要loki为什么不是EFK&#xff1f;Loki是如何存储数据的&#xff1f;底层的LSM treeB tree 和LSM tree的区别&#xff1f;Ref参考链接 为什么需要loki 日志记录本质上是一个事件。大多数语言、应用程序框架或库都支持日志&#xff0c;表现形式可以是字符串这样…

纯手写pyqt界面

from PyQt5.Qt import * import loggingclass Ui(QWidget):def __init__(self):super().__init__()self.ui()def ui(self):self.label_list [self.__creat_label() for _ in range(10)] #创建10个标签self.btn_list [self.__creat_buttons(objnamefbtn-{i},textfbutton-{i})…

Linux系统编程总结

day2 vim的三种工作模式 命令模式 vi hello.c zz 保存退出 2.编辑模式 i a o s &#xff08;有大写&#xff09;可以写东西 3.末行模式&#xff1a; 文本和末行模式不能直接切换 要切换回命令模式 再到末行模式&#xff0c;w:保存 q:退出 按两次esc回到命令模式 vim的基本…

SCSI介绍和SCSI命令承载于各类总线的方式

1. SCSI协议简介 小型计算机系统接口(SCSI&#xff0c;Small Computer System Interface&#xff09;是一种用于计算机及其周边设备之间&#xff08;硬盘、软驱、光驱、打印机、扫描仪等&#xff09;系统级接口的独立处理器标准。虽然名字里面带个接口&#xff0c;但实际上是一…

大Op和小op的含义及理解

大Op和小op的含义及理解 Stochastic order notation(随机有序符号)6.1.1 O p O_p Op​和 o p o_p op​之间的关系6.2 符号速记及其算数性质6.3 为什么 o p o_p op​和 O p O_p Op​符号很有用&#xff1f;6.4例子&#xff1a;均值估计的相合性参考&#xff1a; Stochastic orde…