エンドツーエンド暗号化

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バイトのステップ識別子がプレフィックスとして付きます。

ステップ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バイトのマーカー(BLERPC_CONFIRM_C)で、導出されたセッション鍵とランダムな12バイトのnonceを使用してAES-128-GCMで暗号化されます。ペリフェラルはこれを復号し、期待される正確なマーカーと一致するかを確認することで、両者が同じセッション鍵を保持していることを証明します

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

ペリフェラルがステップ3を復号し、期待されるBLERPC_CONFIRM_Cマーカーと一致することを検証した後、BLERPC_CONFIRM_Pを含む独自の暗号化された確認を送信

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カウンターから12バイトのnonceを構築: [counter_LE:4][direction:1][0x00:7]directionはCentral→Peripheralの場合0、Peripheral→Centralの場合1
  3. AES-128-GCMで暗号化: ciphertext, tag = AES-GCM(session_key, nonce, plaintext)
  4. 現在のカウンターをプレフィックス: [counter:4][ciphertext:N][tag:16]
  5. 次のメッセージのためにTXカウンターをインクリメント(したがってセッション最初のメッセージはカウンター0を使用)
  6. 通常通りコンテナに分割

復号プロセス

  1. コンテナを完全なペイロードに再組み立て
  2. カウンター(先頭4バイト)、暗号文、タグ(末尾16バイト)を抽出
  3. カウンターが最後に受信したカウンターより大きいことを検証(リプレイ検出)
  4. nonceを構築しAES-128-GCMで復号
  5. 復号されたデータをCommandPacketとしてパース

リプレイ検出

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

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

信頼モデル

Trust On First Use (TOFU)

最初の接続時、クライアントはペリフェラルのEd25519公開鍵をピン留めします。以降の接続では、ペリフェラルが同じ鍵を提示するかを検証し、鍵が変更されていれば接続を拒否します。v0.7.0以降のクライアントライブラリでは、このピン留めはデフォルトで有効かつフェイルクローズで、鍵交換中に自動的に実行されます。アプリはピン留めした鍵を永続化する小さなキーストア(Android の SharedPreferences、iOS の UserDefaults、AsyncStorage、またはファイル)を提供するだけです

これは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="...",
)