更新日

ゼロ知識暗号化:Vaultedがあなたの秘密を守る仕組み

著者

ゼロ知識暗号化とは、サーバーがあなたの平文データや暗号化キーを決して見ないことを意味する。Vaultedは、データがサーバーに到達する前に、ブラウザ内でAES-256-GCMを使ってすべての秘密を暗号化することでこれを実現している。サーバーは暗号文のみを保存する — 復号キーへのアクセス手段は持たず、そのキーはURLフラグメントの中にのみ存在する。

この記事では、正確な暗号実装を解説する:キーの生成方法、データの暗号化方法、キーが受信者に届く仕組み、そしてパスフレーズを追加したときに何が起こるか。ここに記載されているすべての詳細は、あなたのブラウザで実際に動いているコードを反映している。

クライアントサイド暗号化が重要な理由についての概要は、クライアントサイド暗号化に関する以前の記事を参照してほしい。この記事では、暗号プリミティブをより深く掘り下げる。

キー生成

秘密を作成するたびに、VaultedはWeb Crypto APIを使って新鮮なAES-256-GCM暗号化キーを生成する:

crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, [
  'encrypt',
  'decrypt',
]);

いくつか注意点がある:

  • AES-GCM(Galois/Counter Mode)は機密性と完全性の両方を提供する — 不正読み取りだけでなく、改ざんも検出する。
  • 256ビットキー長はAESで使用可能な最強のオプションであり、世界中の政府や金融機関が採用している。
  • Extractable: true — キーは生のバイト列としてエクスポートでき、受信者のためにURLフラグメントに埋め込むことができる。

Web Crypto APIはブラウザのネイティブ暗号エンジンに処理を委譲する(例:ChromiumではBoringSSL、FirefoxではNSS)。キーはメモリ内で安全に生成され、ディスクやネットワークには触れない。

暗号化

キーが生成されたら、Vaultedは平文を暗号化する:

const iv = crypto.getRandomValues(new Uint8Array(12));

crypto.subtle.encrypt(
  { name: 'AES-GCM', iv },
  key,
  new TextEncoder().encode(plaintext),
);

初期化ベクトル(IV): AES-GCMは各暗号化操作に対して12バイト(96ビット)のランダムなIVを必要とする。IVは秘密である必要はない — 暗号文とともに保存される — しかし、同じキーで再利用してはならない。Vaultedは秘密ごとに新しいキーを生成するため、IVの再利用は設計上不可能だ。

エンコード: 暗号文とIVはどちらもbase64url文字列としてエンコードされる(標準のbase64で+-に、/_に置換し、末尾の=を削除したもの)。これにより、URLやJSONペイロードへの組み込みが安全になる。

暗号化された暗号文とIVはサーバーに送信されて保存される。平文はブラウザの外に出ることはない。

URLフラグメントによるキー配送

暗号化後、キーはエクスポートされてURLフラグメント — #の後ろの部分 — に配置される:

const rawKey = await crypto.subtle.exportKey('raw', key);
// rawKey → base64url-encoded string

生成された共有リンクはこのような形になる:

https://vaulted.fyi/s/abc123#dGhpcyBpcyBhIGtleQ
                              ^^^^^^^^^^^^^^^^^^^^^^^^
                              encryption key (base64url)

#区切り文字は重要だ。RFC 3986に従い、フラグメント識別子はクライアントによってのみ処理される。ブラウザはHTTPリクエストにフラグメントを含めない — URLにも、Refererヘッダーにも、サーバーが見られる場所には一切含まれない。

受信者がリンクを開くと:

  1. ブラウザはサーバーに/s/abc123をリクエストする(フラグメントなし)
  2. サーバーは暗号文とIVを返す
  3. JavaScriptがwindow.location.hashからフラグメントを読み取る
  4. キーはWeb Crypto APIに再インポートされる
  5. 暗号文はローカルで復号される

サーバーは暗号化されたblobの保存と取得を担う。暗号操作には一切関与しない。

オプションのパスフレーズ保護

追加の保護が必要な秘密のために、Vaultedはパスフレーズベースのキーラッピングをサポートしている。これにより第二の層が追加される:誰かがリンクを傍受したとしても、パスフレーズなしでは秘密を復号できない。

PBKDF2によるキー導出

パスフレーズを設定すると、Vaultedはこれを使ってPBKDF2でラッピングキーを導出する:

crypto.subtle.deriveKey(
  {
    name: 'PBKDF2',
    salt, // 16 bytes, randomly generated
    iterations: 600_000,
    hash: 'SHA-256',
  },
  keyMaterial, // the passphrase, imported as raw key material
  { name: 'AES-KW', length: 256 },
  false,
  ['wrapKey', 'unwrapKey'],
);
  • 600,000回のイテレーションにより、ブルートフォース攻撃が計算上高コストになる。
  • 16バイトのランダムソルトにより、同一のパスフレーズでも異なる秘密に対して異なる導出キーが生成される。
  • SHA-256はPBKDF2の各イテレーションで使用されるハッシュ関数だ。
  • 導出されたキーはAES-GCMキーではなくAES-KW(AES キーラップRFC 3394)キーだ。キーラッピングは暗号化とは別の操作だ。

