本篇博客主要结合Google Fast Pair Service(GFPS)规范(https://developers.google.com/nearby/fast-pair/specifications/introduction)和Nordic的应用实例及代码资源(基于nRF Connect SDK 2.8.0)对Google 快速配对服务进行详细介绍。
一,Google Fast Pair服务简介
Google Fast Pair 是一项利用低功耗蓝牙(Bluetooth LE)技术,实现设备间快速安全配对及提供多种服务的协议。其主要功能包括:
- 设备处于配对模式时,显示半页通知,便于用户进行初始配对。此外,还可以轻松地向用户推广配套的Companion APP。
- 初次配对完成后,将设备与用户的 Google 账户关联。
- 当设备开机且靠近用户拥有的其他手机、平板或桌面设备时,显示后续配对通知,无需用户再次将设备置于配对模式即可配对。
- 为设备关联个性化名称。
- 为耳机等设备显示电池电量通知。
- 在 Android 11 及以上系统中显示设备详细信息。
- 帮助用户定位丢失的耳机或耳塞。
- 在网络状况不佳时提供离线配对功能。
- 支持音频切换,根据用户活动(如开始播放影音)和优先事件(如来电)无缝切换耳机连接。
- 支持Hearable控件,以便为重要的Hearable功能提供更好的访问控制。
规范SPEC原文如下:
GFPS is aimed at facilitating the pairing of Bluetooth and Bluetooth LE devices, such as speakers, headphones, car kits, mice and keyboards, with as little user interaction required as possible. By implementing the following spec, Google will continue to release additional features that build upon it. This includes:
- Displaying a half page notification when the device is in pairing mode to facilitate easy initial pairing. Additionally companion apps are easily marketed to users.
- Associating the device with the user's account after the initial pairing has completed.
- Displaying a subsequent pairing notification when the device is turned on and near another phone, tablet, or desktop that the user owns, so that the user does not need to know how to put the device back into pairing mode before pairing with their other devices.
- Associating a personalized name with the device.
- Battery notifications are displayed for the headphones.
- Shows device details in Android 11+.
- Ability for users to locate a lost headset or buds.
- Offline pairing is available for low-network situations.
- Support Audio switch to seamlessly transition headset connections between devices based on user activity (e.g. starting a movie) and prioritized events (e.g. an incoming call).
- Support Hearable Controls to provide better access controls for important Hearable features.
协议中定义了两个角色:Seeker 和 Provider。站在 Bluetooth LE的角度,Master 或 Central 通常是手机,作为 Seeker;Slave 或 Peripheral 通常是设备,作为 Provider。Provider广播,Seeker扫描。
Seeker(如手机)扫描到支持GFPS的设备后,会弹框(half page notification)。用户确认连接后,建立Bluetooth LE连接。连接过程中交互信息,并建立Google account和设备之间的关联(设备将获得并存储account key)。
建立关联的过程就是Google Fast Pair。这里的配对(Pair)和Bluetooth LE配对是不同的概念。Seeker和Provider建立关联的过程有两种:Initial paring和Subsequence pairing。建立关联的对象是Google Account和Provider设备。多个Seeker设备可以拥有同一个Google Account 。Provider和第一个Seeker建立关联的过程称为initial pairing;Provider和第二个拥有相同Google Account的Seeker建立关联的过程称为subsequent pairing。过程略有不同, subsequent pairing 不需要生成’ Anti-Spoofing AES Key’的过程,直接使用已有的account key代替‘Anti-Spoofing AES Key’。Provider和不同的Seeker(拥有同一个Google account )之间建立关联时,会获得和保存不同的account key。(Anti-spoofing AES Key是由Anti-spoofing Private Key 和Anti-spoofing Public Key通过Elliptic Curve Diffie–Hellman key Exchange算法改良后生成的)。
关于initial pairing和Subsequent pairing在SPEC中的原文和翻译如下:
Initial pairing is the sequence of events that occur when a user pairs a device to a Google Account signed-in on phone for the very first time. In this sequence, a phone detects the advertisement from the device and displays a notification prompting the user to connect to and save the device. (In this guideline, 'device' means the Bluetooth headset or speaker instead of a reference phone).
首次配对是指用户首次将设备与手机上已登录的谷歌账号进行配对时所发生的一系列事件。在这个过程中,手机会检测到设备发出的广播,并显示一条通知,提示用户连接并保存该设备。(在本指南中,“设备” 指的是蓝牙耳机或音箱,而非参考手机。)
Subsequent pairing is the sequence of events that occur when a user signs into their Google Account on a new phone and attempts to pair a device already saved to their Google Account. In this sequence, the new phone recognizes that the advertised Model ID is already saved to the user's Google Account and provides a notification to expedite pairing the device to this phone.
后续配对是指用户在新手机上登录其谷歌账号,并尝试配对已保存到其谷歌账号的设备时所发生的一系列事件。在此过程中,新手机会识别出广播里的Model ID ,它已保存到用户的 Google 帐号中,并提供一条通知,以加快将该设备与这部手机配对的速度。
测试Initial paring和Subsequence pairing时,必须要满足以下的条件:
- 所有手机都应连接到互联网,并在“设置”中打开蓝牙和位置。
- 所有手机都应登录到同一个 Google 帐户。
- 参考手机应是市场上活跃的手机,并且覆盖相当多的用户群体。
- 实现了fast pair版本和相关扩展的蓝牙设备且是被认证的。
原文如下:
- All phones should be connected to the Internet and have Bluetooth and Location turned on in Settings.
- All phones should be logged in to the same Google Account.
- Reference phones should be phones actively on the market and cover a reasonably large population of users.
- A Bluetooth device implementing the version of Fast Pair, and associated extensions, that are to be certified.
手机或其它Android 设备如何知道,附近蓝牙设备是否支持Google Fast Pair Service?是通过设备的Bluetooth LE广播内容,主要是广播数据里面的Model ID。所以开发GFPS蓝牙设备必须先在Google上注册以获得开发许可和Model ID相关数据。
注册及开发流程请访问如下网站:
https://developers.google.com/nearby/fast-pair/landing-page

Google Fast Pair Service 除了建立关联(快速配对)外,还提供一些其他服务,如电池电量通知、个性化命名、寻物等扩展功能。
nRF Connect SDK 中的 Fast Pair 模块支持以下扩展:
Battery Notification extension
Personalized Name extension
Find My Device Network (FMDN) extension
和fast pair相关的例子介绍,可以访问如下链接:
二,模型注册及应用配置
一个品类的设备需要注册一个Model ID,以下是nRF Connect SDK 中例子的注册信息:




这些信息对Provider设备而言,指向了功能需求和数据存储。
- 功能需求相关配置
- 需要使用Personalized Name功能(No Personalized Name:false),那么要设置CONFIG_BT_FAST_PAIR_PN=y,反之,设置CONFIG_BT_FAST_PAIR_PN=n
config BT_FAST_PAIR_PN
bool "Fast Pair Personalized Name extension [EXPERIMENTAL]"
default y if !BT_FAST_PAIR_FMDN
select BT_FAST_PAIR_GATT_SERVICE_ADDITIONAL_DATA
select BT_FAST_PAIR_STORAGE_PN
select EXPERIMENTAL
help
Enable Fast Pair Personalized Name extension.
- 需要使用寻物功能(Find My Device:true),那么需要设置CONFIG_BT_FAST_PAIR_FMDN=y,反之,设置CONFIG_BT_FAST_PAIR_FMDN=n
menuconfig BT_FAST_PAIR_FMDN
bool "Find My Device Network extension for Fast Pair"
select BT_FAST_PAIR_STORAGE_OWNER_ACCOUNT_KEY
select BT_FAST_PAIR_FMDN_CRYPTO
help
Enable Find My Device Network (FMDN) extension for Fast Pair.
- 另外,有一些设备是电池供电的,需要用到电量通知服务。使能这个服务后,蓝牙广播里会出现电池电量数据信息。设置CONFIG_BT_FAST_PAIR_BN=y开启电量通知服务。
config BT_FAST_PAIR_BN
bool "Fast Pair Battery Notification extension [EXPERIMENTAL]"
select BT_FAST_PAIR_BATTERY
select EXPERIMENTAL
help
Enable Fast Pair Battery Notification extension.
- 数据存储
Model ID和Anti-spoofing Private Key是需要存储在Provider设备上的。存储这部分的逻辑,我们Nordic的构建系统已经做好了。用户只需要把自己的Model ID和Anti-spoofing Private Key数据,写到工程的CMakeLists.txt文件里面,例子如下:
if(NOT DEFINED FP_MODEL_ID AND NOT DEFINED FP_ANTI_SPOOFING_KEY AND NOT SYSBUILD)
message(WARNING "
-------------------------------------------------------
--- WARNING: Using demo Fast Pair Model ID and Fast ---
--- Pair Anti Spoofing Key, it should not be used ---
--- for production. ---
-------------------------------------------------------
\n"
)
set(FP_MODEL_ID "0x4A436B")
set(FP_ANTI_SPOOFING_KEY "rie10A7ONqwd77VmkxGsblPUbMt384qjDgcEJ/ctT9Y=")
endif()
构建系统脚本文件包含一个分区文件pm.yml.bt_fast_pair和一个Python文件scripts/nrf_provision/fast_pair/fp_provision_cli.py,Python文件通过读取分区文件和数据信息生成fp_provisioning_data.hex文件,这个hex文件最终会合并到工程总的hex文件中。
bt_fast_pair文件内容如下:
bt_fast_pair:
placement:
before: end
align: {start: 0x04}
size: 0x48
0x48个字节包含了Magic值,Model ID,Anti-spoofing Private Key和由这些数据计算出来的hash值。
Hex文件的格式如下:

定义分区文件的好处是:bt_fast_pair分区通过构建系统的Partition Manager 工具可以生成PM_BT_FAST_PAIR_ID,这个ID可以被flash API函数直接使用。下面的代码是通过flash API和PM_BT_FAST_PAIR_ID读取model id的函数。
#define FP_PARTITION_ID PM_BT_FAST_PAIR_ID
int fp_reg_data_get_model_id(uint8_t *buf, size_t size)
{
__ASSERT_NO_MSG(is_enabled);
int err;
const struct flash_area *fa;
if (size < FP_REG_DATA_MODEL_ID_LEN) {
return -EINVAL;
}
err = flash_area_open(FP_PARTITION_ID, &fa);
if (!err) {
err = flash_area_read(fa, FP_MODEL_ID_OFF, buf, FP_REG_DATA_MODEL_ID_LEN);
flash_area_close(fa);
}
return err;
}
除了bt_fast_pair区域之外,还有一些数据存储到了settings_storage区域,如account key,Personalized Name。Personalized Name的存储代码如下:
int fp_storage_pn_save(const char *pn_to_save)
{
int err;
size_t str_len;
if (!is_enabled) {
return -EACCES;
}
str_len = strnlen(pn_to_save, FP_STORAGE_PN_BUF_LEN);
if (str_len == FP_STORAGE_PN_BUF_LEN) {
return -ENOMEM;
}
err = settings_save_one(SETTINGS_PN_FULL_NAME, pn_to_save, str_len + 1);
if (err) {
return err;
}
strcpy(personalized_name, pn_to_save);
return 0;
}
3.应用相关配置
关于应用相关的更多配置选项介绍,可以参考:https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/libraries/bluetooth/services/fast_pair.html#configuration
三,Google Fast Pair蓝牙广播
我们先回顾一下Bluetooth LE 广播数据的格式,Bluetooth LE广播是由一个或多个类型的数据元组成,如下:
Google Fast Pair有两种广播: Discoverable Advertising和not Discoverable Advertising。两种广播主要的区别就是广播内容不同。
两种广播都必须包含服务数据类型(Service Data-16-bit UUID)。
UUID 为Fast Pair Service UUID 0xFE2C。0xFE2C后面跟随的Service Data的内容不同。 Discoverable Advertising跟随Model ID, not Discoverable Advertising跟随的数据比较复杂,主要是Account key Data。
- Discoverable Advertising :出厂后,没有和任何设备建立关联的情况下,进入广播状态时,发出的广播就是Discoverable Advertising 。The interval between advertisements should be no larger than 100ms (10Hz). A fast rate allows the Seeker to quickly find the Provider, even when scanning in low-power mode( 广播间隔不应大于 100ms(10Hz)。较快的速率使Seeker可以快速的发现Provider。 )
- not Discoverable Advertising:设备断电,重新复位后,且有做过关联的情况下,进入广播状态时,发出的广播就是not Discoverable Advertising 。 The interval between advertisements should be at most 250ms (4Hz). (广播间隔不要大于250ms)
- 另外,根据应用的不同,也可以灵活的设计广播类型。比如input_device 例子中,Discoverable Advertising 模式下,如满足如下条件,会转换成not discoverable advertising with the show UI indication mode。 1. After 10 minutes of discoverable advertising;2. After a Bluetooth Central successfully pairs. 在tag例子里面,如果获得了account key就切换成not discoverable advertising。
- Discoverable Advertising数据
Discoverable Advertising数据在SPEC中的描述如下:

Fast Pair Model ID Data是必须的,还有一些Bluetooth LE广播数据类型是可选的。通过nRF connect APP抓取的locator_tag例子的Discoverable Advertising的数据如下:

- AD TYPE 0x16代表Service Data-16-bit UUID,VALUE是Fast Pair Service UUID 0xFE2C+ Model ID 0x4A436B
- AD TYPE 0x01代表flags,VALUE=0x06 BR/EDR not supported(0x04)| General Discoverable(0x02)
- AD TYPE 0x0A代表Tx Power level, VALUE=0xF2 (-14dBm)
- AD TYPE 0x09 代表 Complete Local Name,VALUE是NCS locator tag字符串的ASCII编码
- not Discoverable Advertising数据
not Discoverable Advertising数据在SPEC中的描述如下:

Fast Pair Account Data是必须的,Battery value是可选的。
Seeker在进行通信之前,可以先通过Account key filter快速判断出Provider是否拥有某个Account key。Account key filter的计算使用了布隆过滤器算法(这个算法平均而言,错误概率很低,通常远低于0.5%)和account key list数据。如果list为空,则account key data为0。当Seeker看到广播的过滤器类型为0(但显示用户界面指示)或是包含了其账户密钥之一时,它可以自动连接并启动Fast Pair Procedure。(由Seeker决定是否pairing)。
Battery数据是可选的,这个就是前面提到的Battery Notification extension服务。最多可以为三个不同的组件设置电池信息(如耳机:左耳塞、右耳塞和外壳)。
通过nRF connect APP抓取的input_device例子的not Discoverable Advertising的数据如下:
四,Google Fast Pair过程
Google Fast Pair的流程图如下:
Google Fast Pair是包含了Bluetooth LE pair(上图蓝色箭头部分),当然,Bluetooth LE pair部分是可选的,不是强制的。在程序中由CONFIG_BT_FAST_PAIR_REQ_PAIRING宏控制。
config BT_FAST_PAIR_REQ_PAIRING
bool "Require Bluetooth Pairing during Fast Pair Procedure"
default y
help
Require the Bluetooth Pairing during the Fast Pair Procedure.
With this option enabled, the Fast Pair modules rejects the Fast Pair
procedure that is performed without the Bluetooth pairing operation.
Google Fast Pair对Bluetooth LE pair的安全等级是有要求的,需要使用Numeric Comparison配对方式。Numeric Comparison配对,要求配对双方都显示一个6位的数字,由用户来核对数字是否一致,一致的话,需要用户确认。例如手机之间的配对,需要对比数据,按下确认按钮。这个要求Bluetooth LE双方都具有KeyboardDisplay或DisplayYesNo的能力。但是Google fast pair解决了这个限制,它使用Bluetooth LE来传输这个加密的6位数字。双方把自己计算的6位数字,用之前计算的密钥K(Anti-spoofing AES Key或 Account Key)加密后发给对方,进行比对和确认。
SPEC中描述了详细的关联(fast pair)过程,请参考如下链接:
https://developers.google.com/nearby/fast-pair/specifications/service/gatt#procedure
(文中提到了几个蓝牙特性(characteristic),它们可以理解成数据传输的逻辑通道。)
原文和解释如下:
Instead of immediately invoking any of the normal BR/EDR or Bluetooth LE bonding procedures, the Seeker first enables Notifications on the Key-based Pairing characteristic, and then writes the data in Table 1.1 to it.
Seeker和Provider建立蓝牙连接后,首先,使能Key-based Pairing特性的通知功能,然后通过Key-based Pairing逻辑通道和write方式,Seeker发送一个数据包给Provider。
When handling a write request from a Fast Pair Seeker, the Fast Pair Provider shall do the following:
Provider收到来至Seeker的write request数据包后,会做如下处理:
-
If the optional Public Key field is present:
判断数据包是否包含Public Key(初始配对的时候会包含Public Key,它由Seeker生成。Seeker会生成密钥对,包含Public Key和Private Key,Public Key发送给Provider,自己保留Private Key)
a. If the device is not in pairing mode, ignore the write and exit.
如果设备不在配对模式下,忽略这个write数据包,并退出。
b. Otherwise(否则):
i. Use the received Public Key (a 64-byte point on the secp256r1 elliptic curve), the pre-installed Anti-Spoofing Private Key - also secp256r1, and the Elliptic-Curve Diffie-Hellman algorithm to generate a 256-bit AES key.
使用接收到的公钥(secp256r1 椭圆曲线上64 字节点的坐标值)、预先存储的Anti-Spoofing Private Key(同样是 secp256r1)以及椭圆曲线迪菲 - 赫尔曼算法生成一个 256 位的高级加密标准(AES)密钥。(Provider拥有来至Seeker生成的Public Key和系统注册时生成的Anti-Spoofing Private Key;Seeker拥有自己生成的Private Key和访问Google服务器获得的Anti-Spoofing Public Key,那么通过Elliptic-Curve Diffie-Hellman算法,Provider和Seeker能计算出一个相同的数,这个数可以做为对称密钥AES Key来使用。)
ii. Use SHA-256 to hash the 256-bit AES key.
使用安全哈希算法 2(SHA - 256)对这个 256 位的 AES 密钥进行哈希运算。
iii. Take the first 128 bits of the result. This is the Anti-Spoofing AES Key, used in the next step.
取运算结果的前 128 位。这就是Anti-Spoofing AES Key,用于下一步操作。
-
Using AES-128, attempt to decrypt the value. Since the value is a single 16-byte AES block, no IV or multi-block cipher mode is necessary.
使用 AES - 128 尝试解密该值(write数据包里的Encrypted Request部分)。由于该值是单个 16 字节的 AES 块,所以不需要初始向量(IV)或多块密码模式。
a. Which key to use(用哪个Key呢):
i. If an Anti-Spoofing AES Key was generated in step 1, use that key.
如果在步骤 1 中生成了Anti-Spoofing AES Key,则使用该密钥。
ii. Otherwise, try each key in the persisted Account Key List.
否则,尝试持久化账户密钥列表中的每个密钥。
b. If a key successfully decrypts the value, break, and continue to the next step.
如果某个密钥成功解密了该值,则中断操作,并继续下一步骤。
c. The value is decrypted successfully if the output matches the format in Table 1.2.1 or Table 1.2.2 - (that is, if it contains either the Fast Pair Provider's current Bluetooth LE address, or the Fast Pair Provider's public address).
如果(解密)输出值与表 1.2.1 或表 1.2.2 中的格式相匹配(即,如果它包含Fast Pair Provider当前的Bluetooth LE地址,或者Fast Pair Provider的公共地址),则表示该值已成功解密。
NOTE: At the end of the packet there is a salt attached. When possible, these salts should be tracked, and if the Provider receives a request containing an already used salt, the request should be ignored to prevent replay attacks.
注意:在数据包末尾附有一个盐值(Random value (salt))。在可能的情况下,应对这些盐值进行跟踪,如果Provider接收到包含已使用过的盐值的请求,应忽略该请求,以防止重放攻击。
d. As an alternative to tracking salts, if the write includes the Provider's private address, another way to prevent replay attacks is to bring forward the time of the next resolvable private address rotation so that the rotation occurs before the next Key-based Pairing write will be accepted.
作为跟踪盐值的替代方法,如果write操作包含了Provider的可解析私有地址,另一种防止重放攻击的方法是提前下一次可解析私有地址轮换的时间。在下一次基于Key-based Pairing通道的write操作之前进行地址轮换(rotation)。
-
If no key could successfully decrypt the value, ignore the write and exit.
如果没有密钥能够成功解密数据,则忽略此次write操作并退出。
a. Keep a count of these failures. When the failure count hits 10, fail all new requests immediately. Reset the failure count after 5 minutes, after power on, or after a success.
对这些失败次数进行计数。当失败次数达到 10 次时,立即拒绝所有新请求。在开机后、成功操作后或 5 分钟后重置失败次数。
-
Otherwise, save the successful key as K. Mark this K as usable for decrypting Passkey and Personalized name writes received on this LE link, but not other writes nor any writes on any other link. Start a timer to discard K after 10 seconds if pairing has not been started. Also discard K if this LE link disconnects.
否则,将成功解密的密钥保存为K。将此 K 标记为可用于解密在此Bluetooth LE(LE)链路上接收到的配对密钥(Passkey)和个性化名称的write操作,但不适用于其他write操作,也不适用于任何其他链路上的write操作。如果尚未开始配对,则启动一个定时器,在 10 秒后丢弃 K。如果此 LE 链路断开连接,也要丢弃 K。(K可以解密来至Passkey特征和Additional Data特征通道通过write方式发送的配对密钥和个性化名称数据。)
-
Produce the 16-byte Raw Response shown in Table 1.3, by concatenating the type and the Provider's BR/EDR address, and then filling the remainder of the packet with a block of random bytes (that is, a salt).
将消息类型和Provider的 BR/EDR 地址联合起来生成表 1.3 中所示的 16 字节原始响应,然后用随机字节块(即盐值)填充数据包的剩余部分。(这个数据是Provider返回给Seeker的生数据。)
-
Encrypt the Raw Response with K to produce the 16-byte Encrypted Response shown in Table 1.4. Send this via a notify on the Key-based Pairing characteristic.
使用 K 对响应原始数据进行加密,生成表 1.4 中所示的 16 字节加密响应。通过Key-based Pairing 特性通道和notify的方式,Provider发送响应数据包给Seeker。
-
Read the request flag(读取请求标志):
a. If the Request's Flags byte has bit 2 set to 1, notify Additional Data characteristic with personalized name.
如果请求的标志字节中第 2 位被设置为 1,则需要Provider通过Additional Data特征通道和notify的方式将个性化名称数据发送给Seeker。
b. If the Request's Flags byte has bit 1 set to 1:
如果请求的标志字节中第 1 位被设置为 1:
i. This indicates that the Seeker is requesting the Provider to initiate bonding to the Seeker's BR/EDR address, which is present in bytes 8-13.
这表示Seeker请求Provider发起(Bluetooth LE)配对绑定,这个请求里还包含了Seeker的BR/EDR 地址,该地址位于字节 8 - 13 中。
ii. Send a pairing request to the Seeker's BR/EDR address. The pairing request must be as described below ("During pairing" step).
向Seeker的 BR/EDR 地址发送(Bluetooth LE)配对请求。配对请求必须按照如下所述(“配对期间” 步骤)进行。
iii. Reason this is needed: Having the Provider initiate works around an issue on some devices.
需要这样做的原因:让Provider发起(Bluetooth LE)配对可以解决某些(Seeker)设备上存在的问题。
c. If the Request's Flags byte has bit 1 set to 0:
如果请求的标志字节中第 1 位被设置为 0:
i. Wait up to 10 seconds for a pairing request. If none is received, exit.
最多等待 10 秒以接收(Bluetooth LE)配对请求。如果未收到,则退出。
ii. Note that this might be a BR/EDR request, from a different address (the Seeker's public address, instead of its resolvable private address). We will re-verify during pairing that the requesting device is in possession of K.
注意,这可能是来自不同地址(Seeker的公共地址,而非其可解析私有地址)的 BR/EDR 请求。我们将在配对期间再次验证请求设备是否持有 K。
-
During pairing(配对期间):
a. When a pairing request/response packet is received from the Seeker: If the Device Capabilities in the request are NoInput/NoOutput, end pairing, to avoid using the Just Works pairing method.
当Seeker接收到配对请求 / 响应数据包时:如果请求中的设备能力为NoInput/NoOutput,则结束配对,以避免使用 “Just Works” 配对方法。
b. For the pairing request/response packet sent by the Provider: Set the Device Capabilities field to Display/YesNo and set Authentication Requirements to MITM Protection Required. This triggers the Numeric Comparison pairing method (also known as Passkey Confirmation on Android). We rely on this to confirm that the requesting device is in fact the Fast Pair Seeker, and that there is no man-in-the-middle. See examples.
对于Provider发送的配对请求 / 响应数据包:将设备能力字段设置为Display/YesNo ,并将认证要求设置为需要中间人保护(MITM Protection Required)。这将触发数字比较(Numeric Comparison)配对方法(在安卓系统中也称为配对密钥确认)。我们通过此方式来确认请求设备实际上就是Fast Pair Seeker,并且不存在中间人攻击。详见示例。
c. Reason this is needed: The Out-of-Band pairing method would be a better fit, but the platform does not expose it on all desired versions of Android.
需要这样做的原因:带外配对方法会更合适,但该平台并未在所有期望的安卓版本上提供此方法。
-
When confirmation of the passkey is needed, wait up to 10 seconds for a write to the Passkey characteristic.
当需要确认配对密钥(passkey)时,最多等待 10 秒,以等待Passkey特性通道通过write操作发送的passkey。
a. Normally, with this pairing method, the user would confirm that the passkeys displayed on each device's screen are identical. Instead, only for this pairing, we transfer them over BLE, encrypted with the trusted pre-shared key.
通常,使用这种配对方法时,用户会确认在每个设备屏幕上显示的配对密钥(passkey)是相同的。但仅在此配对情况下,我们通过Bluetooth LE传输它们,并使用可信的pre-shared key(K)进行加密。
b. Note that this approach should not be taken for devices that have a screen or a keyboard as it makes a slight compromise on MITM protection. Fast Pair currently does not support those device types yet because of this.
注意,对于具有屏幕或键盘的设备不应采用这种方法,因为这会在中间人保护方面稍有妥协。由于这个原因,fast pair目前尚不支持这些设备类型。
c. If the 10 second timer expires without a passkey being written, then discard K.
如果 10 秒定时器到期时仍未写入配对密钥(Passkey),则丢弃 K。
-
When a value is written to the Passkey characteristic, this is the Encrypted Passkey Block. Decrypt it with K to yield a Raw Passkey Block, with format shown in Characteristic: Passkey > Table 2.2 - (type = Seeker's Passkey).
当有值被写入Passkey特性时,此值是加密的Passkey块。使用 K 对其进行解密,得到原始数据的Passkey块,其格式如 “特性:Passkey> 表 2.2” 中所示(类型 = Seeker's Passkey)。
-
If decryption fails, ignore the write and discard K.
如果解密失败,则忽略此次write并丢弃 K。
-
Otherwise, the Raw Passkey Block contains a 6-digit passkey PSeeker, which is the passkey that the Seeker expects.
否则,原始Passkey块包含一个 6 位数字的passkeyPSeeker,这就是Seeker期望的passkey。
-
Compare PSeeker with our own expected passkey, PProvider.
将 PSeeker 与我们自己期望的passkey*PProvider*进行比较。
a. If the values are equal, reply "yes" to the confirmation.
如果两个值相等,则对确认操作回复 “是”。
b. Otherwise, reply "no" to the confirmation, causing pairing to fail.
否则,对确认操作回复 “否”,导致配对失败。
-
Regardless of whether pairing failed, produce another Raw Passkey Block, with format shown in Characteristic: Passkey > Table 2.2, containing our own expected passkey, PProvider.
无论配对是否失败,都生成另一个原始Passkey块,其格式如 “特性:Passkey> 表 2.2” 中所示,包含我们自己期望的passkeyPProvider。
a. Ensure the block has the correct type (Provider's Passkey; see table). NOTE: Do not reuse the salt from the Passkey Block received from the Seeker. Generate a new random value.
确保该块具有正确的类型(Provider's Passkey;参见表格)。注意:不要复用从Seeker接收到的Passkey块中的盐值。生成一个新的随机值。
-
Encrypt the Raw Passkey Block with K, and send the resulting Encrypted Passkey Block via a notify on the Passkey characteristic.
使用 K 对原始Passkey块进行加密,并通Passkey特性通道和通知的方式,发送生成的加密的Passkey块给Seeker。
-
If the Seeker receives and decrypts the correct passkey P, the Seeker will also reply "yes" to the confirmation, and pairing will succeed.
如果Seeker接收到并正确解密了passkey P,Seeker也会对确认操作回复 “是”,配对将成功。
a. If the pairing succeeds, then mark K as usable for decrypting Account Key writes on this LE link, but not for any subsequent Passkey writes nor any writes on any other link. Start a timer to discard K after 10 seconds. Also discard K following any attempt to write an Account Key and, as per step 4, if the LE link disconnects.
如果配对成功,则将 K 标记为可用于解密在此 LE 链路上的账户密钥(Account Key)写入操作,但不适用于任何后续的配对密钥(Passkey)写入操作,也不适用于任何其他链路上的写入操作。启动一个定时器,在 10 秒后丢弃 K。同样,如果尝试写入账户密钥,或者如步骤 4 所述,如果 LE 链路断开连接,也要丢弃 K。
b. If the pairing fails, discard K.
如果配对失败,则丢弃 K。
17.Switch the device capabilities field back to default I/O capabilities and Authentication Requirements to default so that new pairings continue as expected.
将设备功能字段切换回默认的I/O能力,将认证要求设置为默认值,以便新的配对能够按预期继续进行。
Note that for Providers that don't require bonding, the Seeker does not send a pairing request to the Provider, that is step 8 - step 17 are skipped. Also, "K" is used in Account Key characteristic.
注意,对于不需要做(Bluetooth LE)配对的Provider,Seeker不会向Provider发送配对请求,即跳过步骤 8 - 17。此外,“K” 将被Account Key特性使用。
五,Google Fast Pair服务和特性
-
Google Fast Pair SPEC定义的服务和特性:


-
通用服务和特性:
-
服务和特征对应的定义代码和配置选项
BT_GATT_SERVICE_DEFINE(fast_pair_svc,
BT_GATT_PRIMARY_SERVICE(BT_FAST_PAIR_UUID_FPS),
#if CONFIG_BT_FAST_PAIR_GATT_SERVICE_MODEL_ID
BT_GATT_CHARACTERISTIC(BT_FAST_PAIR_UUID_MODEL_ID,
BT_GATT_CHRC_READ,
BT_GATT_PERM_READ,
read_model_id, NULL, NULL),
#endif /* CONFIG_BT_FAST_PAIR_GATT_SERVICE_MODEL_ID */
BT_GATT_CHARACTERISTIC(BT_FAST_PAIR_UUID_KEY_BASED_PAIRING,
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_WRITE,
NULL, write_key_based_pairing, NULL),
BT_GATT_CCC(NULL, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
BT_GATT_CHARACTERISTIC(BT_FAST_PAIR_UUID_PASSKEY,
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_WRITE,
NULL, write_passkey, NULL),
BT_GATT_CCC(NULL, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
BT_GATT_CHARACTERISTIC(BT_FAST_PAIR_UUID_ACCOUNT_KEY,
BT_GATT_CHRC_WRITE,
BT_GATT_PERM_WRITE,
NULL, write_account_key, NULL),
#if CONFIG_BT_FAST_PAIR_GATT_SERVICE_ADDITIONAL_DATA
BT_GATT_CHARACTERISTIC(BT_FAST_PAIR_UUID_ADDITIONAL_DATA,
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_WRITE,
NULL, write_additional_data, NULL),
BT_GATT_CCC(NULL, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
#endif /* CONFIG_BT_FAST_PAIR_GATT_SERVICE_ADDITIONAL_DATA */
#if defined(CONFIG_BT_FAST_PAIR_FMDN_BEACON_ACTIONS)
FP_FMDN_BEACON_ACTIONS_CHARACTERISTIC,
#endif /* CONFIG_BT_FAST_PAIR_FMDN_BEACON_ACTIONS */
);
Fast Pair特征有些是可选的,Device Information Service(CONFIG_BT_DIS)也是可选的,设备类型不同,需要的特征也不相同。以下是Mouse设备和tag设备的配置。
config BT_FAST_PAIR_USE_CASE_MOUSE
bool "Mouse use case [EXPERIMENTAL]"
select EXPERIMENTAL
select BT_FAST_PAIR_GATT_SERVICE_MODEL_ID
select BT_FAST_PAIR_PN
select BT_FAST_PAIR_REQ_PAIRING
select BT_FAST_PAIR_SUBSEQUENT_PAIRING
select BT_DIS
select BT_DIS_FW_REV
imply BT_FAST_PAIR_BOND_MANAGER
help
Select the mouse Fast Pair use case. Mouse is a Human Interface Device (HID) that lets
the user interact with electronic devices such as a PC. This Kconfig option configures
the Fast Pair features and extensions to provide the best UX for the mouse use case.
This option is experimental, as the mouse feature requirements are not yet defined
in the Google Fast Pair specification.
config BT_FAST_PAIR_USE_CASE_LOCATOR_TAG
bool "Locator tag use case"
select BT_FAST_PAIR_FMDN
select BT_FAST_PAIR_FMDN_DULT
select BT_FAST_PAIR_USE_CASE_UNSUPPORTED_BOND_MANAGER
select BT_FAST_PAIR_USE_CASE_UNSUPPORTED_GATT_SERVICE_MODEL_ID
select BT_FAST_PAIR_USE_CASE_UNSUPPORTED_SUBSEQUENT_PAIRING
select BT_FAST_PAIR_USE_CASE_UNSUPPORTED_PN
select BT_FAST_PAIR_USE_CASE_UNSUPPORTED_BN
help
Select the locator tag Fast Pair use case. Locator tag is a small electronic device that
can be attached to an object or a person, and is designed to help locate them in case
they are missing. This Kconfig option configures the Fast Pair features and extensions
to satisfy the locator tag device feature requirements from the Google Fast Pair
specification:
https://developers.google.com/nearby/fast-pair/specifications/devicefeaturerequirement/devicefeaturerequirement_locatortags.
-
Model ID特性详解
Model ID特性的权限只有read,这一特性使得Seeker能够在Provider设备处于可被发现广播模式之外(连接状态下)的必要时刻读取Model ID。CONFIG_BT_FAST_PAIR_GATT_SERVICE_MODEL_ID宏控制使能这个特性。Bluetooth LE read操作对应的处理函数如下:
#if CONFIG_BT_FAST_PAIR_GATT_SERVICE_MODEL_ID
static ssize_t read_model_id(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
void *buf,
uint16_t len,
uint16_t offset)
{
uint8_t model_id[FP_REG_DATA_MODEL_ID_LEN];
ssize_t res;
if (!bt_fast_pair_is_ready()) {
res = BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
LOG_INF("Model ID read: res=%d conn=%p, "
"Return error because Fast Pair is not enabled", res, (void *)conn);
return res;
}
if (!fp_reg_data_get_model_id(model_id, sizeof(model_id))) {
res = bt_gatt_attr_read(conn, attr, buf, len, offset, model_id, sizeof(model_id));
} else {
res = BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
}
LOG_DBG("Model ID read: res=%d conn=%p", res, (void *)conn);
return res;
}
#endif /* CONFIG_BT_FAST_PAIR_GATT_SERVICE_MODEL_ID */
-
Key-based Pairing特性详解
This characteristic controls the Key-based Pairing procedure. In this procedure, a certain level of trust is established by verifying that the Seeker and Provider are both in possession of a pre-shared key. The key is different in each case:
这一特性控制着基于Key-based Pairing特性通道的fast pair配对流程。在此流程中,通过验证Seeker和Provider都持有的预共享密钥(pre-shared key即前面介绍的‘K’)来建立一定程度的信任关系。每种情况下密钥有所不同:
- Case 1: The pre-shared key is based on the anti-spoofing public/private key pair, and the Seeker's own public/private key pair which will change for each pairing attempt.
情况 1:预共享密钥基于防欺骗公钥 / 私钥对,以及Seeker自身的公钥 / 私钥对(每次配对尝试时都会变化)。
- The Provider is in pairing mode.
Provider处于配对模式。
- The Seeker verifies that the Provider is in possession of the anti-spoofing private key.
Seeker验证Provider持有防欺骗私钥。
Note that when in pairing mode, the Provider may also of course pair in the usual way, for example, to pair with a device that does not support Fast Pair's Key-based Pairing.
注意,处于配对模式时,Provider当然也可以按常规方式进行配对,例如,与不支持快速配对(Fast Pair)的设备进行配对。
情况 2:预共享密钥是账户密钥之一
- The Provider is usually not in pairing mode. (But this is not a requirement—The Provider should support using an account key even when in pairing mode.)
Provider通常不处于配对模式。(但这并非一项强制要求 —— 即便处于配对模式,Provider也应支持使用账户密钥。)
- The Seeker and Provider each verify that the other is in possession of the account key.
Seeker和Provider各自验证对方持有的账户密钥。
Since both cases are extremely similar, except for which pre-shared key is used, they are combined in procedure.
由于除了所使用的预共享密钥不同外,这两种情况极为相似,所以在流程中将它们合并处理。(处理流程在上一章节已详细介绍)
此特性权限支持Bluetooth LE write和 notify操作。Seeker用write方式发送命令,Provider通过notify方式,返回命令响应。
Key-based Pairing Request命令主要用来发起fast pair流程,数据包格式如下:

Action Request命令主要用来发送一些命令给Provider,数据包格式如下:

关于Provider处理write操作的代码很长,请参看NCS源代码里的validate_key_based_pairing_write()函数,这里就不贴出来了。
Provider通过notify的方式,发送响应数据包给Seeker,数据包格式如下:


整个notify数据包的数据部分都是加密的数据。发送notify的函数如下:
static int fp_gatt_rsp_notify(struct bt_conn *conn, const struct bt_gatt_attr *attr,
struct net_buf_simple *rsp)
{
uint8_t rsp_enc[FP_CRYPTO_AES128_BLOCK_LEN];
uint8_t *salt;
size_t salt_len;
int err = 0;
if (net_buf_simple_max_len(rsp) != FP_CRYPTO_AES128_BLOCK_LEN) {
LOG_ERR("Unsupported response size %zu", net_buf_simple_max_len(rsp));
return -ENOTSUP;
}
salt_len = net_buf_simple_tailroom(rsp);
salt = net_buf_simple_add(rsp, salt_len);
/* Fill remaining part of the response with random salt. */
err = sys_csrand_get(salt, salt_len);
if (err) {
LOG_WRN("Failed to generate salt for GATT response: err=%d", err);
return err;
}
err = fp_keys_encrypt(conn, rsp_enc,
net_buf_simple_pull_mem(rsp, FP_CRYPTO_AES128_BLOCK_LEN));
if (err) {
LOG_WRN("GATT response encrypt failed: err=%d", err);
return err;
}
err = bt_gatt_notify(conn, attr, rsp_enc, sizeof(rsp_enc));
if (err) {
LOG_ERR("GATT response notify failed: err=%d", err);
}
return err;
}
-
Passkey特性详解
Passkey这个Bluetooth LE特征只有使能了Bluetooth LE pairing的情况下,才会用到。它提供了传输Numeric Comparison passkey的通道,简化了Numeric Comparison方式的Bluetooth LE pairing。此特性权限支持Bluetooth LE write和 notify操作。数据包格式如下:
Passkey比较部分的处理函数如下:
static int handle_passkey_req(struct bt_conn *conn, struct net_buf_simple *rsp,
struct msg_seekers_passkey *parsed_req)
{
int err;
uint32_t bt_auth_passkey;
err = fp_auth_cmp_passkey(conn, parsed_req->passkey, &bt_auth_passkey);
if (err) {
LOG_WRN("Passkey comparison failed: err=%d (Passkey)", err);
return err;
}
net_buf_simple_add_u8(rsp, FP_MSG_PROVIDERS_PASSKEY);
net_buf_simple_add_be24(rsp, bt_auth_passkey);
return 0;
}
-
Account Key特性详解
Fast Pairing之后,通过Account Key通道和write方式,Seeker把用‘K’加密了的Account key发送给Provider,Provider需要解密和检查数据的正确性(值是否以 0x04 开头)。然后,检查已保存的账户密钥列表中是否有空间存放新值,如果没有空间,从列表中删除最近最少使用的值并将新值添加到列表中。Provider一旦拥有Account Key,那么它只能和拥有相同Google user‘s account的,其它Seeker做subsequent pairing。数据格式和SPEC原文如下:
Upon getting a write request, the Fast Pair Provider shall do the following:
- Decrypt the account key using the shared secret generated from step 4 in the procedure.
- For Providers that require bonding (common):
- Before decrypting, verify that the shared secret was used to decrypt the passkey request from step 12. If this step has not passed using this secret, ignore this write and quit.
- At this point, the shared secret (K in the procedure) won't be used again for this pairing. Any requests which come in encrypted with this key without restarting the procedure should be rejected.
- Verify that the decrypted value starts with
0x04
. If it does not, ignore this write and quit.
- Check whether the persisted Account Key list has space for the new value.
- If not, delete the least recently used value from the list.
- Add the new value to the list.
Account Keys in the list are used during Key-based Pairing
Provider方处理Account Key特征write操作的函数如下:
static ssize_t write_account_key(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
const void *buf,
uint16_t len, uint16_t offset, uint8_t flags)
{
struct fp_account_key account_key;
int err = 0;
ssize_t res = len;
/* It is assumed that this function executes in the cooperative thread context. */
__ASSERT_NO_MSG(!k_is_preempt_thread());
__ASSERT_NO_MSG(!k_is_in_isr());
if (!bt_fast_pair_is_ready()) {
res = BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
LOG_INF("Account Key write: res=%d conn=%p, "
"Return error because Fast Pair is not enabled", res, (void *)conn);
return res;
}
if (offset != 0) {
LOG_WRN("Invalid offset: off=%" PRIu16 " (Account Key)", offset);
res = BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
goto finish;
}
if (len != FP_ACCOUNT_KEY_LEN) {
LOG_WRN("Invalid length: len=%" PRIu16 " (Account Key)", len);
res = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
goto finish;
}
err = fp_auth_finalize(conn);
if (err) {
LOG_WRN("Authentication finalization failed: err=%d (Account Key)", err);
res = BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
goto finish;
}
err = fp_keys_decrypt(conn, account_key.key, buf);
if (err) {
LOG_WRN("Decrypt failed: err=%d (Account Key)", err);
res = BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
goto finish;
}
err = fp_keys_store_account_key(conn, &account_key);
if (err) {
LOG_WRN("Account Key store failed: err=%d (Account Key)", err);
res = BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
}
finish:
if (res < 0) {
fp_keys_drop_key(conn);
} else if (fast_pair_info_cb && fast_pair_info_cb->account_key_written) {
fast_pair_info_cb->account_key_written(conn);
}
LOG_DBG("Account Key write: res=%d conn=%p", res, (void *)conn);
return res;
}
-
Additional Data特性详解
Seeker通过Additional Data通道和write方式,可将Additional Data发送给Provider;Seeker还可以通过Key-based Pairing发命令,要求Provider通过Additional Data通道和notify的方式,从Provider处获取数据。 SPEC原文如下:
Before writing or notifying to this characteristic, there must be a handshake through characteristic FE2C1234-8366-4814-8EB0-01DE32100BEA to have a shared secret. AES-CTR will be used to encrypt data flowing through this characteristic, the algorithm of which is defined below. This mode is more secure across data that extends beyond a single 16-byte block. HMAC-SHA256 will be used to ensure data integrity, which is also defined below.
在向此特性进行写入或发送通知之前,必须通过特性 FE2C1234-8366-4814-8EB0-01DE32100BEA(Key-based Pairing) 进行握手以生成共享密钥(‘K’)。将使用高级加密标准计数器模式(AES-CTR)对通过该特性传输的数据进行加密,其算法如下所述。这种模式在处理超出单个 16 字节块的数据时安全性更高。同时,将使用基于哈希的消息认证码 - 安全哈希算法 256(HMAC-SHA256)来确保数据的完整性,其相关内容也在下文有所定义。
数据格式如下:

When a notify is requested (e.g. request personalized name via Bit 2 in Table 1.2.1), the Fast Pair Provider shall do the following:
当收到通知请求时(例如,通过表 1.2.1 中的第 2 位请求个性化名称),快速配对提供者(Provider)应执行以下操作:
- Generate cryptographically random 8 bytes for Nonce.
生成 8 字节的加密随机数作为Nonce。
- Encrypt the data using AES-CTR, where each 16-byte block is generated using
使用高级加密标准计数器模式(AES-CTR)对数据进行加密,其中每个 16 字节的块按以下方式生成:
where
a. AES key is the shared secret from step 4 in the procedure.
AES 密钥是配对流程中第 4 步生成的共享密钥(‘K‘)。
b. clearBlock[i] is a 16-byte block starting from data[i * 16]. The last block can be less than 16 bytes.
clearBlock [i] 是从(数据)data [i * 16] 开始的 16 字节块,最后一个块的长度可以小于 16 字节。
- Perform concat(encryptedBlock[0], encryptedBlock[1],...) to create the Encrypted Data.
通过执行连接(加密块 encryptedBlock [0],加密块encryptedBlock [1],……)操作来创建加密数据。
- Generate HMAC-SHA256 by
按以下方式生成基于哈希算法的消息认证码(HMAC-SHA256)
where
a. K is generated by concat(shared_secret, 48-byte ZEROs), the shared_secret is from step 4 in the procedure.
K 由连接(共享密钥,48 字节的零值)生成,共享密钥(fast pair时,计算出来的‘K’)来自配对流程中的第 4 步。
b. opad is 64 bytes outer padding, consisting of repeated bytes valued 0x5C
.
opad 是 64 字节的外填充,由值为 0x5C 的重复字节组成。
c. ipad is 64 bytes inner padding, consisting of repeated bytes valued 0x36
.
ipad 是 64 字节的内填充,由值为 0x36 的重复字节组成。
- Take the first 8 bytes from the HMAC-SHA256 as the prefix of the Data packet.
取 HMAC-SHA256 的前 8 字节作为数据包的前缀。
相关代码如下:
int fp_crypto_additional_data_encode(uint8_t *out_packet, const uint8_t *data, size_t data_len,
const uint8_t *aes_key, const uint8_t *nonce)
{
int err;
uint8_t hmac_sha256_buf[FP_CRYPTO_SHA256_HASH_LEN];
memcpy(&out_packet[ADDITIONAL_DATA_NONCE_POS], nonce, FP_CRYPTO_ADDITIONAL_DATA_NONCE_LEN);
err = fp_crypto_aes128_ctr_encrypt(&out_packet[ADDITIONAL_DATA_ENC_DATA_POS], data,
data_len, aes_key, nonce);
if (err) {
return err;
}
err = fp_crypto_hmac_sha256(hmac_sha256_buf, &out_packet[ADDITIONAL_DATA_NONCE_POS],
FP_CRYPTO_ADDITIONAL_DATA_NONCE_LEN + data_len,
aes_key, FP_CRYPTO_AES128_KEY_LEN);
if (err) {
return err;
}
memcpy(&out_packet[ADDITIONAL_DATA_SHA_POS], hmac_sha256_buf, ADDITIONAL_DATA_SHA_LEN);
return 0;
}
Upon getting a write request, the Fast Pair Provider shall do the following:
当收到写请求时,快速配对提供者(Provider)应执行以下操作:
- Verify the integrity of the data by checking the first 8 bytes of HMAC-SHA256.
通过检查 HMAC-SHA256 的前 8 字节来验证数据的完整性。
- Decrypt the encrypted Data using AES-CTR, where each block is generated using
使用高级加密标准计数器模式(AES-CTR)对加密数据进行解密,其中每个块按以下方式生成:

where
a. encryptedBlock[i] is a 16-byte block start from encrypted_data[i * 16]. The last block can be less than 16 bytes.
加密块encryptedBlock [i] 是从加密数据 encrypted_data [i * 16] 开始的 16 字节块,最后一个块的长度可以小于 16 字节。
b. AES key is generated or identified from the handshake, e.g.
AES 密钥是通过握手生成或确定的,例如:
i. in naming flow 1, it is from ECDH, and it will not be used again for this pairing. Any requests which come in encrypted with this key without restarting the procedure should be rejected.
在naming flow1 中,它来自椭圆曲线迪菲 - 赫尔曼(ECDH)密钥交换,并且在此配对中不会再被使用。任何未经重新启动该流程而使用此密钥加密后发来的请求都应被拒绝。
ii. in naming flow 2, it is the account key.
在naming flow2 中,它是账户密钥(account key)。
- Perform concat(clearBlock[0], clearBlock[1],...) to create the raw data.
通过执行连接(明文块clearBlock [0],明文块clearBlock [1],……)操作来创建原始数据。
相关代码如下:
int fp_crypto_additional_data_decode(uint8_t *out_data, const uint8_t *in_packet, size_t packet_len,
const uint8_t *aes_key)
{
int err;
uint8_t hmac_sha256_buf[FP_CRYPTO_SHA256_HASH_LEN];
int decoded_data_len;
if (packet_len > INT_MAX) {
return -ENOMEM;
}
decoded_data_len = packet_len - FP_CRYPTO_ADDITIONAL_DATA_HEADER_LEN;
if (decoded_data_len < 0) {
return -EINVAL;
}
err = fp_crypto_hmac_sha256(hmac_sha256_buf, &in_packet[ADDITIONAL_DATA_NONCE_POS],
packet_len - ADDITIONAL_DATA_SHA_LEN,
aes_key, FP_CRYPTO_AES128_KEY_LEN);
if (err) {
return err;
}
/* Check packet integrity. */
if (memcmp(&in_packet[ADDITIONAL_DATA_SHA_POS], hmac_sha256_buf, ADDITIONAL_DATA_SHA_LEN)) {
return -EINVAL;
}
return fp_crypto_aes128_ctr_decrypt(out_data, &in_packet[ADDITIONAL_DATA_ENC_DATA_POS],
decoded_data_len, aes_key,
&in_packet[ADDITIONAL_DATA_NONCE_POS]);
}
目前,主要是传输Personalized Name数据的时候用到Additional Data特性。
SPEC中关于Personalized Name的描述如下:
The Seeker can write a personalized name to the Provider during the first pairing and when the user edits the name on the Seeker side. The Provider shall allocate space, at least 64 bytes, to store the personalized name.
在首次配对期间以及用户在Seeker端编辑名称时,查找者(Seeker)可向提供者(Provider)写入个性化名称。Seeker应分配至少 64 字节的空间来存储该个性化名称。
Writes to Additional Data characteristic with personalized name may happen in 2 flows:
向Additional Data特性进行写入个性化名称的操作可能会在以下两种流程中发生:
- After initial pairing, the Seeker may write a personalized name to the Provider after writing the account key.
在初次配对后,Seeker在写入账户密钥之后,可向Provider写入个性化名称。
- After a user modifies the personalized name on the Seeker, the Seeker will write the new name to the Provider:
用户在Seeker端修改了个性化名称后,Seeker会将新名称写入Provider:
a. handshake from step 1 to step 4 in the above procedure to identify each other and know which account key will be used for the encryption and decryption of the name. In Table 1.2.2:
按照上述流程中的第 1 步到第 4 步进行握手,以便双方相互识别,并知晓将使用哪个账户密钥对名称进行加密和解密。在表 1.2.2 中:
i. Set Message type to 0x10
将消息类型设置为 0x10。
ii. Set Flag Bit 1 to 1 to indicate it will be followed by Data characteristic.
将标志位 1 设置为 1,以表明后面会跟着数据特性。
iii. Set Data ID of Table 1.2.2 to 0x01 as personalized name.
将表 1.2.2 的数据标识设置为 0x01,表示其为个性化名称。
b. write the name to Data characteristic FE2C1237-8366-4814-8EB0-01DE32100BEA
.
将名称写入数据特性 FE2C1237-8366-4814-8EB0-01DE32100BEA。
The Provider should notify to Additional Data characteristic with personalized name when it is requested via Bit 2 in Table 1.2.1.
当通过表 1.2.1 中的第 2 位请求个性化名称时,Provider应通过Additional Data特性和通知的方式发送个性化名称给Seeker。
另外,来自Seeker的Device action命令,也可能需要Additional Data特性传递数据。由于Nordic目前的应用不涉及处理Device action命令,所以这一部分不做介绍。如果感兴趣可以参看SPEC原文:https://developers.google.com/nearby/fast-pair/specifications/extensions/deviceaction
-
Beacon actions特性介绍
Tag类设备,需要用到寻物功能时,必须加入这个特性。后续有博客专门介绍寻物应用,将详细介绍Beacon actions特性。
六,开发总结
Google Fast Pair协议的逻辑模块,在NCS里面已经全部实现了,而且都是开源的,模块化的。用户只需要专注应用逻辑部分的实现;Google Fast Pair部分,只需要按照自己产品的需求,加入必要的部分。本文主要介绍了Google Fast Pair协议的功能,如果用户对自己的产品中需要加入的Google Fast Pair功能不明确,可以和我们的销售团队联系,我们Nordic能提供相关支持。
另外,Fast Pair设备是需要认证的,认证指南可以参考:https://developers.google.cn/nearby/fast-pair/fast-pair-certification-guideline