关于配对绑定的一些原理内容这里不再重复介绍,看之前的几篇文档,静态密码,动态密码,连接时触发配对就可以了。
配对绑定的内容可能比较难懂,升入的学习需要去看规范,将前面的几篇相关文档看一遍实验一边再去看规范能更好理解相关理论。
配对绑定是一个完整的过程,只是绑定是可选的,绑定简单来说就是存储一个长期秘钥LTK,以方便以后加密。当然还分配了其他秘钥,这里不涉及。
绑定是在配对之后,要明确 所谓配对 目的就是加密链路,以让数据能加密传输,所以绑定肯定是在配对之后,因为绑定就是分发各种秘钥,所以肯定要加密传输不然被别人窃听到了,以后用 分发的秘钥 再加密链路就不安全了。
总之 配对的目的 就是单纯的加密链路,但是配对过程比较耗时(包括配对信息交换,用户输入配对码或带外传输配对码,协议层的配对确认交换和随机数交换以及确认验证,都没问题后才会生成链路加密秘钥来加密链路),如果为了数据始终都是加密传输而每次连接都去配对的话就比较麻烦,所以又定义了一个绑定过程,绑定过程是在 配对后链路加密的情况下 分发一个 LTK(其他秘钥这里不涉及),这个LTK就可以供以后直接加密链路,而不用进过繁琐的配对过程。
PS:其实LTK分配之后,每次重新连接时的加密并不是用LTK直接加密链路,而是双方交换一些信息(称为会话秘钥分散器),然后利用这些信息和LTK最终生成一个会话秘钥,真正的加密是用这个会话秘钥。
这里我实现一个 从机显示配对码,主机输入配对码的配对方式,配对码为随机的,从机的配对码从串口打印出来。
主机输入配对码这个配对方式由配对信息交换时是否存在MITM标志以及从机是否有显示装置决定,所以我们配对信息中需要设置MITM标志,以及将I/O能力设置为有显示。
绑定过程是否存在 取决于配对信息交换中是否设置了Bond标志,这里我们也要设置。
我们这里测试绑定时在绑定阶段只分发LTK,其他秘钥这里不涉及
配对绑定大致分为3个阶段:
1:配对信息的交换
2:生成STK(短期秘钥)加密链路
3:链路加密后就可以安全分发各种秘钥了。
大致的过程图如下所示
大部分的工作协议栈都做好了,上层要处理的就是设置一些 参数以及处理几个事件。
首先要处理 BLE_GAP_EVT_SEC_PARAMS_REQUEST 事件,当手机发来配对请求时就会收到这个事件。 需要调用 回复api回复配对参数。
简要代码如下。
case BLE_GAP_EVT_SEC_PARAMS_REQUEST:
printf("\r\n\r\n step : %d\r\n",++step_count);
printf("receive pair req\r\n");
init_sec();
err_code = sd_ble_gap_sec_params_reply(m_conn_handle, BLE_GAP_SEC_STATUS_SUCCESS, &g_pair_params, &keyset);
printf("err_code :%d\r\n",err_code);
break;
sd_ble_gap_sec_params_repl 的第三个参数g_pair_params参数就是要回复的配对绑定参数设置
具体设置如下
ble_gap_sec_params_t g_pair_params;
void init_sec(void){
g_pair_params.bond = 1;
g_pair_params.io_caps = 0;
g_pair_params.oob = 0;
g_pair_params.mitm = 1;
g_pair_params.min_key_size = 7;
g_pair_params.max_key_size = 16;
g_pair_params.kdist_central.enc = 1;
g_pair_params.kdist_central.id = 0;
g_pair_params.kdist_central.sign = 0;
g_pair_params.kdist_periph.enc = 1;
g_pair_params.kdist_periph.id = 0;
g_pair_params.kdist_periph.sign = 0;
}
IO能力及设备的输入输出能力,有以下几个值
#define BLE_GAP_IO_CAPS_DISPLAY_ONLY 0x00
#define BLE_GAP_IO_CAPS_DISPLAY_YESNO 0x01
#define BLE_GAP_IO_CAPS_KEYBOARD_ONLY 0x02
#define BLE_GAP_IO_CAPS_NONE 0x03
#define BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY 0x04
设置了MITM为1和io能力设置为0,这个组合表示配对过程中 设备会显示配对码,主机需要输入对应配对码。
Bond 设置为1表示需要绑定,则配对会存在绑定过程即LTK等秘钥的分发。
具体分发哪些秘钥也是可以控制,这里只说LTK,所以上面的设置中只设置了相互分发长期秘钥LTK,其他不需要设置
PS:之所以相互分发LTK是因为,如果手机本次作为主机连接了设备,设备作为从机,配对绑定后断开连接,当再次连接时如果手机依然是作为主机去连设备,那么加密时就需要 从机分发给手机的LTK。但是有的应用可能主从角色并不是固定的,下次可能 是设备作为主机去连手机那么 加密时 就需要上次绑定时 手机发给设备的LTK。 上面的情况我们设置了相互分发LTK,其实一般都是手机一直作为主机去连设备,这种情况我们只需要g_pair_params.kdist_periph.enc = 1;就可以了
既然是配对会存在分发秘钥过程,那么协议栈交换的秘钥存储在哪里以在绑定完成后返回给上层呢。
返回的秘钥就是存在 sd_ble_gap_sec_params_repl 的第四个参数keyset中
设置如下,即我们需要自己创建变量来存储分发的秘钥,因为这只用了LTK,所以其他两个指针设置为NULL就行了。
ble_gap_enc_key_t my_enc_key;
ble_gap_enc_key_t my_enc_key_center;
ble_gap_sec_keyset_t keyset;
void init_keyset(void){
keyset.keys_periph.p_enc_key = &my_enc_key;
keyset.keys_periph.p_id_key = NULL;
keyset.keys_periph.p_sign_key = NULL;
keyset.keys_central.p_enc_key = &my_enc_key_center;
keyset.keys_central.p_id_key = NULL;
keyset.keys_central.p_sign_key = NULL;
}
当 sd_ble_gap_sec_params_repl按如上设置回复后,设备这边就会显示配对码需要让手机输入,因为没有显示 屏这里通过串口打印出来。
上层会收到协议栈的BLE_GAP_EVT_PASSKEY_DISPLAY提交上来的显示事件。直接将配对码打印出来
case BLE_GAP_EVT_PASSKEY_DISPLAY:
printf("\r\n\r\n step : %d\r\n",++step_count);
printf("passkey: ");
for ( int i = 0; i < 6; i++ ){
printf("%c",p_ble_evt->evt.gap_evt.params.passkey_display.passkey[i]);
}
printf("\r\n");
break;
这之后基本都是协议栈内部进行了,当绑定完成后上层会收到协议栈的BLE_GAP_EVT_AUTH_STATUS事件表示完成了秘钥的分发。
我们在收到这个事件后打印相关的秘钥信息
case BLE_GAP_EVT_AUTH_STATUS:
flag = 1;
printf("\r\n\r\n step : %d\r\n",++step_count);
printf("keyset dispatch done\r\n");
printf("LTK :");
for(int i = 0; i < my_enc_key.enc_info.ltk_len; i++){
printf("%x",my_enc_key.enc_info.ltk[i]);
}
printf(" AUTH: %d ",my_enc_key.enc_info.auth);
printf(" LTK length: %d \r\n",my_enc_key.enc_info.ltk_len);
printf("EDIV: %x ",my_enc_key.master_id.ediv);
printf("rand:");
for(int i = 0; i < 8; i++){
printf("%x",my_enc_key.master_id.rand[i]);
}
printf("\r\n");
break;
ps: EDIV 和RAND是和LTK一起发送的,你可以将其看做是LTK的标示,当手机以后请求用LTK来进行加密时就会发送给从机EDIV和RAND让其确定要使用的LTK
到这里配对绑定过程就结束了,前面说过绑定的目的是为了下次链接需要安全链路时不再进行繁琐的配对过程,所以一般手机和一个设备绑定过后,当下次再连接时都会直接用以前绑定时的LTK来发起加密请求。
我们在收到这个信息后打印了加密请求的一些信息,打印了我们回复的LTK和请求的EDIV,RAND看是不是和上面绑定过程时分配的一样。
case BLE_GAP_EVT_SEC_INFO_REQUEST:
printf("\r\n\r\n step : %d\r\n",++step_count);
printf("enc need: %d id need:%d sign need:%d\r\n",
p_ble_evt->evt.gap_evt.params.sec_info_request.enc_info,
p_ble_evt->evt.gap_evt.params.sec_info_request.id_info,
p_ble_evt->evt.gap_evt.params.sec_info_request.sign_info);
printf("RSP: LTK :");
for(int i = 0; i < my_enc_key.enc_info.ltk_len; i++){
printf("%x",my_enc_key.enc_info.ltk[i]);
}
printf("\r\nEDIV :%x RANDOM:",
p_ble_evt->evt.gap_evt.params.sec_info_request.master_id.ediv);
for(int i = 0; i< 8; i++){
printf("%x",
p_ble_evt->evt.gap_evt.params.sec_info_request.master_id.rand[i]);
}
err_code=sd_ble_gap_sec_info_reply(m_conn_handle, &(my_enc_key.enc_info), NULL, NULL);
printf("\r\nencryption rsp err_code:%d\r\n",err_code);
break;
关于如何让手机发起配对,我们还是设置CCCD的访问安全要求 来实现在点击notify时,设备会返回安全不足,然后手机便会发起配对。具体可以看之前的
静态/动态配对码 的文章。
程序运行后如下图所示,连接上后,点击notify便会触发配对。
首先收到配对请求
之后协议栈会上传配对码,通过串口显示,手机端输入该配对码。
最后配对完成,打印了LTK和EDIV,RAND.
然后断开连接,再次连接设备,可以看到手机这次直接就发加密请求过来了。请求中的EDIV和RAND也和之前绑定时分配的一样。
最后贴一下相关代码,代码都是关于几个事件的处理,我全部都添加在了on_ble_evt这个事件处理函数中。
ble_gap_sec_params_t g_pair_params;
void init_sec(void){
g_pair_params.bond = 1;
g_pair_params.io_caps = 0;
g_pair_params.oob = 0;
g_pair_params.mitm = 1;
g_pair_params.min_key_size = 7;
g_pair_params.max_key_size = 16;
g_pair_params.kdist_central.enc = 1;
g_pair_params.kdist_central.id = 0;
g_pair_params.kdist_central.sign = 0;
g_pair_params.kdist_periph.enc = 1;
g_pair_params.kdist_periph.id = 0;
g_pair_params.kdist_periph.sign = 0;
}
ble_gap_sec_keyset_t sec_keyset;
ble_gap_conn_sec_mode_t sec_mode;
ble_gap_enc_key_t my_enc_key;
ble_gap_enc_key_t my_enc_key_center;
ble_gap_sec_keyset_t keyset;
void init_keyset(void){
keyset.keys_periph.p_enc_key = &my_enc_key;
keyset.keys_periph.p_id_key = NULL;
keyset.keys_periph.p_sign_key = NULL;
keyset.keys_central.p_enc_key = &my_enc_key_center;
keyset.keys_central.p_id_key = NULL;
keyset.keys_central.p_sign_key = NULL;
}
uint8_t step_count = 0;
static void on_ble_evt(ble_evt_t * p_ble_evt)
{
uint32_t err_code;
switch (p_ble_evt->header.evt_id)
{
case BLE_GAP_EVT_CONNECTED:
err_code = bsp_indication_set(BSP_INDICATE_CONNECTED);
APP_ERROR_CHECK(err_code);
m_conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
init_keyset();
printf("\r\nconnected\r\n");
break;
case BLE_GAP_EVT_DISCONNECTED:
err_code = bsp_indication_set(BSP_INDICATE_IDLE);
APP_ERROR_CHECK(err_code);
m_conn_handle = BLE_CONN_HANDLE_INVALID;
printf("\r\ndisconnected");
break;
case BLE_GAP_EVT_SEC_PARAMS_REQUEST:
printf("\r\n\r\n step : %d\r\n",++step_count);
printf("receive pair req\r\n");
init_sec();
err_code = sd_ble_gap_sec_params_reply(m_conn_handle, BLE_GAP_SEC_STATUS_SUCCESS, &g_pair_params, &keyset);
printf("err_code :%d\r\n",err_code);
// APP_ERROR_CHECK(err_code);
break;
case BLE_GAP_EVT_PASSKEY_DISPLAY:
printf("\r\n\r\n step : %d\r\n",++step_count);
printf("passkey: ");
for ( int i = 0; i < 6; i++ ){
printf("%c",p_ble_evt->evt.gap_evt.params.passkey_display.passkey[i]);
}
printf("\r\n");
break;
case BLE_GATTS_EVT_SYS_ATTR_MISSING:
// No system attributes have been stored.
err_code = sd_ble_gatts_sys_attr_set(m_conn_handle, NULL, 0, 0);
APP_ERROR_CHECK(err_code);
break;
case BLE_GAP_EVT_AUTH_STATUS:
printf("\r\n\r\n step : %d\r\n",++step_count);
printf("keyset dispatch done\r\n");
printf("LTK :");
for(int i = 0; i < my_enc_key.enc_info.ltk_len; i++){
printf("%x",my_enc_key.enc_info.ltk[i]);
}
printf(" AUTH: %d ",my_enc_key.enc_info.auth);
printf(" LTK length: %d \r\n",my_enc_key.enc_info.ltk_len);
printf("EDIV: %x ",my_enc_key.master_id.ediv);
printf("rand:");
for(int i = 0; i < 8; i++){
printf("%x",my_enc_key.master_id.rand[i]);
}
printf("\r\n");
break;
case BLE_GAP_EVT_SEC_INFO_REQUEST:
printf("\r\nenc need: %d id need:%d sign need:%d\r\n",p_ble_evt->evt.gap_evt.params.sec_info_request.enc_info,
p_ble_evt->evt.gap_evt.params.sec_info_request.id_info,
p_ble_evt->evt.gap_evt.params.sec_info_request.sign_info);
printf("RSP: LTK :");
for(int i = 0; i < my_enc_key.enc_info.ltk_len; i++){
printf("%x",my_enc_key.enc_info.ltk[i]);
}
printf("\r\nEDIV :%x RANDOM:",p_ble_evt->evt.gap_evt.params.sec_info_request.master_id.ediv);
for(int i = 0; i< 8; i++){
printf("%x",p_ble_evt->evt.gap_evt.params.sec_info_request.master_id.rand[i]);
}
err_code = sd_ble_gap_sec_info_reply(m_conn_handle, &(my_enc_key.enc_info), NULL, NULL);
printf("\r\nencryption rsp err_code:%d\r\n",err_code);
break;
default:
// No implementation needed.
break;
}
}