BLE 协议规范
为您自己的控制器——您的板子、您的单片机——加上 Entrixy BLE 支持所需的一切。我们现成的固件做的正是这件事;下面给出精确的协议说明和测试向量,方便您逐字节自检。
1. 密码学原语
- HMAC-SHA256 ——签名与派生。截断到指定长度时,取结果的前 N 个字节。
- HKDF-SHA256 (RFC 5869)——密钥派生。若
salt = null,则使用 32 字节的全零向量。 - X25519 (RFC 7748)——配对时的 ECDH。私钥需做 clamp 处理:
k[0] &= 0xF8; k[31] = (k[31] & 0x7F) | 0x40.
参考实现:mbedTLS(MBEDTLS_ECP_DP_CURVE25519)在 ESP 上,Android 上用 BouncyCastle——两端结果一致。
2. GATT:服务与特征值
所有 UUID 都是 128 位,基于 656e7472-7869-7900-XXXX-426c45000001 (ASCII 的 “entrixy”)。
| UUID 后缀 | 属性 | 用途 |
|---|---|---|
0000 | — | 服务 UUID(完整: 656e7472-7869-7900-0000-426c45000001) |
0001 NONCE | READ | 8 字节随机数(遗留方式;现代客户端从广播中取随机数,见 §3) |
0002 FIRE | WRITE | 开启指令:24 字节(所有者)或 47 字节(访客) |
0003 TIME | WRITE | 时钟校正,正好 26 字节 |
0004 WIFI | WRITE | 用于 NTP 的 SSID 和密码(可选) |
0005 RESULT | READ+NOTIFY | 每次写入后返回一个字节的结果码(§9) |
00f0 PAIR_PUB | READ | 仅在配对模式下:控制器 32 字节的 X25519 公钥(小端) |
00f1 PAIR_DONE | WRITE | 仅在配对模式下:36 字节 [phone_pub 32B][device_id 4B LE] |
"entrixy" 与服务 UUID 被放进 scan-response,而不是主包:128 位 UUID 加上厂商数据放不进主包的 31 字节。Android 的被动扫描只能看到主包,所以过滤是基于厂商数据做的(company id 0x00E0).3. GAP 广播
每隔 sleep_interval 秒,控制器就用厂商专有数据广播一次自己的存在。company ID 为 0x00E0 (在链路上为 E0 00)。该数据块共 23 字节 =2 字节 company ID 加 21 字节数值:
offset size field [0..1] 2 Company ID = E0 00 [2..5] 4 device_id (LE) [6..9] 4 counter (LE, monotonic, anti-replay, survives deep sleep) [10..17] 8 auth_hmac = HMAC(owner_secret, mac_in)[0..7] [18] 1 sleep_interval_s (plaintext) [19..22] 4 opts_cipher (battery, status, fw, hw — encrypted)
签名覆盖 13 字节 (不含 company ID):
mac_in = device_id(4) || counter(4) || sleep(1) || opts_cipher(4) auth_hmac = HMAC-SHA256(owner_secret, mac_in)[0..7]
auth_hmac 充当 nonce ,用于无需往返即可立即开启:没有 owner_secret 的人只会把它们看成随机数,而所有者或访客能算出签名并立刻触发。控制器会保留自己最近签发的 16 个随机数构成的环形缓冲。选项(opts)——XOR 密钥流
ks = HMAC-SHA256(owner_secret, "ks" || device_id(4 LE) || counter(4 LE))[0..3] opts_cipher[i] = opts_plain[i] XOR ks[i] opts_plain[0] = battery % [1] = status [2] = fw (major=(b>>4)&0xF, minor=b&0xF) [3] = hw
双稳态模式
对于双稳态驱动装置,数值后面再追加三个字节,共 26 字节 (见 §8)。
4. 配对
新控制器会自行进入 30–90 秒的配对模式(否则按键进入;长按 5 秒为恢复出厂设置)。整个交换在本地完成,不经服务器:
- 手机读取
PAIR_PUB(00f0)——控制器 32 字节的公钥。 - 手机生成自己的 X25519 密钥对,
shared = X25519(phone_priv, esp_pub). - 双方派生出同一个密钥:
owner_secret = HKDF-SHA256(salt="entrixy-pair-v1", ikm=shared, info="owner-secret", L=32)
- 手机写入
PAIR_DONE(00f1):[phone_pub 32B][device_id 4B LE]. 由手机生成 device_id,控制器把它保存下来。
owner_secret 绝不会经过空中传输。没有服务器参与:device_id 是应用自己生成的,所以它当然知道。5. 所有者开启(owner fire)
写入 FIRE (0002), 24 字节:
[nonce 8B][ HMAC-SHA256(owner_secret, nonce)[0..15] 16B ]
nonce ——8 字节的 auth_hmac ,取自最近一次广播。控制器在环形缓冲中核对该随机数,校验签名,给继电器一个脉冲,然后作废该随机数。
6. 访客开启(guest fire)
访客并不知道 owner_secret。所有者通过服务器签发一个凭据包: token, owner_sig, guest_key。写入 FIRE, 47 字节: [token 7B][owner_sig 16B][nonce 8B][guest_sig 16B].
| 字段 | 长度 | 公式 |
|---|---|---|
token | 7B | [bleId 2B LE][valid_until 4B LE unix-sec][flags 1B] |
owner_sig | 16B | HMAC-SHA256(owner_secret, token)[0..15] |
nonce | 8B | 取自广播的新鲜随机数 |
guest_sig | 16B | HMAC-SHA256(guest_key, nonce)[0..15] |
guest_key = HKDF-SHA256(salt=null, ikm=owner_secret, info="guest" || bleId(2B LE), L=32)
控制器:随机数在环内 → owner_sig 用它自己的 owner_secret → valid_until 对照自己的时钟 → 派生出 guest_key → guest_sig。它不保存任何访客名单——只校验签名。
7. 时间同步
访客令牌的有效期需要时钟。任何受信任的客户端都可以带签名地设置时间。写入 TIME (0003), 26 字节:
[bleId 2B LE][epoch_ms 8B LE][ HMAC-SHA256(key, payload[0..9])[0..15] 16B ]
bleId = 0→ 所有者,key = owner_secret.bleId ≠ 0→ 访客,key = guest_key(同 §6)。
访客时间受三重保护:棘轮机制(不可回拨)、前向上限(每次写入最多 +24 小时)和「先开门后授时」的门槛。外置 RTC 和 NTP 都是可选的。
8. 双稳态状态(K_state)
K_state = HKDF-SHA256(salt=null, ikm=owner_secret, info="ble-state-v1", L=16) [..21] state_enc = state_byte XOR K_state[counter & 15] // bit0: 1=open, 0=closed [22..23] state_mac = HMAC-SHA256(K_state, counter(4B LE) || state_enc)[0..1] // 2 bytes
9. 响应码(RESULT,特征值 0005)
| 代码 | 取值 | 代码 | 取值 |
|---|---|---|---|
0x00 | FIRE OK | 0x10 | TIME OK (owner) |
0x30 | FIRE OK — 已开 | 0x11 | TIME OK (guest) |
0x31 | FIRE OK — 已关 | 0x12 | TIME 长度不对 |
0x01 | 随机数已过期或不在环内 | 0x13 | TIME 的 HMAC 不匹配 |
0x02 | 有效期已过 | 0x15 | TIME 回拨了(棘轮) |
0x03 | owner_sig 无效 | 0x16 | TIME 超过了 +24 小时上限 |
0x04 | guest_sig 无效 | 0x17 | TIME 之前没有近期的开启动作 |
0x05 | 长度不对 | 0x20 | WIFI OK |
0x06 | HKDF failed | 0x21 | WIFI 格式不对 |
0x07 | 所有者的 HMAC 无效 | 0x22 | WIFI 的 HMAC 不匹配 |
10. 测试向量
确定性的已知答案向量:跑一遍您的实现,逐字节对比。所有数值均为十六进制,字节序与链路上一致。整条链路贯通:配对 → owner_secret → 其余一切。
Pairing (X25519 → owner_secret)
esp_priv (clamped) = 0002030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f60 phone_priv (clamped) = 201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a090807060504030241 esp_pub = 07a37cbc142093c8b755dc1b10e86cb426374ad16aa853ed0bdfc0b2b86d1c7c phone_pub = 0d799600f6ffaee2e121e6b8f7a05dc66874b51db3102d0d71f799a09cb4c461 shared_z = 53126e95ac6e407e8a412fdf82c87f1be45a2251edf9422ad00df2e83aaebd19 owner_secret = 3609fa67bd15cf2bbaecdd5305feea0d48e1f21d714d01b29082a5fec459c64b = HKDF(salt="entrixy-pair-v1", ikm=shared_z, info="owner-secret", L=32)
Advertise (did=0x0A0B0C0D, counter=7, sleep=1, opts=batt100/status1/fw v2.3/hw5)
ks[0..3] = ee0a8499 opts_cipher = 8a0ba79c auth_hmac = 1f2d8012178a6e06 (= nonce) mfg (23B) = e0000d0c0b0a070000001f2d8012178a6e06018a0ba79c
Owner fire (24B)
fire = 1f2d8012178a6e06eaafaae40500580a774b62be6e9b6aa2
= nonce || HMAC(owner_secret, nonce)[0..15]
Guest fire (47B) — bleId=0x0042, valid_until=0x6890ABCD, flags=0x01
token (7B) = 4200cdab906801 owner_sig (16B) = faa3a99a76dfba0521fb4eb4bd508559 = HMAC(owner_secret, token)[0..15] guest_key (32B) = 248b57d94ccafeb3cba598c6b81fc18617c1b54da1386ca1ed99bcd5dd012ff5 = HKDF(salt=null, ikm=owner_secret, info="guest"||bleId_LE, L=32) guest_sig (16B) = 7a6e02ae269827b78b7c49840c40476e = HMAC(guest_key, nonce)[0..15] fire (47B) = 4200cdab906801faa3a99a76dfba0521fb4eb4bd5085591f2d8012178a6e067a6e02ae269827b78b7c49840c40476e
Time-sync (26B) — owner (bleId=0), epoch_ms=1750000000000
time = 000000dc2074970100005e1120cdb0bb4c8f5ee22f2b1920cb99
= [bleId 2B LE][epoch_ms 8B LE][ HMAC(owner_secret, payload)[0..15] ]
K_state (16B)
K_state = 19bcfc41f61a103d79e9967c21403a93
= HKDF(salt=null, ikm=owner_secret, info="ble-state-v1", L=16)
参考代码与联系方式
两端的可用实现见 esp32-example/entrixy-ble (MIT 许可)。要构建 .bin 以适配您自己的板子,请使用 浏览器配置器。联网传输方式见 socket 协议。这在开放架构中的位置: /open。OEM 咨询: hello@entrixy.com.