エンドツーエンド暗号化
bleRPCは、4ステップの鍵交換ハンドシェイクとAES-128-GCMセッション暗号化によるオプションのエンドツーエンド暗号化をサポートしています。暗号化は接続確立時に合意形成が行われ、アプリケーション層では意識する必要がありません。
暗号コンポーネント
| コンポーネント | アルゴリズム |
|---|---|
| 鍵合意 | X25519 ECDH |
| 認証 | Ed25519署名 |
| 鍵導出 | HKDF-SHA256 |
| セッション暗号化 | AES-128-GCM |
| リプレイ防止 | 単調増加カウンター |
機能の合意形成
暗号化は、ペリフェラルのCAPABILITIESレスポンスで通知されます。6バイトのペイロードにはflagsフィールドが含まれます:
flags & 0x01がセットされている場合、ペリフェラルは暗号化をサポートしており、セントラルは鍵交換ハンドシェイクを開始する必要があります。
鍵交換ハンドシェイク
ハンドシェイクは4つのステップを使用し、各ステップはKEY_EXCHANGE制御コンテナ(control_cmd=0x6)で送信されます。各ペイロードには1バイトのステップ識別子がプレフィックスとして付きます。
ステップ1: Central → Peripheral (33バイト)
セントラルがエフェメラルなX25519鍵ペアを生成し、公開鍵を送信
ステップ2: Peripheral → Central (129バイト)
ペリフェラルが独自のエフェメラルなX25519鍵ペアを生成し、共有シークレットを計算し、セッション鍵を導出し、長期Ed25519鍵で両方の公開鍵に署名
署名はcentral_x25519_pubkey || peripheral_x25519_pubkey(64バイト)を対象とし、中間者攻撃を防ぐために両者のエフェメラル鍵をバインド
ステップ3: Central → Peripheral (45バイト)
セントラルが署名を検証し、共有シークレットとセッション鍵を計算した後、正しいセッション鍵を保持していることを証明するために暗号化された確認を送信
平文は固定された16バイトのマーカー(BLERPC_CONFIRM_C)で、導出されたセッション鍵とランダムな12バイトのnonceを使用してAES-128-GCMで暗号化されます。ペリフェラルはこれを復号し、期待される正確なマーカーと一致するかを確認することで、両者が同じセッション鍵を保持していることを証明します
ステップ4: Peripheral → Central (45バイト)
ペリフェラルがステップ3を復号し、期待されるBLERPC_CONFIRM_Cマーカーと一致することを検証した後、BLERPC_CONFIRM_Pを含む独自の暗号化された確認を送信
セントラルがステップ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バイトセッション鍵に到達
セッション暗号化
ハンドシェイク後、すべてのコマンドペイロードはコンテナ分割前に暗号化されます:
暗号化ペイロードフォーマット
暗号化プロセス
- CommandPacketをシリアライズ(タイプ + 名前 + protobufデータ)
- 現在のTXカウンターから12バイトのnonceを構築:
[counter_LE:4][direction:1][0x00:7]—directionはCentral→Peripheralの場合0、Peripheral→Centralの場合1 - AES-128-GCMで暗号化:
ciphertext, tag = AES-GCM(session_key, nonce, plaintext) - 現在のカウンターをプレフィックス:
[counter:4][ciphertext:N][tag:16] - 次のメッセージのためにTXカウンターをインクリメント(したがってセッション最初のメッセージはカウンター
0を使用) - 通常通りコンテナに分割
復号プロセス
- コンテナを完全なペイロードに再組み立て
- カウンター(先頭4バイト)、暗号文、タグ(末尾16バイト)を抽出
- カウンターが最後に受信したカウンターより大きいことを検証(リプレイ検出)
- nonceを構築しAES-128-GCMで復号
- 復号されたデータをCommandPacketとしてパース
リプレイ検出
各方向は独立した単調増加カウンターを維持します:
- TXカウンター: 0から開始。各メッセージは現在の値で暗号化され、その後インクリメントされます — したがって最初のメッセージはカウンター0を使用します
- RXカウンター: 最後に受信したカウンターを追跡。最後に受信したカウンター以下のカウンターを持つメッセージは拒否されます
これにより、攻撃者が過去にキャプチャした暗号化パケットを再送信するリプレイ攻撃を防止
信頼モデル
Trust On First Use (TOFU)
最初の接続時、クライアントはペリフェラルのEd25519公開鍵をピン留めします。以降の接続では、ペリフェラルが同じ鍵を提示するかを検証し、鍵が変更されていれば接続を拒否します。v0.7.0以降のクライアントライブラリでは、このピン留めはデフォルトで有効かつフェイルクローズで、鍵交換中に自動的に実行されます。アプリはピン留めした鍵を永続化する小さなキーストア(Android の SharedPreferences、iOS の UserDefaults、AsyncStorage、またはファイル)を提供するだけです
これはSSHのknown_hostsメカニズムに類似しています。PKIや認証局を必要とせずに、最初の接続後の中間者攻撃に対する保護を提供
プラットフォーム実装
| プラットフォーム | 暗号ライブラリ | 鍵交換 | セッション暗号化 |
|---|---|---|---|
| Python | cryptography | CentralKeyExchange / PeripheralKeyExchange | BlerpcCryptoSession |
| Kotlin (Android) | Java Crypto | CentralKeyExchange | BlerpcCryptoSession |
| Swift (iOS) | CryptoKit | CentralKeyExchange | BlerpcCryptoSession |
| Dart (Flutter) | cryptography | CentralKeyExchange | BlerpcCryptoSession |
| 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="...",
)