エンドツーエンド暗号化

bleRPCは、4ステップの鍵交換ハンドシェイクとAES-128-GCMセッション暗号化によるオプションのエンドツーエンド暗号化をサポートしています。暗号化は接続セットアップ時にネゴシエーションされ、アプリケーション層に対して透過的です。

概要

コンポーネントアルゴリズム
鍵合意X25519 ECDH
認証Ed25519署名
鍵導出HKDF-SHA256
セッション暗号化AES-128-GCM
リプレイ防止単調増加カウンター

ケイパビリティネゴシエーション

暗号化は、ペリフェラルのCAPABILITIESレスポンスでアドバタイズされます。6バイトのペイロードにはflagsフィールドが含まれます:

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

flags & 0x01がセットされている場合、ペリフェラルは暗号化をサポートしており、セントラルは鍵交換ハンドシェイクを開始する必要があります。

鍵交換ハンドシェイク

ハンドシェイクは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バイト)

セントラルがエフェメラルなX25519鍵ペアを生成し、公開鍵を送信

step
1 byte
central_x25519_pubkey
32 bytes

ステップ2: Peripheral → Central (129バイト)

ペリフェラルが独自のエフェメラルな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バイト)

セントラルが署名を検証し、共有シークレットとセッション鍵を計算した後、正しいセッション鍵を保持していることを証明するために暗号化された確認を送信

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

平文は16バイトのランダムデータで、導出されたセッション鍵とランダムな12バイトのnonceを使用してAES-128-GCMで暗号化

ステップ4: Peripheral → Central (45バイト)

ペリフェラルがステップ3を復号してセッション鍵を検証し、独自の暗号化された確認を送信

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

セントラルがステップ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
)

セントラルとペリフェラルの両方が、両者のX25519公開鍵の連結をソルトとして使用して同じ導出を行い、同一の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としてパース

リプレイ検出

各方向は独立した単調増加カウンターを維持します:

これにより、攻撃者が過去にキャプチャした暗号化パケットを再送信するリプレイ攻撃を防止

信頼モデル

Trust On First Use (TOFU)

最初の接続時、セントラルはペリフェラルのEd25519公開鍵を受け入れて保存します。以降の接続では、セントラルはペリフェラルが同じ鍵を提示するかを検証します。鍵が変更された場合、接続は拒否される(またはユーザーに確認を求める)

これは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

設定

暗号化の有効化

暗号化はペリフェラル側で有効にします。有効にすると、ペリフェラルはケイパビリティで暗号化サポートをアドバタイズし、セントラルが鍵交換を開始することを期待

Zephyrファームウェア

# prj.conf
CONFIG_BLERPC_ENCRYPTION=y

Python Peripheral

# server.py - Ed25519署名鍵が提供されると暗号化が有効になります。
# X25519鍵ペアは接続ごとにエフェメラルに生成されます。
peripheral = BlerpcPeripheral(
    ed25519_private_key_hex="...",
)