1 前言
本文介绍配对的一些相关理论知识,并且介绍如何实现”静态密码”的设定。这里准确的说法应该是叫配对码,而不是密码。输入这个这个配对码是配对过程中可选的一部分介绍如何设置静态密码前先介绍一下配对的相关知识(后面都直接叫密码,而不是配对码)起初未提供安全性的两个设备如果希望做一些需要安全性的工作,就必须先配对。配对涉及两个设备的身份认证,链路加密。如果配对时设置了绑定位,随后还会有一个秘钥分配。分配的秘钥用户可以存储在flash中这样两个设备再第二次重连时的安全启动会更快。而不需要像第一次一样需要再启动整个配对过程。配对的第一个过程首先是配对信息的交换,这些信息用于确定认证方式,以及后续是否需要分配密钥以及分配哪些密钥。
2 配对理论知识
交换的信息包括:
两端设备的输入输出能力如:是否有显示屏,键盘等。
是否需要绑定(如果设置了绑定位配对的)。
是否需要MITM,是否使用OOB等
这些信息会让BLE协议栈确定一种认证方式:
比如:
1:如果两端设备的输入输出能力有限,比如都没有键盘和显示器,认证方式就是just work,这其实就是没有认证,
2:如果两端设备一个有显示频,而另一个有键盘,而配对中设置了MITM保护。那么认证方式就是passkey entery。
一端会显示一个配对码,另一需要输入这个配对码。之后的配对才能正确进行下去。
3:如果设置了OOB,那么这个配对码就是通过另外的通信方式(如NFC)来发送的,而不是像上面一样一端显示一端输入。
nrf51822中设置如下:
#define SEC_PARAM_BOND 1 /**< Perform bonding. */
#define SEC_PARAM_MITM 1 /**< Man In The Middle protection not required. */
#define SEC_PARAM_IO_CAPABILITIES BLE_GAP_IO_CAPS_DISPLAY_ONLY /**< No I/O capabilities. */
#define SEC_PARAM_OOB 0 /**< Out Of Band data not available. */
/**@defgroup BLE_GAP_IO_CAPS GAP IO Capabilities
* @{ */
#define BLE_GAP_IO_CAPS_DISPLAY_ONLY 0x00 /**< Display Only. */
#define BLE_GAP_IO_CAPS_DISPLAY_YESNO 0x01 /**< Display and Yes/No entry. */
#define BLE_GAP_IO_CAPS_KEYBOARD_ONLY 0x02 /**< Keyboard Only. */
#define BLE_GAP_IO_CAPS_NONE 0x03 /**< No I/O capabilities. */
#define BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY 0x04 /**< Keyboard and Display. */
/**@} */
本文的密码设置就是第二种情况。显示的密码是可以随机的也可以是静态的。由于设备并没有显示器。但是我们仍然可以设置输入输出能力为有显示器,因为我们使用的是静态密码。
配对的过程不仅只是输入配对码这样,后续还会根据输入的配对码,以及两端设备交换的随机数来生成链路密钥来加密链路以及分配后续的长期密钥,身份解析密钥等需要的密钥配对相关的理论比较多,上面的描述只是一个大概的过程。配对过程的详细介绍在蓝牙规范的 安全章节中。
根据上面的理论描述,我们来总结一下:
我们需要的输入“密码”这个功能,其实是配对过程中的一部分。而配对过程又是需要首先交换配对信息,然后协议栈会根据交换的信息才决定是否有输入密码这一过程。
那么我们要做的有如下几步:
1: 首先设置要输入的静态密码
2: 设置配对时会交换的信息:根据上面的介绍如果我们需要手机输入密码,那么配对时就要设置只具有显示器(这样就会是一端显示,
一端输入,虽然我们真的没显示器,但是设置的是静态密码所以也是可以的),设置需要MITM攻击保护。
3:触发配对。
给一个本来不使用passkey的例子增加该功能,需要完成5件事情:
1. 设置ble_gap_sec_params_t 结构中的mitm为1;
2. 设置ble_gap_sec_params_t 结构中的io_caps(输入输出功能)变量为display 或者keyboard;
具体可以参考蓝牙核心文档中的table2.4 该表位于第三卷,H部分,2.3.5.1节。
3. 根据第二步骤的选择。
a) 如果选择了keyboard,当接收到BLE_GAP_EVT_AUTH_KEY_REQUEST 后需要使用sd_ble_gap_auth_key_reply()函数把认证密码回复给你的用户;
b)如果选择了display,在用户终端显示密码输入框,便于用户输入密码。
4. 认证密码可以用户指定或者随机生成,默认是随机生成,需要指定,
a)在main.c(gap_params_init()在的文件) 增加ble_opt_t 全局变量
static ble_opt_t m_static_pin_option;/**< Pointer to the struct containing static pin option. */
b)在gap_params_init()函数的最后添加:
uint8_t passkey[] = STATIC_PASSKEY;
m_static_pin_option.gap.passkey.p_passkey = passkey;
err_code = sd_ble_opt_set(BLE_GAP_OPT_PASSKEY, &m_static_pin_option);
APP_ERROR_CHECK(err_code);
其中 STATIC_PASSKEY 就是认证密码。
5. 为需要passkey才能访问服务设置相应权限,如下面蓝色加粗部分。
static uint32_t rx_char_add(ble_nus_t * p_nus, const ble_nus_init_t * p_nus_init)
{
/**@snippet [Adding proprietary characteristic to S110 SoftDevice] */
ble_gatts_char_md_t char_md;
ble_gatts_attr_md_t cccd_md;
ble_gatts_attr_t attr_char_value;
ble_uuid_t ble_uuid;
ble_gatts_attr_md_t attr_md;
memset(&cccd_md, 0, sizeof(cccd_md));
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
BLE_GAP_CONN_SEC_MODE_SET_ENC_WITH_MITM(&cccd_md.write_perm);
cccd_md.vloc = BLE_GATTS_VLOC_STACK;
memset(&char_md, 0, sizeof(char_md));
char_md.char_props.notify = 1;
char_md.p_char_user_desc = NULL;
char_md.p_char_pf = NULL;
char_md.p_user_desc_md = NULL;
char_md.p_cccd_md = &cccd_md;
char_md.p_sccd_md = NULL;
ble_uuid.type = p_nus->uuid_type;
ble_uuid.uuid = BLE_UUID_NUS_RX_CHARACTERISTIC;
memset(&attr_md, 0, sizeof(attr_md));
BLE_GAP_CONN_SEC_MODE_SET_ENC_WITH_MITM(&attr_md.read_perm);
BLE_GAP_CONN_SEC_MODE_SET_ENC_WITH_MITM(&attr_md.write_perm);
attr_md.vloc = BLE_GATTS_VLOC_STACK;
attr_md.rd_auth = 0;
attr_md.wr_auth = 0;
attr_md.vlen = 1;
memset(&attr_char_value, 0, sizeof(attr_char_value));
attr_char_value.p_uuid = &ble_uuid;
attr_char_value.p_attr_md = &attr_md;
attr_char_value.init_len = sizeof(uint8_t);
attr_char_value.init_offs = 0;
attr_char_value.max_len = BLE_NUS_MAX_RX_CHAR_LEN;
return sd_ble_gatts_characteristic_add(p_nus->service_handle,
&char_md,
&attr_char_value,
&p_nus->rx_handles);
/**@snippet [Adding proprietary characteristic to S110 SoftDevice] */
}
//首先定义一下静态密码,配对密码只能是 6-digit ASCII string
#defineSTATIC_PASSKEY "123456" /**< Static pin. */
//改结构体中可以设置静态密码
staticble_opt_t m_static_pin_option;
定义了这两个参数后,我们需要设置一下静态密码,设置的操作需要在协议栈初始化之后 所以我们将设置密码操作放在 gap_params_init()函数的最后
如下:
static void gap_params_init(void)
{
//前面都是设置一些设备名以及一些后续需要协商的连接参数
//详细解释在 串口透传剖析 中有说明
uint32_t err_code;
ble_gap_conn_params_tgap_conn_params;
ble_gap_conn_sec_mode_t sec_mode;
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
err_code=sd_ble_gap_device_name_set(&sec_mode,
(constuint8_t*DEVICE_NAME,trlen(DEVICE_NAME));
APP_ERROR_CHECK(err_code);
memset(&gap_conn_params, 0,sizeof(gap_conn_params));
gap_conn_params.min_conn_interval = MIN_CONN_INTERVAL;
gap_conn_params.max_conn_interval = MAX_CONN_INTERVAL;
gap_conn_params.slave_latency= SLAVE_LATENCY;
gap_conn_params.conn_sup_timeout= CONN_SUP_TIMEOUT;
err_code = sd_ble_gap_ppcp_set(&gap_conn_params);
APP_ERROR_CHECK(err_code);
//以下是设置静态密码操作
uint8_tpasskey[] = STATIC_PASSKEY; m_static_pin_option.gap_opt.passkey.p_passkey= passkey;
//该系统调用执行密码的设置操作。
err_code=sd_ble_opt_set(BLE_GAP_OPT_PASSKEY,&m_static_pin_option)
APP_ERROR_CHECK(err_code);
}
到这里设置静态密码的操作就做完了。
然后是设置配对时要交换的信息:
下面定义我们需要交换的信息的宏,也就是和安全参数相关的一些宏。
//这里只是演示静态密码,不需要绑定
#define SEC_PARAM_BOND 0
//因为要输入密码,就是一种MITM攻击保护,所以这里设置MITM
#define SEC_PARAM_MITM 1
//这里设置只有显示屏(其实没有,但是我们用的是事先知道的静态密码所以不// 需要显示)
#define SEC_PARAM_IO_CAPABILITIES BLE_GAP_IO_CAPS_DISPLAY_ONLY
//不使用带外数据
#define SEC_PARAM_OOB 0
//链路加密密钥的长度
#define SEC_PARAM_MIN_KEY_SIZE 7
#define SEC_PARAM_MAX_KEY_SIZE 16
定义了宏之后我们需要在设置参数,写一个如下的函数。
m_sec_params 是一个全局变量
ble_gap_sec_params_t m_sec_params;
static void sec_params_init(void)
{
m_sec_params.bond = SEC_PARAM_BOND;
m_sec_params.mitm =SEC_PARAM_MITM;
m_sec_params.io_caps =SEC_PARAM_IO_CAPABILITIES;
m_sec_params.oob =SEC_PARAM_OOB;
m_sec_params.min_key_size = SEC_PARAM_MIN_KEY_SIZE;
m_sec_params.max_key_size = SEC_PARAM_MAX_KEY_SIZE;
}
将该函数放在 main函数的初始化流程中的conn_params_init(); 函数之后。
设置的这个全局变量会在配对启动后的信息交换中使用(因为其内部值就是要交换的信息)。
到这里我们设置完了配对启动后会交换的信息。但是怎么把这个信息给对端设备呢? 先看完最后一步的触发配对的问题,再来解决将配对信息发给对端设备的问题。
最后一步触发配对:
配对的触发有以下几种情况:
1:主机直接发起。
2:从机发起安全请求,如果之前绑定过,那么主机会直接用用保存的LTK加密链路,如果没有那么主机会发起配对请求。
3:BLE中的有一个安全模式的概念。当某个属性被设置为需要认证的加密链路访问时,那么当在主机访问从机的属性器时,如果链路是不安全的就会返回错误,然后主机会发起配对请求从而实现安全要求。
我们采用的就是第三种 被动等待主机触发的方式,那么首先要做的就是将一些属性设置为需要安全的链路才能访问,那么手机在访问时就会触发配对过程了。
因为手机端使能notify是需要写CCCD的,所以我们将具有notify 性质RX 特征值的 cccd(客户端配置描述符)设置为需要认证和加密的安全链路。那么当手连上板子后 点击rx特征值的notify 按钮后主机会发一个 写命令写板子上的rx特征值的cccd,因为初试链路是不完全的,那么这时手机就会返回写出错,然后启动配对过程。
设置如下:
在添加RX特征值的函数中做如下的简单就可以了。
这里只截取部分代码:
static uint32_t rx_char_add(ble_nus_t * p_nus, constble_nus_init_t * p_nus_init)
{
/**@snippet [Addingproprietary characteristic to S110 SoftDevice] */
ble_gatts_char_md_tchar_md;
ble_gatts_attr_md_tcccd_md;
ble_gatts_attr_t attr_char_value;
ble_uuid_t ble_uuid;
ble_gatts_attr_md_tattr_md;
memset(&cccd_md, 0, sizeof(cccd_md));
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
//BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.write_perm);
//将上面的一行修改成下面这行
BLE_GAP_CONN_SEC_MODE_SET_ENC_WITH_MITM(&cccd_md.write_perm);
cccd_md.vloc =BLE_GATTS_VLOC_STACK;
memset(&char_md, 0, sizeof(char_md));
··············
··············
············
}
这样当对端设备(如手机)使能开发板的上rx特征值的notify功能时,就会因为没有写权限而触发配对,手机会发来配对请求,然后板子回复配对信息,怎么回复? 这就是第二步中最后留下的问题。如何将配对信息交给对端设备(手机)。
当手机发来配对请求时,这对板子来说是一个事件,即配对事件。最终由dispatch派发函数交给各个服务或模块的事件处理函数。那么我们要做的就是在收到这个配对请求事件后回复第二步中设置的配对
信息就可以了。在main.c 文件中的的on_ble_evt做如下修改
staticvoidon_ble_evt(ble_evt_t * p_ble_evt)
{
uint32_terr_code;
switch(p_ble_evt->header.evt_id)
{
caseBLE_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;
break;
caseBLE_GAP_EVT_DISCONNECTED:
err_code= bsp_indication_set(BSP_INDICATE_IDLE);
APP_ERROR_CHECK(err_code);
m_conn_handle= BLE_CONN_HANDLE_INVALID;
break;
case BLE_GAP_EVT_SEC_PARAMS_REQUEST:
//注释掉原本的不支持配对的函数,改为如下的配对回复函数
//err_code= sd_ble_gap_sec_params_reply(m_conn_handle,
//BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP, NULL, NULL);
err_code=sd_ble_gap_sec_params_reply(m_conn_handle,
BLE_GAP_SEC_STATUS_SUCCESS,&m_sec_params,NULL);
APP_ERROR_CHECK(err_code);
break;
case BLE_GATTS_EVT_SYS_ATTR_MISSING:
// No system attributes have beenstored.
err_code=sd_ble_gatts_sys_attr_set(m_conn_handle,NULL, 0, 0);
APP_ERROR_CHECK(err_code);
break;
default:
// No implementation needed.
break;
}
}
到这里所有需要配置的都设置完了。程序运行后。手机连接上板子,然后访问rx特征值。因为该特征值是用来将板子数据通过Notify方式传给手机的,那么首先要点击手机上的notify按钮去使能板子的notify功能。当我们点击该按钮时就会弹出输入密码的配对框。