Closed8

100日チャレンジ day44 (E2E暗号化チャット)

riddle_tecriddle_tec

昨日
https://zenn.dev/gin_nazo/scraps/fbc6986f4a1afe


https://blog.framinal.life/entry/2025/04/14/154104

100日チャレンジに感化されたので、アレンジして自分でもやってみます。

やりたいこと

  • 世の中のさまざまなドメインの簡易実装をつくり、バックエンドの実装に慣れる(dbスキーマ設計や、関数の分割、使いやすいインターフェイスの切り方に慣れる
  • 設計力(これはシステムのオーバービューを先に自分で作ってaiに依頼できるようにする
  • 生成aiをつかったバイブコーティングになれる
  • 実際にやったことはzennのスクラップにまとめ、成果はzennのブログにまとめる(アプリ自体の公開は必須ではないかコードはgithubにおく)

できたもの

https://github.com/lirlia/100day_challenge_backend/tree/main/day44_e2e_encrypted_chat

riddle_tecriddle_tec

E2E暗号化の学習、素晴らしいですね!非常に現代的で重要なトピックです。

過去のプロジェクトには Day7 Passkey 認証 (WebAuthn)Day33 ゼロ知識証明"体験型"デモアプリ があり、暗号技術に触れていますが、E2E暗号化に特化したものはありませんので、良いテーマだと思います。

E2E暗号化を学ぶためのアプリケーションとして、以下のようなアイデアはいかがでしょうか。

アプリケーションアイデア: E2E暗号化チャットアプリケーション

  • コンセプト: ユーザー間でメッセージを送受信するシンプルなチャットアプリケーション。ただし、サーバーは暗号化されたメッセージを中継するだけで、内容を解読することはできないE2E暗号化を実現します。
  • 学べること/主要機能:
    1. 鍵生成と管理:
      • 各ユーザーがクライアントサイドで公開鍵と秘密鍵のペアを生成 (例: RSA, ECC - Web Crypto API を利用)。
      • 公開鍵をサーバーに登録・共有する仕組み。秘密鍵はクライアントのローカルストレージなどに安全に(あくまでデモとしてですが)保存。
    2. 暗号化と署名:
      • メッセージ送信時: 送信者は受信者の公開鍵でメッセージを暗号化。さらに送信者自身の秘密鍵でメッセージにデジタル署名を行う(改ざん防止と送信者認証のため)。
      • サーバーは暗号化されたメッセージと署名を受け取り、受信者に中継する。
    3. 復号と検証:
      • メッセージ受信時: 受信者は自身の秘密鍵でメッセージを復号。さらに送信者の公開鍵でデジタル署名を検証。
    4. セッション管理/鍵交換 (発展):
      • より安全な通信のために、チャットセッションごとに共通鍵を生成し、その共通鍵でメッセージを暗号化する方式も考えられます(例: Diffie-Hellman鍵交換)。ただし、まずは公開鍵暗号方式ベースでの実装から始めるのが良いでしょう。
    5. ユーザーインターフェース:
      • ユーザー登録(ユーザー名のみでOK)。
      • チャット相手を選択する機能。
      • メッセージ送受信インターフェース。
      • (オプション)鍵が正しく機能しているか、暗号化/復号が成功/失敗したかなどを視覚的に示すインジケーター。
  • 技術要素:
    • Next.js (App Router), TypeScript
    • SQLite (ユーザー情報、公開鍵の保存など)
    • better-sqlite3
    • Web Crypto API (暗号化、復号、署名、鍵生成)
    • Tailwind CSS (UIスタイリング - ミニマリズムやサイバーパンク風のデザインなどモダンなものを試しましょう)
    • WebSocket (socket.ioなど) によるリアルタイムメッセージ送受信 (必須ではないが、よりチャットらしくなる)
      • 代替として、APIポーリングでも可。
  • デザインテーマ:
    • セキュリティを意識させるような、クリーンでモダンなデザイン。例えば、ダークテーマを基調としたミニマリズムや、アクセントカラーにネオングリーンなどを使ったサイバーパンク風のデザインも面白いかもしれません。

このアプリを作ることで学べるE2E暗号化の主要なポイント:

  • 公開鍵暗号方式の仕組みと応用(暗号化、デジタル署名)。
  • 鍵管理の重要性と難しさ(特に秘密鍵の安全な保持)。
  • サーバーがメッセージ内容を読めないことの意味と、その実現方法。
  • メッセージの完全性(改ざんされていないこと)と認証(送信者が本物であること)の担保。

実装ステップの提案:

  1. プロジェクト初期化と基本設計
    • day46_e2e_encrypted_chat のようなディレクトリを作成。
    • 基本的なUI(ユーザー登録、ログイン(簡易的)、チャット画面の骨子)を作成。
  2. ユーザーと鍵管理機能の実装
    • ユーザー登録API。
    • クライアントサイドでの鍵ペア生成 (Web Crypto API)。
    • 公開鍵をサーバーに保存するAPI。秘密鍵をローカルストレージに保存(デモ用途)。
    • ユーザー切り替え機能(userId ベースで)。
  3. メッセージ送受信API (暗号化なし)
    • まず、暗号化なしでメッセージを送受信できるAPIとUIを作成。
    • (オプション)WebSocketまたはポーリングによるリアルタイム更新。
  4. E2E暗号化の実装
    • 送信時: 受信者の公開鍵を取得し、メッセージを暗号化。送信者の秘密鍵で署名。
    • 受信時: 自身の秘密鍵で復号。送信者の公開鍵で署名を検証。
    • APIで暗号化されたメッセージと署名を扱うように変更。
  5. UI/UXの改善
    • 暗号化/復号の状態をユーザーにフィードバック。
    • エラーハンドリング。
  6. テストとデバッグ
    • 複数ユーザー間でメッセージが正しくE2E暗号化されて送受信できることを確認。
    • サーバー側のDBを見てもメッセージ内容が読めないことを確認。
  7. ドキュメント作成

このアイデアについて、どう思われますか? もしよろしければ、この方向で具体的な作業計画を作成します。

riddle_tecriddle_tec

ご指摘ありがとうございます。確かに、これらの暗号技術の使い分けは専門知識がないと分かりにくいですよね。E2E暗号化チャットでこれらの技術がどのように連携し、なぜそのような構成になっているのか、用語の解説と合わせてご説明します。

なぜ複数の暗号技術を組み合わせるのか? (ハイブリッド暗号)

このチャットシステムでは、「ハイブリッド暗号」という方式を採用しています。これは、特性の異なる複数の暗号技術を組み合わせて、それぞれの良いところ取りをする考え方です。

  • 共通鍵暗号 (AESなど):
    • 長所: 暗号化・復号の処理が非常に高速。
    • 短所: 暗号化と復号に同じ「共通鍵」を使うため、その鍵を安全に相手に渡す(共有する)のが難しい。もし鍵が途中で盗まれたら、通信内容が解読されてしまいます。
  • 公開鍵暗号 (RSAなど):
    • 長所: 「公開鍵」と「秘密鍵」というペアの鍵を使い、公開鍵は誰に知られても安全です。そのため、鍵の共有が容易です。
    • 短所: 処理速度が共通鍵暗号に比べて遅い。大きなデータ(例えばチャットのメッセージ本文全体)を直接暗号化するのには向きません。

そこで、ハイブリッド暗号では以下のように役割分担します。

  1. メッセージ本体の暗号化: 高速な共通鍵暗号 (AES-GCM) を使います。ただし、この共通鍵はメッセージごとに新しく作ります。
  2. 共通鍵の安全な共有: (1)で使った一時的な共通鍵を、相手の公開鍵暗号 (RSA-OAEP) を使って暗号化し、安全に相手に送ります。
  3. メッセージの信頼性確保: 送信者が本当に本人であること、そしてメッセージが途中で改ざんされていないことを保証するために、デジタル署名 (RSA-PSS) を使います。

それでは、具体的に各技術の用語と役割を見ていきましょう。


1. メッセージ本文のAES-GCMによる暗号化・復号

  • 用語解説:

    • AES (Advanced Encryption Standard):
      • 現在、世界標準として広く利用されている共通鍵暗号アルゴリズムの一つです。
      • データを特定の長さのブロック(AESでは128ビット)に区切って暗号化処理を行います(ブロック暗号)。
    • GCM (Galois/Counter Mode):
      • AESのようなブロック暗号の「利用モード」の一つです。
      • 単にデータを暗号化するだけでなく、同時に「認証タグ(MAC: Message Authentication Code)」と呼ばれる短いデータを生成します。
      • この認証タグによって、受信側でメッセージが途中で改ざんされていないか、そして本物であるか(認証性)を確認できます。
      • このように、暗号化による機密性に加えて、完全性認証性も提供できる暗号モードを AEAD (Authenticated Encryption with Associated Data) と呼び、GCMはその代表例です。
    • 共通鍵: AES-GCMでメッセージを暗号化・復号するために使う、送信者と受信者(この場合はメッセージごと)で共有される秘密の鍵です。このチャットでは、メッセージごとに一時的かつランダムな共通鍵を生成します。
    • IV (Initialization Vector / 初期化ベクトル): 暗号化処理の初期値として使われるランダムなデータです。同じ共通鍵を使っても、IVが異なれば暗号化結果は異なるものになるため、セキュリティを高める役割があります。GCMではIVのユニーク性が重要です。
  • 役割と使い分け:

    • チャットで送受信されるメッセージ本文そのものを暗号化・復号するために使われます。
    • AESは処理が高速なので、頻繁にやり取りされるメッセージ本文の暗号化に適しています。
    • GCMモードを使うことで、メッセージが改ざんされていないことも同時に保証されます。

2. AES共通鍵のRSA-OAEPによる暗号化・復号

  • 用語解説:

    • RSA (Rivest-Shamir-Adleman):
      • 最も有名な公開鍵暗号アルゴリズムの一つです。3人の開発者の頭文字から名付けられました。
      • 非常に大きな数の素因数分解が困難であることを安全性の根拠としています。
      • 「公開鍵」と「秘密鍵」のペアで構成され、公開鍵で暗号化されたデータは、対応する秘密鍵でしか復号できません。
    • OAEP (Optimal Asymmetric Encryption Padding):
      • RSAでデータを暗号化する際に、元データをそのまま使うのではなく、特定のルールに従って整形(パディング)する方法の一つです。
      • このパディング処理により、RSA暗号に対する特定の攻撃(例えば選択暗号文攻撃)を防ぎ、暗号の安全性を高めます。
    • 公開鍵・秘密鍵: RSAで使われるキーペアです。
      • 公開鍵: 他人に公開しても良い鍵で、メッセージを暗号化したり、デジタル署名を検証したりするのに使います。
      • 秘密鍵: 自分だけが厳重に保管する鍵で、暗号化されたメッセージを復号したり、デジタル署名を生成したりするのに使います。
  • 役割と使い分け:

    • 上記1でメッセージ本文を暗号化した**「AES共通鍵」を安全に相手に送るため**に使われます。
    • AES共通鍵はメッセージの秘密を守るための重要な鍵なので、これが漏洩すると大変です。
    • そこで、このAES共通鍵を、メッセージの**受信者の公開鍵(RSA)**を使って暗号化します。
    • 受信者の公開鍵で暗号化されたAES共通鍵は、その受信者の秘密鍵を持っている本人しか復号できません。これにより、AES共通鍵を安全に相手に届けることができます。
    • OAEPというパディング方式を使うことで、このRSAによる暗号化処理の安全性をさらに高めています。

3. メッセージ全体のRSA-PSSによる署名・検証

  • 用語解説:

    • RSA (Rivest-Shamir-Adleman): ここではデジタル署名の生成・検証に使われます。
    • PSS (Probabilistic Signature Scheme):
      • RSAでデジタル署名を行う際の、より安全性が高いとされる署名方式(パディングスキーム)です。
      • 従来の方式よりも強固なセキュリティ(証明可能安全性といって、ある仮定のもとで安全性が数学的に証明できる)を提供します。
    • デジタル署名:
      • メッセージの送信者が、そのメッセージが確かに自分が作成したものであり、かつ改ざんされていないことを証明するための仕組みです。
      • 送信者は自分の秘密鍵を使って署名を生成し、受信者は送信者の公開鍵を使って署名を検証します。
  • 役割と使い分け:

    • 送信されるメッセージ全体(具体的には、暗号化されたAES共通鍵、IV、AES-GCMで暗号化されたメッセージ本文をまとめたもの)に対して、送信者がデジタル署名を行います
    • これにより、受信者は以下の2点を確認できます。
      1. メッセージの完全性: メッセージが送信されてから受信するまでの間に、第三者によって内容が書き換えられていないこと。
      2. 送信者の認証 (否認防止): そのメッセージが、確かに対応する秘密鍵を持つ送信者本人から送られたものであること(送信者が「そんなメッセージは送っていない」と後から否定できない)。
    • PSSという署名方式を使うことで、このデジタル署名の信頼性を高めています。

まとめ:E2E暗号化チャットでの連携フロー

これらの技術は、チャットメッセージを送受信する際に以下のように連携して機能します。

メッセージ送信時:

  1. [本文暗号化] 送信者は、書き込んだメッセージ本文を、一時的に生成したAES共通鍵とIVを使って AES-GCM で暗号化します。
  2. [共通鍵の暗号化] 送信者は、(1)で使ったAES共通鍵を、メッセージの受信者のRSA公開鍵を使って RSA-OAEP で暗号化します。
  3. [署名生成] 送信者は、「(2)で暗号化されたAES共通鍵」「IV」「(1)で暗号化されたメッセージ本文」をひとまとめにし、それに対して自身のRSA秘密鍵を使って RSA-PSS でデジタル署名を生成します。
  4. [送信] 送信者は、「暗号化されたAES共通鍵」「IV」「暗号化されたメッセージ本文」「デジタル署名」の4点セットを受信者に送ります。

メッセージ受信時:

  1. [受信] 受信者は、送信者から4点セット(暗号化されたAES共通鍵、IV、暗号化されたメッセージ本文、デジタル署名)を受け取ります。
  2. [署名検証] 受信者は、まず送信者のRSA公開鍵を使って、受け取ったメッセージ全体の**デジタル署名(RSA-PSS)**が正しいか検証します。
    • もし検証に失敗したら、メッセージが改ざんされたか、なりすましの可能性があるため、処理を中止します。
  3. [共通鍵の復号] デジタル署名が正しければ、受信者は自身のRSA秘密鍵を使って、暗号化されたAES共通鍵を RSA-OAEP で復号し、元のAES共通鍵を取り出します。
  4. [本文復号] 受信者は、(3)で取り出したAES共通鍵とIVを使って、暗号化されたメッセージ本文を AES-GCM で復号します。
  5. [表示] これでようやく、受信者は元のメッセージ本文を読むことができます。

このように、それぞれの暗号技術が適切な役割を担い、連携することで、メッセージの「機密性(他人には読めない)」「完全性(改ざんされていない)」「認証性(本人が送ったもの)」を確保しつつ、効率的なエンドツーエンド暗号化を実現しています。

この説明で、使い分けと各用語の役割についてご理解いただけましたでしょうか。もしさらに不明な点があれば、お気軽にご質問ください。

riddle_tecriddle_tec

ご質問ありがとうございます。「なぜメッセージ本文そのものを(受信者の)公開鍵で暗号化しないのか?」ということですね。これは非常に良いポイントで、ハイブリッド暗号方式を採用している理由に直結します。

主な理由は以下の2点です。

  1. 処理速度の問題:

    • RSAのような公開鍵暗号アルゴリズムは、AESのような共通鍵暗号アルゴリズムに比べて、暗号化・復号の計算処理に非常に時間がかかります。一般的に数十倍から数百倍、あるいはそれ以上遅いと言われています。
    • チャットのように頻繁にメッセージをやり取りする場合、すべてのメッセージ本文を公開鍵暗号で処理していると、送信時も受信時も著しく遅延が発生し、ユーザー体験が大きく損なわれてしまいます。
    • 一方、共通鍵暗号(AES)は非常に高速に動作するため、メッセージ本文の暗号化に適しています。
  2. 一度に暗号化できるデータ長の制限:

    • RSAなどの公開鍵暗号アルゴリズムでは、一度に暗号化できるデータの長さに制限があります。通常、その長さはRSAの鍵長(例えば2048ビットや4096ビット)よりも短いデータブロックです。
    • 長いメッセージ本文を公開鍵暗号で直接暗号化しようとすると、メッセージを小さなブロックに分割し、それぞれのブロックに対して個別に公開鍵暗号処理を行う必要があります。これは非常に非効率的であり、処理時間もさらに増大します。
    • 共通鍵暗号(AES)は、ストリーム暗号のようなモード(CTRモードなど)や、GCMモードのようにデータを効率的に扱えるモードがあるため、長いメッセージでも比較的容易に扱えます。

これらの理由から、効率性と実用性を考慮して、以下のようなハイブリッド暗号方式が一般的に採用されています。

  • メッセージ本文(大きなデータ): 高速な**共通鍵暗号(AES-GCM)**で暗号化します。
  • その共通鍵(短いデータ): 安全な鍵交換が可能な**公開鍵暗号(RSA-OAEP)**で暗号化します。

つまり、公開鍵暗号は「共通鍵を安全に相手に渡す」という、その得意な部分(鍵交換)に限定して使用し、実際の大量のデータの暗号化は高速な共通鍵暗号に任せる、という役割分担をしているのです。

これにより、公開鍵暗号の「鍵交換の安全性」と共通鍵暗号の「処理の高速性」という、双方のメリットを活かしつつ、安全で実用的な暗号通信システムを構築できます。

もし、すべてのメッセージを公開鍵で暗号化するとしたら、

  • チャットのレスポンスが非常に遅く感じる。
  • 長いメッセージを送るのが困難になる。
    といった問題が発生するでしょう。

この説明で、なぜメッセージ本文を直接公開鍵で暗号化しないのか、ご理解いただけましたでしょうか?

riddle_tecriddle_tec

はい、承知いたしました。E2E暗号化チャットにおける、サーバーとクライアント間での暗号化データと鍵のやり取り、そしてクライアント側での復号処理のシーケンスをご説明します。

ここでのポイントは、サーバーはメッセージの内容を一切見ることができず、単に暗号化されたデータの「運び屋」および「保管場所」として機能するという点です。実際の暗号化・復号処理は全てクライアントサイド(ユーザーのブラウザ)で行われます。

前提

  • 鍵の事前準備:
    • User A (送信者)User B (受信者) は、それぞれ既に自分のRSAキーペア(公開鍵と秘密鍵)を生成済みです。
    • 秘密鍵は各ユーザーのローカルストレージに安全に保管されています。
    • 公開鍵はサーバーに登録されており、他のユーザーが取得可能な状態です。
    • したがって、User A は User B の公開鍵を、User B は User A の公開鍵を事前に(または必要に応じて)サーバーから取得しているものとします。

メッセージ送信・受信シーケンス

以下に、User A が User B にE2E暗号化メッセージを送る際のシーケンスを示します。

シーケンスのポイント解説

  • クライアントサイドでの暗号化 (ステップ 2-6):
    • 送信者 (User A) のブラウザ内で、メッセージ本文の暗号化、そのために使った共通鍵の暗号化、そして全体のデジタル署名が行われます。
    • サーバーに送られる時点では、メッセージ内容も、それを解くための共通鍵も、全て暗号化されています。
  • サーバーの役割 (ステップ 7-8, 9-10):
    • サーバーは、クライアントから送られてきた「暗号化されたデータの塊」を、送信者IDと受信者IDに基づいて適切に保存し、要求に応じて受信者に渡すだけです。
    • サーバーは、User B の秘密鍵を持っていないため、encryptedSymmetricKey(暗号化されたAES共通鍵)を復号できません。したがって、メッセージ本文を解読することも不可能です。
  • クライアントサイドでの復号と検証 (ステップ 11-16):
    • 受信者 (User B) のブラウザ内で、まずメッセージのデジタル署名を検証し、送信者が本物であることとメッセージが改ざんされていないことを確認します。
    • 次に、自身の秘密鍵を使って暗号化されたAES共通鍵を復号します。
    • 最後に、そのAES共通鍵を使ってメッセージ本文を復号します。
  • E2E暗号化の実現:
    • メッセージは User A の端末で暗号化され、User B の端末で復号されるまで、その間の経路(サーバーを含む)ではずっと暗号化されたままです。これがエンドツーエンド暗号化の基本的な仕組みです。

このシーケンスにより、メッセージの内容は送信者と意図された受信者以外には読み取ることができず、高いセキュリティが確保されます。
ご不明な点があれば、さらに詳しくご説明しますので、お気軽にお尋ねください。

このスクラップは3ヶ月前にクローズされました