精华内容
下载资源
问答
  •  蓝牙配对过程,其实就是一个认证的过程。  为什么不配对便无法建立连接?  任何无线通信技术都存在被监听和破解的可能,蓝牙SIG为了保证蓝牙通信的安全性,采用认证的方式进行数据交互。同时为了保证使用的...
  •  蓝牙配对过程,其实就是一个的过程。  为什么不配对便无法建立连接?  任何无线通信技术都存在被监听和破解的可能,蓝牙SIG为了保证蓝牙通信的安全性,采用的方式进行数据交互。同时为了保证使用的方便性,以...
  • 蓝牙配对过程分析

    2020-08-23 14:40:48
    1.概念 配对是指“Master和Slave通过协商确立用于加(解)密的key的过程。指的是下图中的第7部分。 2.配对方法 Master和Slave有两种可选的配对方法:legacy pairing和Secure Connections。...配对过程如图所示,

    1.概念

    配对是指“Master和Slave通过协商确立用于加(解)密的key的过程。指的是下图中的第7部分。
    蓝牙配对过程分析

    2.配对方法

    Master和Slave有两种可选的配对方法:legacy pairing和Secure Connections。从命名上看,前者是过去的方法,后者是新方法。选择的依据是:当Master和Slave都支持Secure Connections(新方法)的时候,则使用Secure Connections。否则,使用legacy pairing。

    3.配对过程

    配对过程如图所示,主要由下面四部分完成:
    蓝牙配对过程分析

    3.1.Pairing Feature Exchange

    用于交换双方有关鉴权的需求(authentication requirements),以及双方具有怎么的人机交互能力(IO capabilities)。其中最重要的是IO capabilities exchange。
    蓝牙配对过程分析
    IO的能力可以归纳为如下的六种:
    NoInputNoOutput
    DisplayOnly
    NoInputNoOutput1
    DisplayYesNo
    KeyboardOnly
    KeyboardDisplay
    上述的IO能力决定了后续的鉴权方式。

    3.2.Public key exchange

    两个设备之间交换Public key。 一旦设备收到对端设备的公钥,它就可以开始计算Diffie Hellman密钥(DHKey)。耗时较多,应该尽早开始,以便用户交互可以隐藏计算时间。 在步骤8之前不需要DHKey。
    当 Public key的长度大于DM1包的长度时,要使用专门的PDU来进行数据发送。
    蓝牙配对过程分析

    3.3.Authentication

    通过SMP协议进行实际的配对操作,根据阶段1 “Feature Exchange”的结果,有三种鉴权方法可选:

    3.3.1.OOB鉴权:

    如果双方都支持OOB鉴权,则选择该方式(优先级最高)。由配对的双方,在配对过程之外,额外的交互一些信息,并以这些信息为输入,进行后续的配对操作。这些额外信息也称作OOB(out of band),OOB的交互过程称为OOB protocol。
    蓝牙配对过程分析

    3.3.2.MITM鉴权:

    (man-in-the-middle)authentication,由两者方法:
    Numeric Comparision方式鉴权:两个设备自行协商生成6个数字,并显示出来(要求两个设备具有显示能力),用户比较后进行确认(一致,或者不一致,要求设备有简单的yes or no的确认能力)。
    蓝牙配对过程分析
    Passkey Entry,通过输入配对码的方式鉴权。
    蓝牙配对过程分析

    3.3.3.Just Work:

    Just Work,不需要用户参与,两个设备自行协商。

    3.4.DHKey Checks

    一旦设备完成鉴权过程,并且DHKey计算已完成,则检查生成的DHKey值。 如果成功,则两个设备都将完成向用户显示关于该过程的信息,否则控制器向主机发送消息以通知其停止显示该信息。
    蓝牙配对过程分析

    当配对过程完成后,link key就可以从DHKey中计算得到,并用做后续交互过程的输入(KEY + 明文 => 加密数据),通过HCI_Link_Key_Notification来通知host。
    蓝牙配对过程分析

    经过上述过程后,双方已经产生了加密key,因而可以建立加密的连接。加密连接建立后,可以互相传送一些私密的信息,例如Encryption Information、Identity Information、Identity Address Information等。

    展开全文
  • 蓝牙配对过程分析(经典蓝牙)

    万次阅读 2018-03-21 10:24:13
    安全简易配对SSP(Secure simple pairing),蓝牙2.0之后配对方式,简易安全配对一共有四种,其中Out of Band很少使用到,具体如下: Numeric Comparison 配对双方都显示一个6位的数字,由用户来核对数字...

    打开手机/手表的蓝牙,就能在列表中看到扫描到的蓝牙,点击就可以实现配对功能。目前手表支持与所有设备的配对,但是仅支持与耳机类型的设备进行连接

    安全简易配对SSP(Secure simple pairing),蓝牙2.0之后配对方式,简易安全配对一共有四种,其中Out of Band很少使用到,具体如下:
    Numeric Comparison
    配对双方都显示一个6位的数字,由用户来核对数字是否一致,并输入Yes/No,两端Yes表示一致即可配对,可以防止中间人攻击。
    使用场景:两端设备可以弹出6位十进制数,并且有yes和no按钮。

    Passkey Entry
    配对目标输入一个在本地设备上显示的6位数字,输入正确即可配对,并可以防止中间人攻击。
    使用场景:一端设备可以显示,另一端设备可以输入。

    Just Works
    不会进行鉴权,不能防止中间人攻击用于配对没有显示没有输入的设备,主动发起连接即可配对,用户看不到配对过程,不可以防止中间人攻击,例如连接蓝牙耳机。
    使用场景:用于即不能显示6位随机数,也不能输入的设备。

    Out of Band
    两设备的通过别的途径交换配对信息,例如一些NFC蓝牙音箱。

    一、原生应用设置界面的packages\Settings

    目录:android\packages\apps\Settings\src\com\android\settings\bluetooth\BluetoothDevicePreference.java

    void onClicked() {
        int bondState = mCachedDevice.getBondState();
    
        if (mCachedDevice.isConnected()) {
            askDisconnect();//断开连接
        } else if (bondState == BluetoothDevice.BOND_BONDED) {
            mCachedDevice.connect(true);//已经配对了就连接
        } else if (bondState == BluetoothDevice.BOND_NONE) {
            pair();//没有配对的话就去配对
        }
    }
    private void pair() {
        if (!mCachedDevice.startPairing()) {
            Utils.showError(getContext(), mCachedDevice.getName(),
                    R.string.bluetooth_pairing_error_message);
        } else {
            final Context context = getContext();
    
            SearchIndexableRaw data = new SearchIndexableRaw(context);
            data.className = BluetoothSettings.class.getName();
            data.title = mCachedDevice.getName();
            data.screenTitle = context.getResources().getString(R.string.bluetooth_settings);
            data.iconResId = R.drawable.ic_settings_bluetooth;
            data.enabled = true;
    
            Index.getInstance(context).updateFromSearchIndexableData(data);
        }
    }

    Y:\HLOS\frameworks\base\packages\SettingsLib\src\com\android\settingslib\bluetooth\CachedBluetoothDevice.java

    public boolean startPairing() {
            // Pairing is unreliable while scanning, so cancel discovery
            if (mLocalAdapter.isDiscovering()) {
                mLocalAdapter.cancelDiscovery();//如果设备正在搜索,就取消搜索
            }
    
            if (!mDevice.createBond()) {//配对
                return false;
            }
    
            mConnectAfterPairing = true;  // auto-connect after pairing
            return true;
        }

    二、framework层配对逻辑

    Y:\HLOS\frameworks\base\core\java\android\bluetooth\BluetoothDevice.java

    public boolean createBond() {
            if (sService == null) {
                Log.e(TAG, "BT not enabled. Cannot create bond to Remote Device");
                return false;
            }
            try {
                Log.i(TAG, "createBond() for device " + getAddress() +
                        " called by pid: " + Process.myPid() +
                        " tid: " + Process.myTid());
                return sService.createBond(this, TRANSPORT_AUTO);//跨进程调用到AdapterService中的方法
            } catch (RemoteException e) {Log.e(TAG, "", e);}
            return false;
        }

    三、frameworks的aidl文件

    private static IBluetooth sService;

    HLOS\frameworks\base\core\java\android\bluetooth\IBluetooth.aidl
    boolean createBond(in BluetoothDevice device);
    这个是属于跨进程通信的方法,通过IBluetooth.aidl文件,调用到AdapterService.java中的createBond方法

    四、从framework跨进程调到packages\Bluetooth

    Y:\HLOS\packages\apps\Bluetooth\src\com\android\bluetooth\btservice\AdapterService.java

    public boolean createBond(BluetoothDevice device, int transport) {
                if (!Utils.checkCallerAllowManagedProfiles(mService)) {
                    Log.w(TAG, "createBond() - Not allowed for non-active user");
                    return false;
                }
    
                AdapterService service = getService();
                if (service == null) return false;
                return service.createBond(device, transport, null);//调用本服务的createBond
            }

    同在AdapterService文件下

    boolean createBond(BluetoothDevice device, int transport, OobData oobData) {
            enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                "Need BLUETOOTH ADMIN permission");
            DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
            if (deviceProp != null && deviceProp.getBondState() != BluetoothDevice.BOND_NONE) {
                return false;
            }
            // Multicast: Do not allow bonding while multcast
            A2dpService a2dpService = A2dpService.getA2dpService();
            if (a2dpService != null &&
                a2dpService.isMulticastFeatureEnabled() &&
                a2dpService.isMulticastOngoing(null)) {
                Log.i(TAG,"A2dp Multicast is ongoing, ignore bonding");
                return false;
            }
    
            // Pairing is unreliable while scanning, so cancel discovery
            // Note, remove this when native stack improves
            cancelDiscoveryNative();//配对过程,取消扫描
    
            Message msg = mBondStateMachine.obtainMessage(BondStateMachine.CREATE_BOND);
            msg.obj = device;
            msg.arg1 = transport;
    
            if (oobData != null) {
                Bundle oobDataBundle = new Bundle();
                oobDataBundle.putParcelable(BondStateMachine.OOBDATA, oobData);
                msg.setData(oobDataBundle);
            }
            mBondStateMachine.sendMessage(msg);//给配对的状态机发消息,创建创建了BondStateMachine.CREATE_BOND
            return true;
        }

    在Y:\HLOS\packages\apps\Bluetooth\src\com\android\bluetooth\btservice\BondStateMachine.java
    处理服务发送过来的消息

    @Override
            public boolean processMessage(Message msg) {
    
                BluetoothDevice dev = (BluetoothDevice)msg.obj;
                DeviceProperties devProp = mRemoteDevices.getDeviceProperties(dev);
                boolean result = false;
                 if (mDevices.contains(dev) && msg.what != CANCEL_BOND &&
                       msg.what != BONDING_STATE_CHANGE && msg.what != SSP_REQUEST &&
                       msg.what != PIN_REQUEST) {
                     deferMessage(msg);
                     return true;
                 }
    
                Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
    
                switch (msg.what) {
                    case CREATE_BOND:
                        OobData oobData = null;
                        if (msg.getData() != null)
                            oobData = msg.getData().getParcelable(OOBDATA);
    
                        result = createBond(dev, msg.arg1, oobData, false);
                        break;
                        、、、
                        }
                }
    private boolean createBond(BluetoothDevice dev, int transport, OobData oobData,
                                   boolean transition) {
            if(mAdapterService == null) return false;
            if (dev.getBondState() == BluetoothDevice.BOND_NONE) {
                infoLog("Bond address is:" + dev);
                byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
                boolean result;
                if (oobData != null) {//判读是否借助其他硬件进行无绑定配对
                    result = mAdapterService.createBondOutOfBandNative(addr, transport, oobData);
                } else {
                    result = mAdapterService.createBondNative(addr, transport);//调用到JNI层,进行配对
                }
    
                if (!result) {
                    sendIntent(dev, BluetoothDevice.BOND_NONE,
                               BluetoothDevice.UNBOND_REASON_REMOVED);
                    return false;
                } else if (transition) {
                    transitionTo(mPendingCommandState);
                }
                return true;
            }
            return false;
        }
    

    五、packages\Bluetooth的java层调到jini层的cpp文件

    Y:\HLOS\packages\apps\Bluetooth\jni\com_android_bluetooth_btservice_AdapterService.cpp

    static jboolean createBondNative(JNIEnv* env, jobject obj, jbyteArray address, jint transport) {
        ALOGV("%s:",__FUNCTION__);
    
        jbyte *addr;
        jboolean result = JNI_FALSE;
    
        if (!sBluetoothInterface) return result;
    
        addr = env->GetByteArrayElements(address, NULL);
        if (addr == NULL) {
            jniThrowIOException(env, EINVAL);
            return result;
        }
    
        int ret = sBluetoothInterface->create_bond((bt_bdaddr_t *)addr, transport);//该接口调用到hal层的配对函数
        env->ReleaseByteArrayElements(address, addr, 0);
        result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
    
        return result;
    }
    

    六、hal层createBond的调用

    Y:\HLOS\system\bt\btif\src\bluetooth.c

    static int create_bond(const bt_bdaddr_t *bd_addr, int transport)
    {
        /* sanity check */
        if (interface_ready() == FALSE)
            return BT_STATUS_NOT_READY;
    
        return btif_dm_create_bond(bd_addr, transport);
    }

    Y:\HLOS\system\bt\btif\src\btif_dm.c

    bt_status_t btif_dm_create_bond(const bt_bdaddr_t *bd_addr, int transport)
    {
        btif_dm_create_bond_cb_t create_bond_cb;
        create_bond_cb.transport = transport;
        bdcpy(create_bond_cb.bdaddr.address, bd_addr->address);
    
        bdstr_t bdstr;
        BTIF_TRACE_EVENT("%s: bd_addr=%s, transport=%d", __FUNCTION__, bdaddr_to_string(bd_addr, bdstr, sizeof(bdstr)), transport);
        if (pairing_cb.state != BT_BOND_STATE_NONE)
            return BT_STATUS_BUSY;
    
        btif_stats_add_bond_event(bd_addr, BTIF_DM_FUNC_CREATE_BOND, pairing_cb.state);//添加了绑定事件
    
        btif_transfer_context(btif_dm_generic_evt, BTIF_DM_CB_CREATE_BOND,
                              (char *)&create_bond_cb, sizeof(btif_dm_create_bond_cb_t), NULL);//cotext是开辟了上下文的意思,这里create_bond_cb已经包含了要绑定的蓝牙地址,会分别发送给底层两部分,最后会调用btif_dm_generic_evt
    
        return BT_STATUS_SUCCESS;
    }

    Y:\HLOS\system\bt\btif\src\btif_dm.c

    static void btif_dm_generic_evt(UINT16 event, char* p_param)
    {
        BTIF_TRACE_EVENT("%s: event=%d", __FUNCTION__, event);
        switch(event)
        {
            case BTIF_DM_CB_DISCOVERY_STARTED:
            {
                HAL_CBACK(bt_hal_cbacks, discovery_state_changed_cb, BT_DISCOVERY_STARTED);
            }
            break;
    
            case BTIF_DM_CB_CREATE_BOND://走这一路去配对
            {
                pairing_cb.timeout_retries = NUM_TIMEOUT_RETRIES;
                btif_dm_create_bond_cb_t *create_bond_cb = (btif_dm_create_bond_cb_t*)p_param;
                btif_dm_cb_create_bond(&create_bond_cb->bdaddr, create_bond_cb->transport);
            }
            break;
    
            case BTIF_DM_CB_REMOVE_BOND:
            {
                btif_dm_cb_remove_bond((bt_bdaddr_t *)p_param);
            }
            break;
            、、、
        }
        、、、
    }
    static void btif_dm_cb_create_bond(bt_bdaddr_t *bd_addr, tBTA_TRANSPORT transport)
    {
        BOOLEAN is_hid = check_cod(bd_addr, COD_HID_POINTING);
        bond_state_changed(BT_STATUS_SUCCESS, bd_addr, BT_BOND_STATE_BONDING);
    
    #if BLE_INCLUDED == TRUE
        int device_type;
        int addr_type;
        bdstr_t bdstr;
        bdaddr_to_string(bd_addr, bdstr, sizeof(bdstr));
        if (transport == BT_TRANSPORT_LE)
        {
            if (!btif_config_get_int((char const *)&bdstr,"DevType", &device_type))
            {
                btif_config_set_int(bdstr, "DevType", BT_DEVICE_TYPE_BLE);
            }
            if (btif_storage_get_remote_addr_type(bd_addr, &addr_type) != BT_STATUS_SUCCESS)
            {
                btif_storage_set_remote_addr_type(bd_addr, BLE_ADDR_PUBLIC);
            }
        }
        if((btif_config_get_int((char const *)&bdstr,"DevType", &device_type) &&
           (btif_storage_get_remote_addr_type(bd_addr, &addr_type) == BT_STATUS_SUCCESS) &&
           (device_type & BT_DEVICE_TYPE_BLE) == BT_DEVICE_TYPE_BLE) || (transport == BT_TRANSPORT_LE))
        {
            BTA_DmAddBleDevice(bd_addr->address, addr_type, device_type);
        }
    #endif
    
    #if BLE_INCLUDED == TRUE
        if(is_hid && (device_type & BT_DEVICE_TYPE_BLE) == 0)
    #else
        if(is_hid)
    #endif
        {
            int status;
            status = btif_hh_connect(bd_addr);//连接?
            if(status != BT_STATUS_SUCCESS)
                bond_state_changed(status, bd_addr, BT_BOND_STATE_NONE);//绑定状态发生变化时,这里开始回调
        }
        else
        {
            BTA_DmBondByTransport((UINT8 *)bd_addr->address, transport);//第一次调用会走这里
        }
        /*  Track  originator of bond creation  */
        pairing_cb.is_local_initiated = TRUE;
    
    }
    
    

    Y:\HLOS\system\bt\bta\dm\bta_dm_api.c

    void BTA_DmBondByTransport(BD_ADDR bd_addr, tBTA_TRANSPORT transport)
    {
        tBTA_DM_API_BOND *p_msg =
            (tBTA_DM_API_BOND *)osi_malloc(sizeof(tBTA_DM_API_BOND));
    
        p_msg->hdr.event = BTA_DM_API_BOND_EVT;
        bdcpy(p_msg->bd_addr, bd_addr);
        p_msg->transport = transport;//消息包装
    
        bta_sys_sendmsg(p_msg);//发送到另一个线程
    }

    七、蓝牙协议栈给线程间消息的收发

    bta_sys_sendmsg这是蓝牙协议栈的进程间收发消息的机制,不是三言两语能说清楚的,若想了解,可以参考android bluedroid协议栈里面的各个组件之间的消息处理机制
    根据BTA_DM_API_BOND_EVT这个类型,可以找到消息接收的地方
    Y:\HLOS\system\bt\bta\dm\bta_dm_main.c

    const tBTA_DM_ACTION bta_dm_action[] =
    {
    
        /* device manager local device API events */
        bta_dm_enable,            /* 0  BTA_DM_API_ENABLE_EVT */
        bta_dm_disable,           /* 1  BTA_DM_API_DISABLE_EVT */
        bta_dm_set_dev_name,      /* 2  BTA_DM_API_SET_NAME_EVT */
        bta_dm_set_visibility,    /* 3  BTA_DM_API_SET_VISIBILITY_EVT */
        bta_dm_acl_change,        /* 8  BTA_DM_ACL_CHANGE_EVT */
        bta_dm_add_device,        /* 9  BTA_DM_API_ADD_DEVICE_EVT */
        bta_dm_close_acl,        /* 10  BTA_DM_API_ADD_DEVICE_EVT */
    
        /* security API events */
        bta_dm_bond,              /* 11  BTA_DM_API_BOND_EVT */
        bta_dm_bond_cancel,       /* 12  BTA_DM_API_BOND_CANCEL_EVT */
        bta_dm_pin_reply,         /* 13 BTA_DM_API_PIN_REPLY_EVT */
    
        /* power manger events */
        bta_dm_pm_btm_status,     /* 16 BTA_DM_PM_BTM_STATUS_EVT */
        bta_dm_pm_timer,          /* 17 BTA_DM_PM_TIMER_EVT*/
    
        /* simple pairing events */
        bta_dm_confirm,           /* 18 BTA_DM_API_CONFIRM_EVT */
    
        bta_dm_set_encryption,    /* BTA_DM_API_SET_ENCRYPTION_EVT */
    
        /* out of band pairing events */
        bta_dm_loc_oob,           /* 20 BTA_DM_API_LOC_OOB_EVT */
        bta_dm_ci_io_req_act,     /* 21 BTA_DM_CI_IO_REQ_EVT */
        bta_dm_ci_rmt_oob_act,    /* 22 BTA_DM_CI_RMT_OOB_EVT */
    、、、
    }

    Y:\HLOS\system\bt\bta\dm\bta_dm_act.c

    void bta_dm_bond (tBTA_DM_MSG *p_data)
    {
        tBTM_STATUS status;
        tBTA_DM_SEC sec_event;
        char        *p_name;
    
        if (p_data->bond.transport == BTA_TRANSPORT_UNKNOWN)//这里看transport类型,走不通渠道
            status = BTM_SecBond ( p_data->bond.bd_addr, 0, NULL, 0 );
        else
            status = BTM_SecBondByTransport ( p_data->bond.bd_addr, p_data->bond.transport, 0, NULL, 0 );
    
    
        if (bta_dm_cb.p_sec_cback && (status != BTM_CMD_STARTED))
        {
    
            memset(&sec_event, 0, sizeof(tBTA_DM_SEC));
            bdcpy(sec_event.auth_cmpl.bd_addr, p_data->bond.bd_addr);
            p_name = BTM_SecReadDevName(p_data->bond.bd_addr);
            if (p_name != NULL)
            {
                memcpy(sec_event.auth_cmpl.bd_name, p_name, (BD_NAME_LEN-1));
                sec_event.auth_cmpl.bd_name[BD_NAME_LEN-1] = 0;
            }
    
    /*      taken care of by memset [above]
            sec_event.auth_cmpl.key_present = FALSE;
            sec_event.auth_cmpl.success = FALSE;
    */
            sec_event.auth_cmpl.fail_reason = HCI_ERR_ILLEGAL_COMMAND;
            if (status == BTM_SUCCESS)
            {
                sec_event.auth_cmpl.success = TRUE;
            }
            else
            {
                /* delete this device entry from Sec Dev DB */
                bta_dm_remove_sec_dev_entry(p_data->bond.bd_addr);
            }
            bta_dm_cb.p_sec_cback(BTA_DM_AUTH_CMPL_EVT, &sec_event);
        }
    
    }

    Y:\HLOS\system\bt\stack\btm\btm_sec.c

    tBTM_STATUS BTM_SecBondByTransport (BD_ADDR bd_addr, tBT_TRANSPORT transport,
                                        UINT8 pin_len, UINT8 *p_pin, UINT32 trusted_mask[])
    {
    #if SMP_INCLUDED == TRUE
        tBT_DEVICE_TYPE     dev_type;
        tBLE_ADDR_TYPE      addr_type;
    
        BTM_ReadDevInfo(bd_addr, &dev_type, &addr_type);
        /* LE device, do SMP pairing */
        if ((transport == BT_TRANSPORT_LE && (dev_type & BT_DEVICE_TYPE_BLE) == 0) ||
            (transport == BT_TRANSPORT_BR_EDR && (dev_type & BT_DEVICE_TYPE_BREDR) == 0))
        {
            return BTM_ILLEGAL_ACTION;
        }
    #endif
        return btm_sec_bond_by_transport(bd_addr, transport, pin_len, p_pin, trusted_mask);
    }

    Y:\HLOS\system\bt\stack\btm\btm_sec.c

    tBTM_STATUS btm_sec_bond_by_transport (BD_ADDR bd_addr, tBT_TRANSPORT transport,
                                           UINT8 pin_len, UINT8 *p_pin, UINT32 trusted_mask[])
    {
        tBTM_SEC_DEV_REC *p_dev_rec;
        tBTM_STATUS      status;
        UINT8            *p_features;
        UINT8            ii;
        tACL_CONN        *p= btm_bda_to_acl(bd_addr, transport);
        BTM_TRACE_API ("btm_sec_bond_by_transport BDA: %02x:%02x:%02x:%02x:%02x:%02x",
                        bd_addr[0], bd_addr[1], bd_addr[2], bd_addr[3], bd_addr[4], bd_addr[5]);
    
        BTM_TRACE_DEBUG("btm_sec_bond_by_transport: Transport used %d" , transport);
    
        /* Other security process is in progress */
        if (btm_cb.pairing_state != BTM_PAIR_STATE_IDLE)
        {
            BTM_TRACE_ERROR ("BTM_SecBond: already busy in state: %s", btm_pair_state_descr(btm_cb.pairing_state));
            return(BTM_WRONG_MODE);
        }
    
        if ((p_dev_rec = btm_find_or_alloc_dev (bd_addr)) == NULL)
        {
            return(BTM_NO_RESOURCES);
        }
        if (!controller_get_interface()->get_is_ready())
        {
            BTM_TRACE_ERROR ("%s controller module is not ready", __func__);
            return(BTM_NO_RESOURCES);
        }
        BTM_TRACE_DEBUG ("before update sec_flags=0x%x", p_dev_rec->sec_flags);
    
        /* Finished if connection is active and already paired */
        if ( ((p_dev_rec->hci_handle != BTM_SEC_INVALID_HANDLE) && transport == BT_TRANSPORT_BR_EDR
             &&  (p_dev_rec->sec_flags & BTM_SEC_AUTHENTICATED))
    #if (BLE_INCLUDED == TRUE)
            ||((p_dev_rec->ble_hci_handle != BTM_SEC_INVALID_HANDLE) && transport == BT_TRANSPORT_LE
             &&  (p_dev_rec->sec_flags & BTM_SEC_LE_AUTHENTICATED))
    #endif
    
             )
        {
            BTM_TRACE_WARNING("BTM_SecBond -> Already Paired");
            return(BTM_SUCCESS);
        }
    
        /* Tell controller to get rid of the link key if it has one stored */
        if ((BTM_DeleteStoredLinkKey (bd_addr, NULL)) != BTM_SUCCESS)
            return(BTM_NO_RESOURCES);
    
        /* Save the PIN code if we got a valid one */
        if (p_pin && (pin_len <= PIN_CODE_LEN) && (pin_len != 0))
        {
            btm_cb.pin_code_len = pin_len;
            p_dev_rec->pin_code_length = pin_len;
            memcpy (btm_cb.pin_code, p_pin, PIN_CODE_LEN);
        }
    
        memcpy (btm_cb.pairing_bda, bd_addr, BD_ADDR_LEN);
    
        btm_cb.pairing_flags = BTM_PAIR_FLAGS_WE_STARTED_DD;
    
        p_dev_rec->security_required = BTM_SEC_OUT_AUTHENTICATE;
        p_dev_rec->is_originator     = TRUE;
        if (trusted_mask)
            BTM_SEC_COPY_TRUSTED_DEVICE(trusted_mask, p_dev_rec->trusted_mask);
    
    #if BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE
        if (transport == BT_TRANSPORT_LE)
        {
            btm_ble_init_pseudo_addr (p_dev_rec, bd_addr);
            p_dev_rec->sec_flags &= ~ BTM_SEC_LE_MASK;
    
            if (SMP_Pair(bd_addr) == SMP_STARTED)
            {
                btm_cb.pairing_flags |= BTM_PAIR_FLAGS_LE_ACTIVE;
                p_dev_rec->sec_state = BTM_SEC_STATE_AUTHENTICATING;
                btm_sec_change_pairing_state (BTM_PAIR_STATE_WAIT_AUTH_COMPLETE);
                return BTM_CMD_STARTED;
            }
    
            btm_cb.pairing_flags = 0;
            return(BTM_NO_RESOURCES);
        }
    #endif
    
        p_dev_rec->sec_flags &= ~(BTM_SEC_LINK_KEY_KNOWN | BTM_SEC_AUTHENTICATED | BTM_SEC_ENCRYPTED
                                      | BTM_SEC_ROLE_SWITCHED  | BTM_SEC_LINK_KEY_AUTHED);
    
        BTM_TRACE_DEBUG ("after update sec_flags=0x%x", p_dev_rec->sec_flags);
        if (!controller_get_interface()->supports_simple_pairing())
        {
            /* The special case when we authenticate keyboard.  Set pin type to fixed */
            /* It would be probably better to do it from the application, but it is */
            /* complicated */
            if (((p_dev_rec->dev_class[1] & BTM_COD_MAJOR_CLASS_MASK) == BTM_COD_MAJOR_PERIPHERAL)
                && (p_dev_rec->dev_class[2] & BTM_COD_MINOR_KEYBOARD)
                && (btm_cb.cfg.pin_type != HCI_PIN_TYPE_FIXED))
            {
                btm_cb.pin_type_changed = TRUE;
                btsnd_hcic_write_pin_type (HCI_PIN_TYPE_FIXED);
            }
        }
    
        for (ii = 0; ii <= HCI_EXT_FEATURES_PAGE_MAX; ii++)
        {
            p_features = p_dev_rec->features[ii];
            BTM_TRACE_EVENT("  remote_features page[%1d] = %02x-%02x-%02x-%02x",
                             ii, p_features[0], p_features[1], p_features[2], p_features[3]);
            BTM_TRACE_EVENT("                              %02x-%02x-%02x-%02x",
                                 p_features[4], p_features[5], p_features[6], p_features[7]);
        }
    
        BTM_TRACE_EVENT ("BTM_SecBond: Remote sm4: 0x%x  HCI Handle: 0x%04x", p_dev_rec->sm4, p_dev_rec->hci_handle);
    
    #if BTM_SEC_FORCE_RNR_FOR_DBOND == TRUE
        p_dev_rec->sec_flags &= ~BTM_SEC_NAME_KNOWN;
    #endif
    
        /* If connection already exists... */
        if (p && p->hci_handle != BTM_SEC_INVALID_HANDLE)
        {
            if (!btm_sec_start_authentication (p_dev_rec))
                return(BTM_NO_RESOURCES);
    
            btm_sec_change_pairing_state (BTM_PAIR_STATE_WAIT_PIN_REQ);
    
            /* Mark lcb as bonding */
            l2cu_update_lcb_4_bonding (bd_addr, TRUE);
            return(BTM_CMD_STARTED);
        }
    
        BTM_TRACE_DEBUG ("sec mode: %d sm4:x%x", btm_cb.security_mode, p_dev_rec->sm4);
        if (!controller_get_interface()->supports_simple_pairing()
            || (p_dev_rec->sm4 == BTM_SM4_KNOWN))
        {
            if ( btm_sec_check_prefetch_pin (p_dev_rec) )
                return (BTM_CMD_STARTED);
        }
        if ((btm_cb.security_mode == BTM_SEC_MODE_SP ||
             btm_cb.security_mode == BTM_SEC_MODE_SP_DEBUG ||
             btm_cb.security_mode == BTM_SEC_MODE_SC) &&
             BTM_SEC_IS_SM4_UNKNOWN(p_dev_rec->sm4))
        {
            /* local is 2.1 and peer is unknown */
            if ((p_dev_rec->sm4 & BTM_SM4_CONN_PEND) == 0)
            {
                /* we are not accepting connection request from peer
                 * -> RNR (to learn if peer is 2.1)
                 * RNR when no ACL causes HCI_RMT_HOST_SUP_FEAT_NOTIFY_EVT */
                btm_sec_change_pairing_state (BTM_PAIR_STATE_GET_REM_NAME);
                BTM_ReadRemoteDeviceName(bd_addr, NULL, BT_TRANSPORT_BR_EDR);
            }
            else
            {
                /* We are accepting connection request from peer */
                btm_sec_change_pairing_state (BTM_PAIR_STATE_WAIT_PIN_REQ);
            }
            BTM_TRACE_DEBUG ("State:%s sm4: 0x%x sec_state:%d",
                btm_pair_state_descr (btm_cb.pairing_state), p_dev_rec->sm4, p_dev_rec->sec_state);
            return BTM_CMD_STARTED;
        }
    
        /* both local and peer are 2.1  */
        status = btm_sec_dd_create_conn(p_dev_rec);
    
        if (status != BTM_CMD_STARTED)
        {
            btm_sec_change_pairing_state (BTM_PAIR_STATE_IDLE);//改变配对的状态
        }
    
        return status;
    }

    这里代码很多,每一句都看懂不现实,现摘出重要的一段看

     if (!controller_get_interface()->supports_simple_pairing())//这里做一个判断,看是否支持简单配对方式
        {
            /* The special case when we authenticate keyboard.  Set pin type to fixed */
            /* It would be probably better to do it from the application, but it is */
            /* complicated */
            if (((p_dev_rec->dev_class[1] & BTM_COD_MAJOR_CLASS_MASK) == BTM_COD_MAJOR_PERIPHERAL)
                && (p_dev_rec->dev_class[2] & BTM_COD_MINOR_KEYBOARD)
                && (btm_cb.cfg.pin_type != HCI_PIN_TYPE_FIXED))
            {
                btm_cb.pin_type_changed = TRUE;
                btsnd_hcic_write_pin_type (HCI_PIN_TYPE_FIXED);//这里就可以看到在和hci层打交道了
            }
        }

    八、通过HCI向底层发送命令进行控制

    BOOLEAN btsnd_hcic_write_pin_type (UINT8 type)
    {
        BT_HDR *p = (BT_HDR *)osi_malloc(HCI_CMD_BUF_SIZE);
        UINT8 *pp = (UINT8 *)(p + 1);
    
        p->len    = HCIC_PREAMBLE_SIZE + HCIC_PARAM_SIZE_WRITE_PARAM1;
        p->offset = 0;
    
        UINT16_TO_STREAM (pp, HCI_WRITE_PIN_TYPE);
        UINT8_TO_STREAM  (pp, HCIC_PARAM_SIZE_WRITE_PARAM1);
    
        UINT8_TO_STREAM (pp, type);
    
        btu_hcif_send_cmd (LOCAL_BR_EDR_CONTROLLER_ID,  p);//这里是向hci层发命令,
        return (TRUE);
    }
    

    可以看出,这里是通过和hci层的通信,host告诉controlor蓝牙地址、数据、命令等,从而控制其底层硬件发起配对操作

    展开全文
  • 蓝牙配对和连接的建立过程

    千次阅读 2019-09-27 09:58:41
    平时我们用蓝牙耳机听音乐,和不同的设备共享文件,打电话等,都有一个配对--连接--传输数据的过程配对,其实就是一个认证的过程。 1. 为什么不配对便无法建立连接? 任何无线通信技术都存在被监听和破解的...

    蓝牙的建立过程是一个复杂的过程,即使有过相当一段工作和使用经验的人,如果不仔细去了解还是理解不全。

    平时我们用蓝牙耳机听音乐,和不同的设备共享文件,打电话等,都有一个配对--连接--传输数据的过程。

    配对,其实就是一个认证的过程。

     

    1. 为什么不配对便无法建立连接?

    任何无线通信技术都存在被监听和破解的可能,蓝牙SIG为了保证蓝牙通信的安全性,采用认证的方式进行数据交互。同时为了保证使用的方便性,以配对的形式完成两个蓝牙设备之间的首次通讯认证,经配对之后,随后的通讯连接就不必每次都要做确认。所以认证码的产生是从配对开始的,经过配对,设备之间以PIN码建立约定的link key用于产生初始认证码,以用于以后建立的连接。

    所以不配对,两个设备之间便无法建立认证关系,无法进行连接及其之后的操作,所以配对在一定程度上保证了蓝牙通信的安全,当然这个安全保证机制是比较容易被破解的,因为现在很多个人设备没有人机接口,所以PIN码都是固定的而且大都设置为通用的0000或者1234之类的,所以很容易被猜到并进而建立配对和连接。

     

    2. 蓝牙的连接过程

    现在的蓝牙芯片供应商提供的技术支持能力相当强大,有完整的硬件和软件解决方案。对于应用而言,提供了固件用于实现底层协议栈,提供了profile库及源代码规范了各种应用,开发人员只要专注于应用程序开发就可以了。对于蓝牙底层的一些东西往往不甚了了。以前我也是这样子的,最近在做一个自动搜索以实现自动连接的应用,发现还是需要了解一些底层的机制的。
    我们可以很容易的进行操作在一个手机和免提设备之间建立连接,那么这个连接是怎么建立起来的呢?
    首先,主设备(master,即发起连接的设备)会寻呼(page)从设备(slave,接收连接的设备),master会已跳频的方式去寻呼slave,slave会固定间隔地去扫描(scan)外部寻呼,即page scan,当scan 到外部page时便会响应response该page,这样两个设备之间便会建立link的连接,即ACL链路的连接。当ACL 链路连接建立后,主设备会发起channel的连接请求,即L2CAP的连接,建立L2CAP的连接之后,主设备采用SDP去查询从设备的免提服务,从中得到rfcomm的通道号,然后主设备会发起rfcomm的连接请求建立rfcomm的连接。然后就建立了应用的连接。
    即link establish->channel establish->rfcomm establish->connection

     

    3. 广播数据分析

    3.1. 发送广播数据包的叫广播发起者(advertisers),在广播通道接收广播数据包但没意向连接广播发起设备的叫扫描者( scanners), 需要连接到另一个设备的设备叫做 initiators,它监听可连接的广播数据包。如果advertiser正在使用一个可连接的广播事件, initiator在收到连接数据包的物理通道上发起一个连接请求,如果advertiser接受这个连接请求则这个广播事件结束,并且开始一个新的连接事件。一旦连接建立,initiator成为主设备,advertiser成为从设备。连接事件被用于在主从设备之间传输数据包。
    连接过程:
    广播者:广播包使用ADV_IND PDU标志;
    扫描者:发送扫描请求(SCAN_REQ PDU)请求关于广播者的信息一个SCAN_REQ PDU(包含了扫描者的设备地 址), 在同一信道上回复一个SCAN_RSP PDU;
    发起者: 发起者发送连接请求(CONNECT_REQ PDU)请求进入连接态,广播者确认即可以连接上。

            SCAN_REQ_PDU载荷如下图所示,由ScanA(扫描设备地址)和AdvA组成(广播设备地址),ScanA是扫描设备的公共或随机地址(由TxAdd确定),AdvA是广播设备的公共或随机地址(由RxAdd确定)。

                                                                                   图 -  扫描请求PDU载荷

      广播报文的报头中的TxAdd指示了扫描设备使用的是公共地址(Public Address)还是随机地址(Random Address)。

    TxAdd = 0:公共地址。
    TxAdd = 1:随机地址。
      RxAdd指示了广播设备使用的是公共地址(Public Address)还是随机地址(Random Address)。

    RxAdd = 0:公共地址。
    RxAdd = 1:随机地址。
    3.2. 扫描响应

      SCAN_RSQ_PDU载荷如下图所示,由AdvA(广播设备地址)和ScanRspData组成(扫描响应数据),AdvA是广播设备的公共或随机地址(由TxAdd确定)。

                                                                               图 - 扫描响应PDU载荷

      广播报文的报头中的TxAdd指示了广播设备使用的是公共地址(Public Address)还是随机地址(Random Address)。

    TxAdd = 0:公共地址。
    TxAdd = 1:随机地址。
      广播报文的长度域指示了载荷的字节数(AdvA和ScanRspData)。

    3.3. SCAN_REQ和SCAN_RSP解析

    3.3.1. 捕获SCAN_REQ

      按照《蓝牙4.0BLE抓包(一)》中的描述进行抓包,下面是我们捕获一个心率计的SCAN_REQ包。

                                                       图4:捕获的SCAN_REQ包

    3.3.2. 分析SCAN_REQ

      为了方便分析,我们先取出这个SCAN_REQ包实际传输的数据,如图3中所示。心率计完整的广播报文如下:

      D6 BE 89 8E 83 0C 7F 0F 72 DD DF 68 DA B5 E9 D2 CC F3 BD BF 27

      在分析数据之前,再次说明:广播包含扫描请求和扫描响应,所以扫描请求和扫描响应得包格式遵循广播包的格式。

         分析报文时,需要注意一下报文各个域的字节序。

    3.3.2.1. 接入地址

      D6 BE 89 8E:接入地址,对广播来说是固定值。注意一下这里的字节序,接入地址传输时是低字节在前的。

    3.3.2.2. PDU

        1). 83:广播报文报头。

    bit0~bit3是0011,说明广播类型是SCAN_REQ,即扫描请求。
    bit7(RxAdd)是1:说明广播设备使用的是随机地址。
    bit6(TxAdd)是0:说明扫描设备使用的是公共地址。
        2). 0C:长度,表示SCAN_REQ报文的长度是12个字节。

        3). 7F 0F 72 DD DF 68:扫描设备的公共地址(报头里的TxAdd指示了这个地址是公共地址)。这里使用的实验设备是[艾克姆科技]的EN-nRF51DK开发板和小米3手机,扫描设备是小米3手机,在图3中可以看到该公共地址对应的是Xiao_mico_72。

        4). DA B5 E9 D2 CC F3:广播设备的地址(报头里的RxAdd指示了这个地址是随机地址)。

    3.3.2.3. 校验 

       BD BF 27:24字节CRC校验。

    3.3.3. 捕获SCAN_RSP

      按照《蓝牙4.0BLE抓包(一)》中的描述进行抓包,捕获一个心率计的SCAN_REQ包。

                                                                         图 - 捕获的SCAN_RSP包

    3.3.4. 分析SCAN_RSP

      同样,在这里我们先取出SCAN_REQ包的数据,便于分析。

      D6 BE 89 8E 44 06 DA B5 E9 D2 CC F3 61 6A 0F

    3.3.4.1 接入地址

      D6 BE 89 8E:接入地址,对广播来说是固定值。注意一下这里的字节序,接入地址传输时是低字节在前的。

    3.3.4.2 PDU

        1). 44:广播报文报头。

    bit0~bit3是0100,说明广播类型是SCAN_RSP,即扫描响应。
    bit6(TxAdd)是1:说明广播设备使用的是随机地址。
        2). 06:长度,表示SCAN_ RSP报文的长度是6个字节。

        3). DA B5 E9 D2 CC F3:广播设备的地址(报头里的RxAdd指示了这个地址是随机地址)。

    3.3.4.3 校验 

       61 6A 0F:24字节CRC校验。

    3.4.连接请求CONNECT_REQ

    在低功耗蓝牙技术建立连接的过程中,设备都是成对出现的:master和slave设备。如果master希望与slave建立连接,master就需要发起连接请求(ConnectionRequest,CONNECT_REQ)因此master可以称之为连接发起者;同时,slave必须是可连接的并且具有解析连接请求CONNECT_REQ的能力,slave可以称之为广播者。图1是连接请求CONNECT_REQ的帧结构。


    图1 CONNECT_REQ帧结构

    其中,InitA是连接发起者的蓝牙设备地址,长度为6字节;AdvA是广播者的蓝牙设备地址,长度为6字节。除了InitA和AdvA之外,帧格式中最为重要的部分则是LLData,这一部分包含了在连接建立过程中所需要使用的有意义的参数。

    为了更好的理解连接请求CONNECT_REQ,我们可以在日常生活中找到类似的一个例子,帮助我们理解它的含义。读者们很多应该都有过工作经验,在开始新的工作之前,都需要和雇主签署一份劳动合同,而CONNECT_REQ就是一份由“雇主”master提供的“劳动合同”,只需经过“雇员”slave确认,这份“合同”就开始生效,低功耗蓝牙技术的连接也就建立了。接下来我们就对“合同”中的各项条款逐条进行分析(如图2所示)。


    图2 LLData示意图

    (1)接入地址(AA:Access Address)

    这份合同的第一条款就是为雇员分配一个公司内部的唯一识别码,类似于工号,雇员可以在公司内部使用这一工号;当雇员离开公司之后,唯一识别码自动失效;即使是这一雇员再次加入到这家公司,他/她的新工号也与旧的工号不同。类似的,在两个低功耗蓝牙技术设备建立连接之前,master设备负责生成接入地址,这一地址类似于一个4字节的随机数,当连接建立之后,master和slave都使用这一接入地址进行通信;当连接断开之后,接入地址自动失效。

    (2)CRCInit(CRC初始值)

    这份“合同”的第二条款是CRCInit,它就是雇员在公司内部的一个密钥,通过这个密钥,雇员可以访问公司内部的资源。对于低功耗蓝牙技术设备,master和slave使用CRCIinit来验证数据包的完整性。

    (3)WinSize和WinOffset

    合同的第三条款中规范了雇员首次来公司报到的时间以及今后每次工作的时长。WinSize和WinOffset在低功耗蓝牙技术连接中,也做了类似的定义。WinOffset定义了在CONNECT_REQ命令之后的一个通信窗口的偏移量,如图3所示。在slave设备收到CONNECT_REQ之后,slave设备需要占用一些时间、根据LLData参数进行一些相关的配置,因此,WinOffeset为slave设备进行此种操作提供了时间,transmitWindowOffset= WinOffset×1.25 ms。WinSize定义了设备每次开启收发机的窗口时间,无论是master还是slave,它们都遵循WinSize的定义,窗口时间transmitWindowSize=WinSize×1.25 ms。

    因此,在CONNECT_REQ之后,第一个由master发送到slave的数据帧,我们称之为“锚点”(如图3所示),因为之后的所有的连接事件都以这一时刻为基准,呈现周期性变化。从红色框图中我们可以看到,第一个数据帧的时刻不能早于(1.25ms+transmitWindowOffset),同时也不能晚于(1.25 ms + transmitWindowOffset + transmitWindowSize)。

    图3 发起连接时序图

    (4)Interval, Latency & Timeout

    一般情况下,人们的工作时间是朝九晚五,一周工作五天。但是在低功耗蓝牙技术的连接机制当中,我们采用了更加灵活的“弹性工作制”。对于低功耗蓝牙技术连接的弹性工作制,这里有三个参数需要了解,Interval,Lantency和Timeout。


    图4 连接事件时序图

    在连接建立之后,master和slave之间的数据交互我们可以称之为连接事件,连接事件的发生周期(connInterval)则是由Interval参数来进行设定,connInterval= Interval×1.25 ms, connInterval的取值范围则是在7.5 ms至4 s秒之间。因此,在确定了锚点之后,master和slave将按照connInterval确定的时间间隔进行数据的交互,如图4所示。

    但是,对于低功耗蓝牙技术,低功耗的特性是需要特别考虑的,而且在实际的应用当中,不需要在每次connInterval都产生连接事件,因此引入了参数Lantancy,可以有效的减少连接事件的产生,connSlaveLatency= Latency。connSlaveLatency 定义了slave设备可以忽略多少个连续的连接事件,其不需要在这些被忽略的连接事件中侦听来自master的数据包,这也意味着slave设备不需要在每个连接事件产生的时刻都唤醒并打开射频接收机进行侦听,所以可以有效减少slave设备的功耗。这也是低功耗蓝牙技术能够实现其低功耗特性的一个重要的原因。

    Timeout参数定义了连接超时的长度,connSupervisionTimeout= Timeout×10 ms,其取值范围在100 ms至32 s之间。不论是master还是slave,在其收到一个数据帧之后,如果等待了connSupervisionTimeout时长都没有下一个数据帧到来,则可以认为连接已经断开。在这里要强调的是,connSupervisionTimeout必须大于(1 + connSlaveLatency) × connInterval × 2,否则,slave设备即使是在Lantency状态,也会被误认为是连接超时,导致连接误断开。

    (5)ChM & Hop

    我们都知道,蓝牙使用的是跳频技术,当连接建立之后,master和slave设备就需要利用某种机制来在预先设定的信道图谱上、按照预先设定的跳频跨度进行跳频工作,信道图谱就来自ChM参数,每跳的跨度则来自于Hop参数。Hop是一个整数,取值范围在5至16之间。下面的公式提供了跳频的工作方式:


    ————————————————
    版权声明:本文为CSDN博主「偏执灬」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/sinat_23338865/article/details/52189538

    展开全文
  • DeviceListPreferenceFragment是蓝牙扫描到的设备列表,点击其中一个蓝牙设备,调用onPreferenceTreeClick方法开始蓝牙配对过程。 /packages/apps/Settings/src/...
  • BLE蓝牙的连接和配对过程

    万次阅读 2019-12-24 22:52:19
    图11:GAP层角色 二 配对 区别于传统蓝牙配对过程,BLE的配对过程发生在连接过程之后。 配对是一个三阶段的过程。前两个阶段是必须的,第三阶段是可选的。 第一阶段:配对特征交换 第二阶段:短期秘钥(STK)生成...

    一 连接

    同一款手机,为什么跟某些设备可以连接成功,而跟另外一些设备又连接不成功?同一个设备,为什么跟某些手机可以建立连接,而跟另外一些手机又无法建立连接?同一个手机,同一个设备,为什么他们两者有时候连起来很快,有时候连起来又很慢?Master是什么?slave又是什么?什么又是Connection event和slave latency?希望这篇文章能帮助你回答上述问题。

    1 BLE连接示例

    假设我们有一台手机A(以安卓手机为例),一个设备B(设备名称:Nordic_HRM),如下所示,我们可以通过安卓设置菜单里面的蓝牙界面,让两者连接起来。

    1. 打开安卓设置菜单
    2. 选择“蓝牙”条目
    3. 打开蓝牙
    4. 等待系统搜索结果,不出意外的话,设备“Nordic_HRM”会出现在结果列表中
    5. 点击“Nordic_HRM”,手机将与此设备建立连接
      在这里插入图片描述
      上述即为大家直观感受到的“连接”,那么手机要与设备Nordic_HRM建立连接,具体包含哪些流程?他们为什么可以连接成功?下面给大家一一道来。

    2 广播(advertising)

    在手机跟设备B建立连接之前,设备B需要先进行广播,即设备B(Advertiser)不断发送如下广播信号,t为广播间隔。每发送一次广播包,我们称其为一次广播事件(advertising event),因此t也称为广播事件间隔。虽然图中广播事件是用一根线来表示的,但实际上广播事件是有一个持续时间的,蓝牙芯片只有在广播事件期间才打开射频模块,这个时候功耗比较高,其余时间蓝牙芯片都处于idle状态,因此平均功耗非常低,以Nordic nRF52810为例,每1秒钟发一次广播,平均功耗不到11uA。
    在这里插入图片描述
    上面只是一个概略图,按照蓝牙spec,实际上每一个广播事件包含三个广播包,即分别在37/38/39三个通道上同时广播相同的信息,即真正的广播事件是下面这个样子的。
    在这里插入图片描述
    设备B不断发送广播信号给手机(Observer),如果手机不开启扫描窗口,手机是收不到设备B的广播的,如下图所示,不仅手机要开启射频接收窗口,而且只有手机的射频接收窗口跟广播发送的发射窗口匹配成功,手机才能收到设备B的广播信号。由于这种匹配成功是一个概率事件,因此手机扫到设备B也是一个概率事件,也就是说,手机有时会很快扫到设备B,比如只需要一个广播事件,手机有时又会很慢才能扫到设备B,比如需要10个广播事件甚至更多。
    在这里插入图片描述

    3 建立连接(connection)

    根据蓝牙spec规定,advertiser发送完一个广播包之后150us(T_IFS,该值称为帧间隔,是指在同一个信道上连续两帧之间的间隔详见: 蓝牙核心卷,Vol 6, Part B,4.1.1),advertiser必须开启一段时间的射频Rx窗口,以接收来自observer的数据包。Observer就可以在这段时间里给advertiser发送连接请求。如下图所示,手机在第三个广播事件的时候扫到了设备B,并发出了连接请求conn_req。
    在这里插入图片描述
    上图的交互流程比较粗略,为此我们引入下图,以详细描述连接建立过程。
    在这里插入图片描述
    图5:连接建立过程

    注:图中M代表手机,S代表设备B,M->S表示手机将数据包发给设备B,即手机开启Tx窗口,设备B开启Rx窗口;S->M正好相反,表示设备B将数据包发给手机,即设备B开启Tx窗口,手机开启Rx窗口。

    如图所示,手机在收到A1广播包ADV_IND后,以此为初始锚点(这个锚点不是连接的锚点),T_IFS后给Advertiser发送一个connection request命令,即A2数据包,告诉advertiser我将要过来连你,请做好准备。手机在发完连接请求之后会被强制延时1.25ms,紧接着是发送窗口偏移,和发送窗口。发送窗口偏移可以是0到连接间隔之间的任意值,但必须是1.25ms的整数倍。从发送窗口开始从设备必须打开RX窗口用来接收手机发过来的P1数据包。如果发送窗口结束还没有收到P1数据包,那么从设备终止监听,并会在下一个连接间隔后再次尝试。从P1开始使用数据通道。Advertiser根据connect_req命令信息做好接收准备,connect_req包含如下关键信息:

    • Transmit window offset,定义如图5所示
    • Transmit window size,定义如图5所示
    • connect_req数据包完整定义如下所示
      在这里插入图片描述
    • Initiator: 发起连接者的mac地址,BLE的MAC地址,随机地址的最高两位应该为11b
    • Advertiser: 广播者的地址mac地址
    • Access Address: 接入地址。LL层使用接入地址来区分当前发送的数据是广播包还是数据包,广播包接入地址固定为0x8E89BED6,数据包使用就是该值。
    • CRC initialization value: CRC初始值
    • transmitWindowSize: 发送窗口大小, 单位是1.25ms
    • transmitWindowOffset: 发送窗口偏移, 单位是1.25ms
    • connInterval: 连接间隔 单位是1.25ms
    • connSlaveLatency: 从设备延时,表示从设备可以跳过多少个连接事件。
    • connSupervisionTimeout: 监控超时。单位是10ms
    • Channel Map: 信道图,表示当前环境中哪一个信道可用,每一个bit表示一个信道1表示可用,0表示不可用。比如ff ff ff ff 1f(0x1fffffffff), 二进制为0001111111111111111111111111111111111111b
    • masterSCA: 00100b, 休眠时钟精度, 151 ppm to 250 ppm
    • hopIncrement: 110b, 跳频算法的hop值,6
      在这里插入图片描述

    connect_req其实是在告诉advertiser,手机将在Transmit Window期间发送第一个同步包(P1)给你,请在这段时间里把你的射频接收窗口打开。设备B收到P1后,T_IFS时间后将给手机回复数据包P2。一旦手机收到数据包P2,连接即可认为建立成功。后续手机将以P1为锚点(原点),Connection Interval为周期,周期性地给设备B发送Packet,Packet除了充当数据传送功能,它还有如下两个非常重要的功能:

    1. 同步手机和设备的时钟,也就是说,设备每收到手机发来的一个包,都会把自己的时序原点重新设置,以跟手机同步。
    2. 告诉设备你现在可以传数据给我了。连接成功后,BLE通信将变成主从模式,因此把连接发起者(手机)称为Master或者Central,把被连接者(之前的Advertiser)称为Slave或者Peripheral。BLE通信之所以为主从模式,是因为Slave不能“随性”给Master发信息,它只有等到Master给它发了一个packet后,然后才能把自己的数据回传给Master。

    对于主设备而言,连接请求一旦发出就认为连接已经建立。当从设备收到连接请求时,它也认为自己已经处在连接之中,连接已经创建,但不能证明完全确立。

    4 连接失败

    有如下几种典型的连接失败情况:

    1. 如图5所示,如果slave在transmit window期间没有收到master发过来的P1,那么连接将会失败。此时应该排查master那边的问题,看看master为什么没有在约定的时间把P1发出来。
    2. 如果master在transmit window期间把P1发出来了,也就是说master按照connect_req约定的时序把P1发出来了,但slave没有把P2回过去,那么连接也会失败。此时应该排查slave这边的问题,看一看slave为什么没有把P2回过去
    3. 如果master把P1发出来了,slave也把P2回过去了,此时主机还是报连接失败,这种情况有可能是master软件有问题,需要仔细排查master的软件。
    4. 还有一种比较常见的连接失败情况:空中射频干扰太大。此时应该找一个干净的环境,比如屏蔽室,排除干扰后再去测试连接是否正常。

    5 连接事件

    连接事件(Connection events)

    连接成功后,master和slave在每一个connection interval开始的时候,都必须交互一次,即master给slave发一个包,slave再给master发一个包,整个交互过程称为一个connection event。蓝牙芯片只有在connection event期间才把射频模块打开,此时功耗比较高,其余时间蓝牙芯片都是处于idle状态的,因此蓝牙芯片平均功耗就非常低,以Nordic nRF52810为例,每1秒钟Master和Slave通信1次,平均功耗约为6微安左右。Master不可能时时刻刻都有数据发给slave,所以master大部分时候都是发的空包(empty packet)给slave。同样slave也不是时时刻刻都有数据给master,因此slave回复给master的包大部分时候也是空包。另外在一个connection event期间,master也可以发多个包给slave,以提高吞吐率。综上所述,连接成功后的通信时序图应该如下所示:

    在这里插入图片描述
    图7: 连接成功后的通信时序图(每个connection event只发一个包)

    在这里插入图片描述
    图9: 连接成功后的通信时序图( connection event可能发多个包)

    在这里插入图片描述
    图10:connection event细节图

    6 从设备延时

    从设备延时(Slave latency)

    图10中出现了slave latency(slave latency = 2),那么什么叫slave latency?

    如前所述,在每一个connection interval开始的时候,Master和Slave必须交互一次,哪怕两者之间交互的是empty packet(空包),但如果slave定义了slave latency,比如slave latency = 9,此时slave可以每9个connection interval才回复一次master,也就是说slave可以在前面8个connection interval期间一直睡眠,直到第9个connection interval到来之后,才回复一个packet给master,这样将大大节省slave的功耗,提高电池续航时间。当然如果slave有数据需要上报给master,它也可以不等到第9个connection interval才上报,直接像正常情况进行传输即可,这样既节省了功耗,又提高了数据传输的实时性。

    7 GAP层角色总结

    对上面提到的手机和设备B,在BLE通信过程中,随着时间的推移,他们的状态在发生变化,两者的关系也在发生变化,为此蓝牙spec根据不同的时间段或者状态给手机和设备B取不同的名字,即GAP层定义了如下角色:

    • advertiser。 发出广播的设备
    • observer或者scanner。可以扫描广播的设备
    • initiator。能发起连接的设备
    • master或者central。连接成功后的主设备,即主动发起packet的设备
    • slave或者peripheral。连接成功后的从设备,即被动回传packet的设备

    图11通过时间把observer,initiator和central串起来了,其实这三个角色是相互独立的,也就是说一个设备可以只支持observer角色,而不支持initiator和central角色。同样,图11也把advertiser和peripheral串起来了,其实advertiser和peripheral也是相互独立的,即一个设备可以只作为advertiser角色,而不支持peripheral角色。
    在这里插入图片描述
    图11:GAP层角色

    二 配对

    区别于传统蓝牙的配对过程,BLE的配对过程发生在连接过程之后。

    配对是一个三阶段的过程。前两个阶段是必须的,第三阶段是可选的。

    • 第一阶段:配对特征交换
    • 第二阶段:短期秘钥(STK)生成
    • 第三阶段: 传输特定秘钥分配

    image

    image

    STK生成规则

    1. Just work: 没有加密 TK=0x00
    2. passkey entry: 密码输入如果 passkey 是 ‘019655’ TK的值就是0x00000000000000000000000000004CC7。
      将输入的值作为一个6位数的十进制,转换成16字节的十六进制。
    3. OOB: 带外的TK值是一个16字节的随机数,通过非BLE的方式传递给对端。

    2.1 第一阶段

    设备首先在配对特征交换阶段交换IO能力来决定在第二阶段使用下面哪种方法:

    • JustWorks:只工作
    • PasskeyEntry:输入密码
    • OutOfBand(OOB):带外

    LE Legacy Pairing - Just Works
    Just Works方式不能抵抗窃听者和中间人攻击,只有在配对过程时没有遭受攻击,后面加密的链路的数据传输才是可信的。安全级别很低。

    LE Legacy Pairing - Passkey Entry
    这种方式通过输入6位数字的方式来进行配对,生成STK。6位数是随机产生的在000000到999999之间的数值,这个数值相当于一个TK,比如远端显示这个数字,需要在本地端输入这个数字给本地设备与远端配对。如输入019655,那此时的临时Key–TK是:0x00000000000000000000000000004CC7。

    Out of Band 带外
    这种方式是通过BLE之外的,设备上的其他方式来获取这个OOB data,比如通过IR红外,或其余的方式,因此对于蓝牙窃听者/攻击者而言这个data的传输是不可见的了,因此会显得要安全些。

    2.1.1 配对请求的数据格式

    image

    1. IO capabilities表明输入,输出的能力

    输入是按键、键盘,输出是显示数字用的界面。

    • 0x00 DisplayOnly 只能是显示000000 ~ 999999的数字
    • 0x01 DisplayYesNo 显示Yes/No 的按钮
    • 0x02 KeyboardOnly 只能是输入000000 ~ 999999的数字
    • 0x03 NoinputNoOutput 没有输入也没有显示,只能用Just work工作方式
    • 0x04 KeyboardDisplay 能输入000000 ~ 999999的数字和输出

    2. OOB data flag

    • 0x00 OOB 数据没有发送
    • 0x01 OOB 数据通过远端设备发送(如IR)
    • 0x02-0xFF 保留

    3. 身份验证请求

    image

    • Bonding_Flags b1b0 Bonding Type

      • 00 No Bonding
      • 01 Bonding
      • 10 Reserved
      • 11 Reserved
    • MITM

    MITM域设置为1为请求MITM(中间人介入)保护,否则设置为0. 设备将标志设置为1为STK请求认证的安全属性。
    选择Key生成的方法
    如果auth Req中MITM没有,则说明不需要人参与中间,所以IO capabilities会被忽略,只用Just Works就OK了。
    如果有OOB data,auth Req将可直接忽略,会直接选择OOB的方式了。

    • SC

    SC字段是一个1位标志,设置为1以请求LE安全连接配对,否则应根据发起方和响应方支持的功能将其设置为0,可能的结果配对机制为:如果两个设备均支持 LE安全连接,使用LE安全连接; 否则,请使用LE旧式配对。

    • Keypress

    keypress字段是一个1位标志,仅在Passkey Entry协议中使用,而在其他协议中将被忽略。 当双方将该字段设置为1时,应使用SMP配对按键通知PDU生成并发送按键通知。

    4. MaximumEncryptionKeySize

    最大秘钥长度,7到16字节之间

    5. InitiatorKeyDistribution

    发起者的秘钥分配,该域表明秘钥初始化设备请求分配使用。

    配对请求命令中的“生成”字段由主机使用,以请求发起者向响应者分发或生成哪些密钥。

    6. ResponderKeyDistribution

    响应者的秘钥分配,该字段表明秘钥初始化设备请求响应设备来分配秘钥分配使用。

    2.1.2 配对请求实例

    image

    1. Code (1 octet)

    0x01 Pairing Request

    2. IO Capability (1 octet)

    0x03 NoInputNoOutput: 用just work 认证方式

    3. OOB data

    0x00 OOB(out of band)
    没有带外认证, 带外这种方式是通过BLE之外的,设备上的其他方式来获取这个OOB data,比如通过IR红外,或其余的方式,因此对于蓝牙窃听者/攻击者而言这个data的传输是不可见的了,因此会显得要安全些。

    4. AuthReq (1 octet)

    AuthReq字段是一个位字段,指示STK和LTK以及GAP绑定信息的请求安全属性
    0x01 :表示绑定

    5. MaxEncKeySize

    0x10 表示最大认证key大小是0x10个字节

    6. InitiatorKeyDistribution

    该域表明秘钥初始化设备请求分配秘钥分配使用。

    7. ResponderKeyDistribution

    001 该字段表明秘钥初始化设备请求响应设备来分配秘钥分配使用。

    2.1.3 从设备向主设备向发送配对回复报文

    image

    具体字段含义参考 配对请求报文。

    2.2 第二阶段

    2.2.1 配对确认

    第一阶段的配对特征交换成功之后,用来启动STK生成。该命令被两个对等设备使用,来向对等设备发送确认值。初始化设备通过向响应设备发送配对确认命令启动STK生成。

    报文格式
    image

    启动STK的生成,这一部分可简述为以下步骤的实现

    1. Initiator生成128-bit随机数Mrand,并使用这个Mrand结合一些其他的输入,使用密码工具箱中c1计算出一个128-bit的Mconfirm值:
    Mconfirm = c1(TK, Mrand,
    Pairing Request command, Pairing Response command,
    initiating device address type, initiating device address,
    responding device address type, responding device address)
    

    Responder也生成一个128-bit随机数Srand,并使用这个Srand结合一些其他的输入,使用密码工具箱中c1计算出一个128-bit的Sconfirm值:

    Sconfirm = c1(TK, Srand,
    Pairing Request command, Pairing Response command,
    initiating device address type, initiating device address,
    responding device address type, responding device address)
    
    1. Initiator将其计算的Mconfirm值通过Pairing Confirm包发送给Responder,而Responder也将其计算的Sconfirm值通过Pairing Confirm包发送给Initiator;
    2. Initiator收到Sconfirm后,再将Mrand值通过Pairing Random包发送给Responder;
    3. Responder收到Mrand值后计算它的Mconfirm值,再跟前面那个Initiator送过来的Mconfirm值进行比较,若不同说明配对失败了。若相同,则Responder也会将它的Srand值通过Pairing Random包发送给Initiator;
    4. 而Initiator也会计算收到的Srand值的Sconfirm值,并跟前面那个Responder送过来的Sconfirm值进行比较,若不同说明配对失败了,若相同,继续。

    报文实例

    主设备向从设备发送配对确认报文,从设备也向主设备发送配对确认报文。

    image

    image

    2.2.2 随机配对

    该命令用来由初始化和响应设备发送,用来计算在配对确认命令中的确认值的随机数。

    报文数据格式
    image

    报文实例

    主设备向从设备发送配对随机值报文,从设备也向主设备发送配对随机值报文。
    image

    image

    2.3 第三阶段

    2.3.1 传输特定的密钥分发

    所有的键和值都由主从设备分发。

    要分发的密钥由配对请求和配对响应的密钥分发参数决定,
    配对请求和配对响应来自第一阶段配对特征交换
    image

    BLE的SMP的一些Key相关定义

    1. Long Term Key (LTK):加密链路用,128-bit;

    2. Encrypted Diversifier (EDIV):在LE legacy pairing过程中,用于识别LTK分发,16-bit;

    3. Random Number (Rand):在LE legacy pairing过程中,用于识别LTK分发,64-bit。

    4. Identity Resolving Key (IRK):用于生成和解析random address用的,128-bit;

    5. AddrType (1 octet)
      如果BD_ADDR是公共设备地址,则AddrType应设置为0x00。
      如果BD_ADDR是静态随机设备地址,则AddrType应设置为0x01。
      BD_ADDR(6个八位字节)此字段设置为分发设备的公共设备地址或静态随机地址。

    6. Connection Signature Resolving Key (CSRK):用于对数据进行签名已经验证签名数据,128-bit;

    2.3.2 特定key分发原因

    密钥分发阶段的从设备将密钥发送给主设备,这样就可以对重新连接进行加密,并解析其随机地址。或者,主设备可以验证来自从设备的签名数据,主设备也可以向从设备提供密钥,这样,如果角色互换,可以对重连接进行加密,可以解析主设备的随机地址,或者从设备可以验证来自主设备的签名数据。

    三 绑定

    就是将配对阶段产生的一系列key 保持到flash中,以便后续使用。

    以上这个过程的报文交互如下图,
    image

    展开全文
  • 蓝牙4.0 BLE传统配对绑定过程

    千次阅读 2019-10-23 19:06:53
    蓝牙配对过程分为三个阶段,第1阶段用来交换配对特征交并得到临时密钥TK。第2阶段中会进行身份确认以及产生短期密钥STK。第3阶段是设备绑定的过程,这期间会传输加密链路中使用到的LTK、IRK 以及 CSRK 等密钥,此...
  • android 在接收到蓝牙配对请求时如何自动点亮屏幕配对过程中很实用,具体的实现思路及代码如下,感兴趣的朋友可以参考下哈
  • BLE蓝牙配对方式

    千次阅读 2020-05-29 14:24:52
    Capacity IOCapcaity是由设备InputCapacity和OutputCapacity组合而成,表示的是设备的输入输出的能力,InputCapacity和OutputCapacity具体如下: ...Core_V5.0中蓝牙2308页讲述了BLE蓝牙配对方式,其中低功
  • 蓝牙配对流程(一)

    2021-06-25 08:19:51
    一、扫描 被动扫描(主从之间没有扫描请求与扫描响应) 2.主动扫描(主从之间有扫描请求与扫描响应) 二、过滤 1、信息匹配(是否在白名单) 三、建立连接 1、建立连接 建立连接后的结果: ......
  • 1 UI 蓝牙配对开始于settings...DeviceListPreferenceFragment是蓝牙扫描到的设备列表,点击其中一个蓝牙设备,调用onPreferenceTreeClick方法开始蓝牙的配对过程。 @Override public boolean onPreferenceTreeClick(Pr
  • kangear注: ... 原文把图给搞丢了,...在BT2.1及之后版本,蓝牙协议有在传统的密码配对(PIN Code Pairing)之外,新增一种简单配对(Simple Pairing)的方式。这种新的配对方式操作更为简单、安全性也更强。目前市面..

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,433
精华内容 2,173
关键字:

蓝牙配对过程