上一章我们介绍了Legacy配对——这位"闪电侠"只需三步就能搞定(特性交换、STK生成、密钥分发),简单得就像泡方便面。而本章要介绍的Secure配对,就像突然变成了树懒,非要多做一个ECDH密钥交换动作(虽然是为了生成更安全的LTK)。不过别担心,这个"慢动作"主要发生在JustWork模式——如果是需要用户输密码的PasskeyEntry或者NumericComparison模式,那人类的手指头才是真正的速度瓶颈!
3.13.4.1 代码修改:比给泡面加个蛋还简单
要开启安全配对?代码修改简单到令人发指,就加一行代码而已(抓hcilog会告诉你:这次我们玩真的了)。
抓hcilog可以看到,这次的配对过程是安全配对:
不过本章的重点可不是找代码位置,而是用福尔摩斯般的洞察力看看:这整个配对过程到底在磨蹭什么?
单纯抓空中包就像看哑剧,只能看到设备在"说"什么,却不知道它们"想"什么。通过空中抓包,我们无法知道什么时间是设备在做本地计算,所以必须结合HCILOG来看,而hcilog一般是独立的,无法和空中包放到一起来看。为此,我们使用自己开发的小工具,将btstack向Jlink RTT打印的Hcilog抓取,并同步发送到Ellisys分析仪的Hci Injection的UDP端口,这样,让HCILOG和空中包像情侣吵架一样实时对线,所有小心思都无所遁形。
3.14.4.2 Phase1时间谜案:250ms的"拖延症患者"
在检查抓包的时候,我们一开始就发现了一些问题:
图3-89 安全配对Phase1时间过长
安全配对的第一阶段主要是交换双方的feature,就是两个数据包的事,结果从图中可以看到,总共花费了449ms,而因为有了hcilog,我们就方便分析了——绿色的那条线表示hci收到数据报文,蓝色那条线表示hci发送数据报文,可见这个延误是我们本地造成的。
仔细看了代码,我们发现,当我们收到pairing request这样一条报文的时候,代码里会进入这样一个流程:
图3-90 sm层收到报文处理
可以看到这里开启timer,在timer中去回复数据包,而因为我们现在的timer是以system tick为时间单位,而之前我们设定的system tick是250ms,这个颗粒度就太粗了,所以对代码做如下修改就可以了:
图3-91 systemtick改为1ms一次
重新抓包后,我们看到第一阶段花费时间大概是75ms,这个时间正常了。——看来蓝牙设备也受不了拖延症啊!
图3-92 安全配对第一阶段时间
3.14.4.3 PublicKey交换:预谋已久的"快枪手"
接下来进入安全配对的第二阶段,安全配对的第二阶段的目的是为了生成LTK,和Legacy配对的STK对应。
首先,是双方交换public key。
图3-93 Public Key交换
这一步是安全配对的重头戏,因为非对称加密的思想就是公钥公开,私钥不公开。公钥交换(Public Key Exchange)是实现安全密钥协商的核心步骤,其核心机制基于椭圆曲线密码学(ECDH)。
Public Key是每个设备预先生成的ECC密钥对(私钥d和公钥Q=d×G),公钥坐标(Q_x, Q_y)遵循NIST P-256曲线标准。
生成秘钥对(公钥和私钥)的时间是比较长的,在蓝牙芯片这样的arm架构上进行纯软件计算的话,通常需要几百毫秒。
说句题外话,BLE的secure connection和经典蓝牙芯片的SSP配对都使用ECDH椭圆曲线来进行非对称加密,但是,因为经典蓝牙通常是高带宽低延迟场景(比如播放音乐),连接配对的快慢决定了用户体验,所以很多经典蓝牙芯片是内置ECC加速的,可以把配对时间缩小到50ms以内。
我们来看一看抓包中的交换public key过程。
图3-94 交换public key
这个过程从抓包来看是很快的,原因是这个public key生成的过程虽然慢,但是实际上,在蓝牙初始化的过程中,设备早就计算好存起来了:
图3-95 初始化过程中的公钥生成
从hcilog可以看到,在初始化的时候,host向controller下发了一个Read Local P-256 Public Key的命令,大概700多ms之后,controller回复了一个public key给host,host就会把这个key给存起来备用,后面配对的时候再拿出来用。Controller在计算这个publickey的时候,所采用的数组 BasePoint_x_256 和 BasePoint_y_256 是椭圆曲线密码学(ECC)中 NIST P-256(secp256r1)曲线的基点(Generator Point) 的坐标值。这是国际标准曲线,广泛用于TLS、蓝牙安全连接(LE Secure Connections)等场景。
图3-96 controller计算公钥的函数
公钥交换后,按照协议会进入认证的第一步
图3-97 认证第一步
这里主要是用的一种叫做f4的算法,其输入参数包括上文交换过的双方的public key,以及一个双方各自提供的一个随机数Na、Nb。
图3-98 认证的f4算法
看一下抓包,我们会发现在这个stage1交互之前,有很长时间的等待,空中包都是空包。
图3-99 authentication stage1之前的dhkey计算
这个时间是在做什么呢,进一步查看底下的Hcilog,我们就知道,这个时候是Host在做DHKey计算。
DHKey(Diffie-Hellman Key)的作用是基于ECDH(椭圆曲线Diffie-Hellman)算法生成的临时共享密钥,用于在LE Secure Connections配对过程中派生长期密钥(LTK)
Phase2真正的"时间杀手"来了:DHKey计算就像让树懒做高数题,要用私钥×对方公钥,还得做模运算。所以这个算法的耗时甚至比生成public key的ecc计算还要长。
图3-100 host在收到publickey之后就会做dhkey计算
从上图中的代码片段也可以看到,host在收到public key之后,是会调用btstack_crypto_ecc_p256_calculate_dhkey去做dhkey计算的,只不过这个计算最终是通过hci下发命令交给controller去做。Controller如果不支持硬件加速的话,实际上也是软件在计算dhkey这个东西,耗时比较长,本质上双方基于交换的公钥和本地私钥,通过椭圆曲线运算(如K = a * B)生成共享密钥,这个从数学上可以保证双方设备会生成相同的dhkey。非对称加密的精华就在于此,中间人即使偷听到所有数据包,也无法解密dhkey。