Appearance
安全配对绑定
实现这一个的时候实际还是依赖GATT和ATT的网络模型
名词解释
Paring(配对),配对包括配对能力交换,设备认证,密钥生成,连接加密以及机密信息分发等过程,配对的目的有三个:加密连接,认证设备,以及生成密钥。
Bonding(绑定),配对过程中会生成一个长期密钥(LTK,long-term Key),如果配对双方把这个LTK存储起来放在Flash中,那么这两个设备再次重连的时候,就可以跳过配对流程,而直接使用LTK对蓝牙连接进行加密,设备的这种状态称为bonding。如果paring过程中不存储LTK(不分发LTK)也是可以的,paring完成后连接也是加密的,但是如果两个设备再次重连,那么就需要重走一次paring流程,否则两者还是明文通信。在不引起误解的情况下,我们经常把paring当成paring和 bonding两者的组合,因为只paring不bonding的应用情况非常少见。在不引起混淆的情况下,下文就不区分paring和bonding的区别,换句话说,我们会把paring和bonding两个概念等同起来进行混用。
SM(security manager),蓝牙协议栈的安全管理层,规定了跟蓝牙安全通信有关的所有要素,包括paring,bonding,以及下文提到的SMP。
SMP(security manager protocol),安全管理协议,SMP着重两个设备之间的蓝牙交互命令序列,对paring的空中包进行了严格时序规定。
OOB(out of band,带外),OOB就是不通过蓝牙射频本身来交互,而是通过比如人眼,NFC,UART等带外方式来交互配对信息,在这里人眼,NFC,UART通信方式就被称为OOB通信方式。
Passkey(又称pin码),是指用户在键盘中输入的一串数字,以达到认证设备的目的。低功耗蓝牙的passkey必须为6位。
客户端(发起端)进行输入
Numeric comparison(数字比较),numeric comparison其实跟passkey一样,也是用来认证设备的,只不过passkey是通过键盘输入的,而numeric comparison是显示在显示器上的,numericcomparison也必须是6位的数字。
直接显示一下, 手机弹出数字, 蓝牙设备输入这一个设备
justwork: 不进行认证, 直接进行连接
直接进行设备的连接
MITM(man in the middle),MITM是指A和B通信过程中,C会插入进来以模拟A或者B,并且具备截获和篡改A和B之间所有通信报文的能力,从而达到让A或者B信任它,以至于错把C当成B或者A来通信。如果对安全要求比较高,需要具备MITM保护能力,在SM中这个是通过认证(authentication)来实现的,SM中实现认证的方式有三种:OOB认证信息,passkey以及numeric comparison,大家根据自己的实际情况,选择其中一种即可。
LESC(LE security connections)- 新式配对,又称SC,蓝牙4.2引入的一种新的密钥生成方式和验证方式,SC通过基于椭圆曲线的Diffie-Hellman密钥交换算法来生成设备A和B的共享密钥,此密钥生成过程中需要用到公私钥对,以及其他的密码算法库。LESC同时还规定了相应的通信协议以生成该密钥,并验证该密钥。需要注意的是LESC对paring的其他方面也会产生一定的影响,所以我们经常会把LESC看成是一种新的配对方式。
Legacy paring - 老式配对,在LESC引入之前的密钥生成方式,称为legacy paring,换句话说,legacy paring是相对LESC来说的,不支持LESC的配对即为legacy paring(legacy配对)。
TK(Temporary Key,临时密钥),legacy paring里面的概念,如果采用just work配对方式,TK就是为全0;如果采用passkey配对方式,TK就是passkey;如果采用OOB配对方式,TK就是OOB里面的信息。
STK(short term key,短期密钥),legacy配对里面的概念,STK是通过TK推导出来的,通过TK对设备A和B的随机数进行加密,即得到STK。
LTK(long term key,长期密钥),legacy配对和LESC配对都会用到LTK,如前所述,LTK是用来对未来的连接进行加密和解密用的。Legacy paring中的LTK由从设备根据相应的算法自己生成的(LTK生成过程中会用到EDIV(分散因子)和Rand(随机数)),然后通过蓝牙空中包传给主机。LESC配对过程中,先通过Diffie-Hellman生成一个共享密钥,然后这个共享密钥再对设备A和B的蓝牙地址和随机数进行加密,从而得到LTK,LTK由设备A和B各自同时生成,因此LTK不会出现在LESC蓝牙空中包中,大大提高了蓝牙通信的安全性。
IRK(Identity Resolving Key,蓝牙设备地址解析密钥),有些蓝牙设备的地址为可解析的随机地址,比如iPhone手机,由于他们的地址随着时间会变化,那如何确定这些变化的地址都来自同一个设备呢?答案就是IRK,IRK通过解析变化的地址的规律,从而确定这些地址是否来自同一个设备,换句话说,IRK可以用来识别蓝牙设备身份,因此其也称为Identity information。IRK一般由设备出厂的时候按照一定要求自动生成。
Identity Address(设备唯一地址),蓝牙设备地址包括public,random static, privateresolvable,random unresolved共四类。如果设备不支持privacy,那么identity address就等于public或者random static设备地址。如果设备支持privacy,即使用private resolvable蓝牙设备地址,在这种情况下,虽然其地址每隔一段时间会变化一次,但是identity address仍然保持不变,其取值还是等于内在的public或者random static设备地址。Identity Address和IRK都可以用来唯一标识一个蓝牙设备。
IO capabilities(输入输出能力),是指蓝牙设备的输入输出能力,比如是否有键盘,是否有显示器,是否可以输入Yes/No两个确认值。Key size(密钥长度),一般来说,密钥默认长度为16字节,为了适应一些低端的蓝牙设备处理 能力,你也可以把密钥长度调低,比如变为10个字节。
认证要求
使用OOB和MITM的区别是使用OOB的时候, 需要两端同时输入16位数字
使用ESP32的MITM的时候是使用密钥的方式, 这里的检查实际是查看是不是设置了IO
通过双方的IO配置设置实际的IO方式, 主要使用的是老式配对
(这一个数字比较好像不会触发)
实际实现
主要是设置参数这一步是不同的
客户端
主要认证是GAP层
服务端
实际实现的时候需要实现
实际实现
实际参数设置
c
/***************************************************************************/
/* set the security iocap & auth_req & key size & init key response key parameters to the stack*/
// 设置BOND的时候, 会记录连接, 下一次连接的时候跳过认证
// esp_ble_auth_req_t auth_req = ESP_LE_AUTH_NO_BOND; //未启用绑定
// esp_ble_auth_req_t auth_req = ESP_LE_AUTH_BOND; //启用绑定
// esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_MITM; //开启MITM保护
// esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_ONLY; //未启用绑定的安全连接
// esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_BOND; //启用绑定后的安全连接
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM; //使用MITM保护和未启用连接的安全连接
// esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND; //安全连接,启用MITM(中间人)保护和连接
//设置IO类型, 这一个类型会使得MITM认证的发生区别
// esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; //NoInputNoOutput
esp_ble_io_cap_t iocap = ESP_IO_CAP_KBDISP; //Keyboard display
// esp_ble_io_cap_t iocap = ESP_IO_CAP_IO; //DisplayYesNo
// esp_ble_io_cap_t iocap = ESP_IO_CAP_OUT; //DisplayOnly
// esp_ble_io_cap_t iocap = ESP_IO_CAP_IN; //KeyboardOnly
uint8_t key_size = 16; //the key size should be 7~16 bytes
//发起密钥, 长期密钥以及身份解析密钥
uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
//set static passkey
uint32_t passkey = 123456; //密码
uint8_t auth_option = ESP_BLE_ONLY_ACCEPT_SPECIFIED_AUTH_DISABLE;
// uint8_t auth_option = ESP_BLE_ONLY_ACCEPT_SPECIFIED_AUTH_ENABLE; //必须绑定才能开启
uint8_t oob_support = ESP_BLE_OOB_DISABLE; //关闭OOB, 这时候使用其他的方式认证
// uint8_t oob_support = ESP_BLE_OOB_ENABLE; //开启OOB
//设置静态密钥, 密码是一个uint32_t的值
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t));
//设置认证要求 - 身份验证以后与对方设备绑定
esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(uint8_t));
// 设置本地io的能力
esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
//设置加密密钥的长度(OOP)
esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t));
//关闭仅接受指定的SMP的身份验证的要求
esp_ble_gap_set_security_param(ESP_BLE_SM_ONLY_ACCEPT_SPECIFIED_SEC_AUTH, &auth_option, sizeof(uint8_t));
//关闭支持OOB
esp_ble_gap_set_security_param(ESP_BLE_SM_OOB_SUPPORT, &oob_support, sizeof(uint8_t));
/* If your BLE device acts as a Slave, the init_key means you hope which types of key of the master should distribute to you,
and the response key means which key you can distribute to the master;
If your BLE device acts as a master, the response key means you hope which types of key of the slave should distribute to you,
and the init key means which key you can distribute to the slave. */
//设置密钥类型
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t));
c
//数字比较事件 - 安全配对的模式下面才有, 具体的设备需要看两方的输入输出
case ESP_GAP_BLE_NC_REQ_EVT:
/* The app will receive this evt when the IO has DisplayYesNO capability and the peer device IO also has DisplayYesNo capability.
show the passkey number to the user to confirm it with the number displayed by peer device. */
esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, true);
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GAP_BLE_NC_REQ_EVT, the passkey Notify number:%d", (int)param->ble_security.key_notif.passkey);
count_printf("ESP_GAP_BLE_NC_REQ_EVT");
break;
//安全请求
case ESP_GAP_BLE_SEC_REQ_EVT:
/* send the positive(true) security response to the peer device to accept the security request.
If not accept the security request, should send the security response with negative(false) accept value*/
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
count_printf("ESP_GAP_BLE_SEC_REQ_EVT");
break;
//密钥显示通知
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability.
///show the passkey number to the user to input it in the peer device.
ESP_LOGI(GATTS_TABLE_TAG, "The passkey Notify number:%06d", (int)param->ble_security.key_notif.passkey);
count_printf("ESP_GAP_BLE_PASSKEY_NOTIF_EVT");
break;
//对方设备密钥
case ESP_GAP_BLE_KEY_EVT:
//shows the ble key info share with peer device to the user.
ESP_LOGI(GATTS_TABLE_TAG, "key type = %s", esp_key_type_to_str(param->ble_security.ble_key.key_type));
count_printf("ESP_GAP_BLE_KEY_EVT");
break;
//认证完成
case ESP_GAP_BLE_AUTH_CMPL_EVT: {
esp_bd_addr_t bd_addr;
//记录一下地址
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
ESP_LOGI(GATTS_TABLE_TAG, "remote BD_ADDR: %08x%04x",\
(bd_addr[0] << 24) + (bd_addr[1] << 16) + (bd_addr[2] << 8) + bd_addr[3],
(bd_addr[4] << 8) + bd_addr[5]);
ESP_LOGI(GATTS_TABLE_TAG, "address type = %d", param->ble_security.auth_cmpl.addr_type);
ESP_LOGI(GATTS_TABLE_TAG, "pair status = %s",param->ble_security.auth_cmpl.success ? "success" : "fail");
if(!param->ble_security.auth_cmpl.success) {
ESP_LOGI(GATTS_TABLE_TAG, "fail reason = 0x%x",param->ble_security.auth_cmpl.fail_reason);
} else {
ESP_LOGI(GATTS_TABLE_TAG, "auth mode = %s",esp_auth_req_to_str(param->ble_security.auth_cmpl.auth_mode));
}
//显示绑定的设备的id
show_bonded_devices();
// remove_all_bonded_devices();
count_printf("ESP_GAP_BLE_AUTH_CMPL_EVT");
break;
}
case ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT: {
ESP_LOGD(GATTS_TABLE_TAG, "ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT status = %d", param->remove_bond_dev_cmpl.status);
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GAP_BLE_REMOVE_BOND_DEV");
ESP_LOGI(GATTS_TABLE_TAG, "-----ESP_GAP_BLE_REMOVE_BOND_DEV----");
esp_log_buffer_hex(GATTS_TABLE_TAG, (void *)param->remove_bond_dev_cmpl.bd_addr, sizeof(esp_bd_addr_t));
ESP_LOGI(GATTS_TABLE_TAG, "------------------------------------");
count_printf("ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT");
break;
}
case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT:
if (param->local_privacy_cmpl.status != ESP_BT_STATUS_SUCCESS){
ESP_LOGE(GATTS_TABLE_TAG, "config local privacy failed, error status = %x", param->local_privacy_cmpl.status);
break;
}
esp_err_t ret = esp_ble_gap_config_adv_data(&heart_rate_adv_config);
if (ret){
ESP_LOGE(GATTS_TABLE_TAG, "config adv data failed, error code = %x", ret);
}else{
adv_config_done |= ADV_CONFIG_FLAG;
}
ret = esp_ble_gap_config_adv_data(&heart_rate_scan_rsp_config);
if (ret){
ESP_LOGE(GATTS_TABLE_TAG, "config adv data failed, error code = %x", ret);
}else{
adv_config_done |= SCAN_RSP_CONFIG_FLAG;
}
count_printf("ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT");
break;
c
case ESP_GATTS_CONNECT_EVT:
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_CONNECT_EVT");
/* start security connect with peer device when receive the connect event sent by the master */
//接收到客户端连接 启动对等的安全连接
//传参影响是否使用esp_ble_gap_set_security_param 函数的确认类型
//esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT_MITM); //使用MITM,且不管安全参数函数配置
//esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT_NO_MITM); //不使用MITM,且不管安全参数函数配置
esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT); //再次交换密钥
//拷贝对方设备地址
memcpy(dev_addr,param->connect.remote_bda,sizeof(esp_bd_addr_t));
count_printf("ESP_GATTS_CONNECT_EVT");
break;
参数
安全模式
c
ESP_LE_AUTH_NO_BOND:不进行绑定
ESP_LE_AUTH_BOND:设备与对等体进行绑定
ESP_LE_AUTH_REQ_MITM:需要进行中间人攻击验证
ESP_LE_AUTH_REQ_BOND_MITM:需要进行绑定和中间人攻击验证
ESP_LE_AUTH_REQ_SC_ONLY:仅使用安全连接
ESP_LE_AUTH_REQ_SC_BOND:需要进行绑定并使用安全连接
ESP_LE_AUTH_REQ_SC_MITM:需要进行中间人攻击验证并使用安全连接
ESP_LE_AUTH_REQ_SC_MITM_BOND:需要进行绑定、中间人攻击验证和使用安全连接
这里连接的时候使用ESP_LE_AUTH_BOND即可, 会记录连接的信息, 使用SC和MITM会增加一个认证的过程
设备
c
ESP_IO_CAP_OUT:表示设备只能在显示设备上显示信息,但没有输入能力。在BTM_IO_CAP_xxx中对应的是DisplayOnly。
ESP_IO_CAP_IO:表示设备既能在显示设备上显示信息,又能进行确认操作(Yes/No)。在BTM_IO_CAP_xxx中对应的是DisplayYesNo。
ESP_IO_CAP_IN:表示设备只能进行键盘输入操作,但没有显示能力。在BTM_IO_CAP_xxx中对应的是KeyboardOnly。
ESP_IO_CAP_NONE:表示设备既没有显示能力,也没有输入能力。在BTM_IO_CAP_xxx中对应的是NoInputNoOutput。
ESP_IO_CAP_KBDISP:表示设备同时具有键盘输入和显示功能。在BTM_IO_CAP_xxx中对应的是KeyboardDisplay。
ESP_IO_CAP_NONE可以实现最简单的连接方式