端到端加密

bleRPC 支持可选的端到端加密,使用 4 步密钥交换握手和 AES-128-GCM 会话加密。加密在连接建立期间协商,对应用层透明。

概述

组件算法
密钥协商X25519 ECDH
认证Ed25519 Signatures
密钥派生HKDF-SHA256
会话加密AES-128-GCM
重放保护单调递增计数器

能力协商

加密由 peripheral 在 CAPABILITIES 响应中广播。6 字节载荷包含 flags 字段:

max_request
16 bits (LE)
max_response
16 bits (LE)
flags
16 bits (LE)

如果 flags & 0x01 被设置,peripheral 支持加密,central 应发起密钥交换握手。

密钥交换握手

握手使用 4 个步骤,每个步骤都在 KEY_EXCHANGE 控制容器(control_cmd=0x6)中传输。每个载荷以 1 字节步骤标识符为前缀。

sequenceDiagram
    participant C as Central
    participant P as Peripheral
    Note over C: Generate ephemeral X25519 keypair
    C->>P: Step 1: [step:1] + central_x25519_pubkey (33 bytes)
    Note over P: Generate ephemeral X25519 keypair
    Note over P: Compute shared_secret = X25519(priv, central_pub)
    Note over P: Derive session_key = HKDF(shared_secret)
    Note over P: Sign(ed25519_privkey, central_pub || peripheral_pub)
    P->>C: Step 2: [step:1] + x25519_pub + signature + ed25519_pub (129 bytes)
    Note over C: Compute shared_secret = X25519(priv, peripheral_pub)
    Note over C: Derive session_key = HKDF(shared_secret)
    Note over C: Verify signature (TOFU for first connection)
    Note over C: Generate confirmation = encrypt(random, session_key)
    C->>P: Step 3: [step:1] + encrypted_confirmation (45 bytes)
    Note over P: Decrypt confirmation to verify session_key match
    Note over P: Generate confirmation = encrypt(random, session_key)
    P->>C: Step 4: [step:1] + encrypted_confirmation (45 bytes)
    Note over C: Decrypt confirmation to verify session_key match
    Note over C,P: Session key established - all subsequent data is encrypted
      

步骤 1:Central → Peripheral(33 字节)

Central 生成临时 X25519 密钥对并发送其公钥

step
1 byte
central_x25519_pubkey
32 bytes

步骤 2:Peripheral → Central(129 字节)

Peripheral 生成自己的临时 X25519 密钥对,计算共享密钥,派生会话密钥,并使用其长期 Ed25519 密钥签名两个公钥

step
1 byte
peripheral_x25519_pubkey
32 bytes
ed25519_signature
64 bytes
ed25519_pubkey
32 bytes

签名覆盖 central_x25519_pubkey || peripheral_x25519_pubkey(64 字节),将双方的临时密钥绑定在一起以防止中间人攻击

步骤 3:Central → Peripheral(45 字节)

Central 验证签名,计算共享密钥和会话密钥,然后发送加密确认以证明它持有正确的会话密钥

step
1 byte
nonce
12 bytes
ciphertext
16 bytes
tag
16 bytes

明文是 16 字节随机数据,使用派生的会话密钥和随机 12 字节 nonce 通过 AES-128-GCM 加密

步骤 4:Peripheral → Central(45 字节)

Peripheral 解密步骤 3 以验证会话密钥,然后发送自己的加密确认

step
1 byte
nonce
12 bytes
ciphertext
16 bytes
tag
16 bytes

Central 成功解密步骤 4 后,握手完成,双方共享相同的会话密钥

密钥派生

会话密钥从 X25519 共享密钥通过 HKDF-SHA256 派生:

shared_secret = X25519(my_privkey, peer_pubkey)        # 32 bytes
session_key   = HKDF-SHA256(
    salt = central_x25519_pubkey || peripheral_x25519_pubkey,  # 64 bytes
    ikm  = shared_secret,
    info = b"blerpc-session-key",
    len  = 16                                           # AES-128 key
)

Central 和 peripheral 都使用两个 X25519 公钥的拼接作为 salt 执行相同的派生,从而得到相同的 16 字节会话密钥

会话加密

握手完成后,所有命令载荷在容器分割之前进行加密:

加密载荷格式

counter
32 bits (LE)
ciphertext
variable
tag
128 bits

加密过程

  1. 序列化 CommandPacket(类型 + 名称 + protobuf 数据)
  2. 递增 TX 计数器
  3. 构造 12 字节 nonce:[counter_LE:4][direction:1][0x00:7]direction 为 Central→Peripheral 时是 0,Peripheral→Central 时是 1
  4. 使用 AES-128-GCM 加密:ciphertext, tag = AES-GCM(session_key, nonce, plaintext)
  5. 前缀添加计数器:[counter:4][ciphertext:N][tag:16]
  6. 按常规方式分割到容器中

解密过程

  1. 将容器重新组装为完整载荷
  2. 提取计数器(前 4 字节)、密文和标签(最后 16 字节)
  3. 验证计数器 > 上次接收的计数器(重放检测)
  4. 构造 nonce 并使用 AES-128-GCM 解密
  5. 将解密的数据解析为 CommandPacket

重放检测

每个方向维护一个独立的单调递增计数器:

这可以防止重放攻击,即攻击者重新发送之前捕获的加密数据包

信任模型

首次使用信任 (TOFU)

首次连接时,central 接受 peripheral 的 Ed25519 公钥并存储。在后续连接中,central 验证 peripheral 提供的是相同的密钥。如果密钥发生变化,连接将被拒绝(或提示用户)

这类似于 SSH 的 known_hosts 机制。它在首次连接后提供中间人攻击保护,无需 PKI 或证书颁发机构

平台实现

平台加密库密钥交换会话加密
PythoncryptographyCentralKeyExchange / PeripheralKeyExchangeBlerpcCryptoSession
Kotlin (Android)Java CryptoCentralKeyExchangeBlerpcCryptoSession
Swift (iOS)CryptoKitCentralKeyExchangeBlerpcCryptoSession
Dart (Flutter)cryptographyCentralKeyExchangeBlerpcCryptoSession
C (Zephyr)PSA Crypto (mbedTLS)blerpc_central_kx_* / blerpc_peripheral_kx_*blerpc_crypto_session

配置

启用加密

加密在 peripheral 端启用。启用后,peripheral 在其能力中广播加密支持,并期望 central 发起密钥交换

Zephyr 固件

# prj.conf
CONFIG_BLERPC_ENCRYPTION=y

Python Peripheral

# server.py - encryption is enabled when an Ed25519 signing key is provided.
# X25519 keypairs are generated ephemerally for each connection.
peripheral = BlerpcPeripheral(
    ed25519_private_key_hex="...",
)