バージョン 2026.1.0 より E2EE(エンドツーエンド暗号化)がサポートされました(eSeGeCe All-Access サブスクライバー専用)。
エンドツーエンド暗号化(E2EE)は、通信するピアのみが交換されたメッセージの内容を読めることを保証します。メッセージをルーティングするサーバーでさえ復号できません。本記事では、公開鍵暗号を使用して2 つのピア間で安全にメッセージを交換する E2EE の仕組みを説明します。
E2EE の解説
E2EE の核となる原則
2 ピア E2EE システムでは:
- 各ピアはデバイスから一切外に出ない秘密鍵を所有します。
- 各ピアは他者が閲覧できる公開鍵を公開します。
- メッセージは送信者のデバイスで暗号化され、受信者のデバイスでのみ復号されます。
- サーバーはメッセージリレーとしてのみ機能し、信頼された当事者ではありません。
鍵の概要
各ピア(例:アリスとボブ)は以下を持ちます:
- 秘密鍵
ローカルに保存される秘密の値。決して共有してはなりません。 - 公開鍵
秘密鍵から導出される値。自由に共有できます。
公開鍵と秘密鍵は数学的に結びついていますが、公開鍵を知っても秘密鍵は分かりません。
ステップ 1:公開鍵の交換暗号化通信を始める前に、アリスとボブはお互いの公開鍵を知る必要があります。
一般的なアプローチ:
- サーバーが公開鍵を保管し、要求に応じて配信します。
- アカウント作成時または初回接触時に公開鍵を交換します。
公開鍵は秘密ではないため、この交換によってセキュリティが損なわれることはありません。
ステップ 2:共有シークレットの確立(ECDH)メッセージを効率よく暗号化するため、アリスとボブはまず楕円曲線ディフィー・ヘルマン(ECDH)を使用して共有シークレットを導出します。
ECDH の仕組み(概念)- アリスは以下を使用して共有シークレットを計算します:
- 自分の秘密鍵
- ボブの公開鍵
- ボブは以下を使用して共有シークレットを計算します:
- 自分の秘密鍵
- アリスの公開鍵
楕円曲線の数学的特性により、どちらの側もそのシークレットを送信することなく、両方の計算が同じシークレット値を生成します。
共有シークレットがネットワーク上を送信されることは一切ありません。
ステップ 3:対称暗号化鍵の導出生の ECDH 共有シークレットは暗号化に直接使用しません。代わりに、通常は SHA-256 などの暗号ハッシュである鍵導出関数(KDF)で処理されます。
鍵導出の目的:
- 正しい長さの鍵を生成します(例:AES-256 では 32 バイト)
- 生の ECDH 出力の構造的バイアスを除去します
- 暗号強度を向上させます
結果はアリスとボブのみが知る対称暗号化鍵です。
ステップ 4:メッセージの暗号化アリスがボブにメッセージを送信する場合:
- アリスはメッセージをバイト列に変換します。
- アリスは対称暗号(一般的に AES-GCM)と以下を使用してメッセージを暗号化します:
- 導出した対称鍵
- ランダムな初期化ベクター(IV)
- アリスは暗号化されたメッセージと IV をサーバー経由でボブに送信します。
AES-GCM は以下を提供するため一般的に使用されます:
- 機密性(暗号化)
- 完全性(改ざん検出)
- 認証(偽造メッセージの検出)
ステップ 5:メッセージの復号
ボブが暗号化されたメッセージを受信した場合:
- ボブは ECDH と同じ KDF を使用して独立して同じ対称鍵を導出します。
- ボブは対称鍵と IV を使用してメッセージを復号します。
- 認証が成功した場合、ボブは元の平文を取得します。
メッセージが改ざんされていた場合や誤った鍵が使用された場合、復号は失敗します。
サーバーの役割このアーキテクチャでは、サーバーは:
- 公開鍵を配信します
- 暗号化されたメッセージをルーティングします
- ユーザーのプレゼンスやメタデータを管理します
サーバーは以下のことができません:
- 共有シークレットを導出すること
- メッセージを復号すること
- 有効な暗号化メッセージを偽造すること
これがエンドツーエンド暗号化の定義的特性です。
まとめ
2 つのピア間のエンドツーエンド暗号化は以下を組み合わせることで機能します:
- 公開鍵暗号(鍵の合意のため)
- 対称暗号(効率的なメッセージ暗号化のため)
- 鍵導出関数(セキュリティと正確性のため)
結果として以下のようなシステムが実現します:
- ピアのみがメッセージを読めます
- サーバーはトランスポートの役割に限定されます
- プライバシーはポリシーではなく設計によって保護されます
このモデルは現代の安全なメッセージングシステムの暗号基盤です。
E2EE サンプルコード
// ... Create the Server
WSServer := TsgcWebSocketHTTPServer.Create(nil);
WSServer.Port := 80;
WSPE2EE := TsgcWSPServer_E2EE.Create(nil);
WSPE2EE.Server := WSServer;
WSServer.Active := True;
// ... Create 2 clients
WSClient1 := TsgcWebSocketClient.Create(nil);
WSClient1.Host := '127.0.0.1';
WSClient1.Port := 80;
E2EE1 := TsgcWSPClient_E2EE.Create(nil);
E2EE1.Client := WSClient1;
E2EE1.E2EE_Otpions.UserId := 'client-1';
WSClient1.Active := True;
WSClient2 := TsgcWebSocketClient.Create(nil);
WSClient2.Host := '127.0.0.1';
WSClient2.Port := 80;
E2EE2 := TsgcWSPClient_E2EE.Create(nil);
E2EE2.OnE2EEMessageText := OnE2EEMessageTextEvent;
E2EE2.E2EE_Otpions.UserId := 'client-2';
E2EE2.Client := WSClient2;
WSClient2.Active := True;
// ... client-1 send a message to client-2
E2EE1.SendDirectMessage('client-2', 'Hello Client-2');
// ... read the message in the OnE2EEMessageText event
procedure OnE2EEMessageText(Sender: TObject; const aFrom, aText: string);
begin
DoLog('#direct_message: ' + aText);
end;