暗号化キーのラッピング

導出されたAES-KWキーで元の暗号化キーをラップする:

crypto.subtle.wrapKey('raw', encryptionKey, wrappingKey, 'AES-KW');

URLフラグメントには、ラップされたキーとソルトの両方がドットで区切られて含まれる:

#wrappedKeyBase64url.saltBase64url

復号時のアンラッピング

受信者がパスフレーズを入力すると、Vaultedはプロセスを逆転させる:

  1. フラグメントを.で分割し、ラップされたキーとソルトを取得する
  2. パスフレーズとソルトを使ってPBKDF2で同じAES-KWラッピングキーを導出する
  3. crypto.subtle.unwrapKeyで暗号化キーをアンラップする
  4. アンラップされたAES-GCMキーで暗号文を復号する

パスフレーズが間違っている場合、unwrapKeyは例外をスローする — 部分的な復号や情報漏洩は起こらない。

復号フロー

パスフレーズなしの場合、復号は単純だ:

  1. URLフラグメントからキーをインポートする(crypto.subtle.importKey
  2. IVを使って暗号文を復号する(crypto.subtle.decrypt
  3. UTF-8バイト列から平文をデコードする

パスフレーズありの場合:

  1. フラグメントを分割し、ラップされたキーとソルトを抽出する
  2. パスフレーズとソルトからラッピングキーを導出する(PBKDF2)
  3. 暗号化キーをアンラップする(AES-KW)
  4. 暗号文を復号する(AES-GCM)
  5. 平文をデコードする

これらすべてはブラウザ内で行われる。サーバーは暗号化されたblobとメタデータ(閲覧回数、有効期限)を返すだけだ — それ以上でも以下でもない。

脅威モデル

どんなセキュリティツールもすべてから守ることはできない。ここでは、Vaultedのアーキテクチャが想定している脅威と、スコープ外の事項を示す。

Vaultedが守るもの

  • サーバーへの侵害 — サーバーは暗号化された暗号文のみを保存する。キー(サーバーには決して送られない)がなければ、データは読めない。データベースの完全ダンプを取っても、暗号化されたblobが手に入るだけだ。
  • データベース侵害 — 上記と同様。キーのない暗号文はAES-256-GCMにより計算上無意味だ。
  • サーバー側の中間者攻撃 — 暗号化キーはURLフラグメントに存在し、HTTPリクエストで送信されることはない。TLSが転送中の暗号文を保護する。
  • サービス運営者による秘密の閲覧 — ゼロ知識とはまさにそういう意味だ。キーを持っていないため、強制されたとしても私たちはあなたの秘密を読めない。

Vaultedが守らないもの

  • ブラウザやデバイスへの侵害 — 受信者のデバイスにマルウェアやキーロガーが存在する場合、復号後に復号された平文が取得される可能性がある。
  • 悪意のあるブラウザ拡張機能 — ページアクセス権を持つ拡張機能は、復号後のDOMを読み取れる。公開された秘密も含まれる。
  • リンクの傍受 — URLにはフラグメント内に暗号化キーが含まれている。完全なURL(フラグメントを含む)を入手した者は誰でも秘密を復号できる。安全なチャネルでリンクを共有してほしい。
  • のぞき見 — 秘密が画面に表示されたら、見ている人は誰でもそれを見ることができる。
  • 受信者の行動 — 復号後、受信者はスクリーンショットを撮ったり、コピーしたり、平文を共有したりできる。Vaultedはこれを防ぐことはできない。

パスフレーズ機能はリンク傍受のリスクを軽減する:完全なURLがあっても、攻撃者にはさらにパスフレーズが必要だ。多層防御のため、別のチャネル(電話や別のメッセージ)でパスフレーズを共有してほしい。

まとめ

Vaultedのゼロ知識暗号化が意味するのは:

  1. 秘密ごとに新鮮なAES-256-GCMキーが生成される
  2. 暗号化はすべてブラウザ内で行われる
  3. サーバーは暗号化された暗号文のみを保存する
  4. 復号キーはURLフラグメントのみを通じて伝達され、ブラウザはサーバーにそれを送ることはない
  5. オプションのパスフレーズラッピングにより、PBKDF2とAES-KWを経由した第二の層が追加される

結果として:たとえすべてのサーバー、データベース、ネットワーク経路が同時に侵害されたとしても、あなたの秘密は暗号化されたままだ。キーはあなたが共有するリンクの中にのみ存在する。


安全に秘密を共有する準備はできた?Vaultedで秘密を作成する — 10秒でできる。


関連記事