「ブラウザでファイルを暗号化」「クライアントサイド暗号化」で検索すると、結果はほぼ同じ文を返す。計算は端末で終わる、ファイルは上がらない、鍵はデバイスを出ない。安心できる。証明にはならない。ページはそう書いておきながら、入力欄のパスフレーズ、ファイルの切片、アドレスバーの鍵を fetch で送れる。確かめる対象は文案ではない。この一回の操作が、平文をブラウザの外へ出したかだ。

本稿は製品の選び方ではない。ツールページの機能一覧でもない。問いは狭い。サイトが「ブラウザ内で暗号化する」と書いたとき、開発者ツールとアドレスバーで、いま何が見えるか。MyPassGen のファイル暗号とワンタイムリンクは、どちらも ウェブ暗号化 API の AES-256-GCM を使う。登録は要らない。同じ確認が使える。「保存しません」を信じなくてよい。

スローガンは確かめられない。通信は確かめられる

「クライアントサイド暗号化」は摩耗した語だ。あるサイトはファイルを自前のサーバーへ POST し、向こうで AES をかけ、ダウンロードリンクを返す。それでも「オンライン暗号化」と呼ぶ。別のサイトはタブの中で crypto.subtle.encrypt を呼び、.lock か .enc を書き、業務のアップロードを一切出さない。宣伝文は一文でまとめられる。ネットワークパネルはまとめられない。

ブラウザはスローガンを審査しない。このクリックが生んだドキュメント要求、スクリプト、画像、XHR / Fetch を列挙するだけだ。リクエストURLに平文があるか、本文にパスフレーズがあるか、解析の url に # の後ろの鍵が混ざっているか。見える。それが無いなら、「既定ではこの端末を出ない」はまだ立つ。あれば、スローガンはそこで終わる。

ソースを読み切る必要はない。繰り返せる一手で足りる。パネルを開き、具体的な一事をする(パスワードを作る、捨ててよいパスフレーズを見る、小さいファイルを暗号化する、ワンタイムリンクを作る)。新しい要求を一つずつ開く。先に「ローカル」がどの層を指すかを分ける。次にシャープと疑問符を分ける。それから手順に入る。

「ローカル」が指す層

ウェブ暗号化 API は、ブラウザが用意する暗号の面だ。crypto.subtle でハッシュ、導出、暗号化、復号をする。W3C の用例には、利用者がブラウザで鍵を選び、文書を暗号化し、そのあと暗号化したバイトを事業者へ上げる設計もある。API が保証するのは、計算がこの端末で走れることだ。あとから暗号文を送ることを禁じない。先に上げてから暗号化する作者も止めない。

ウェブ暗号化 API は 安全なコンテキストも要求する。本番では HTTPS(例外は大抵 localhost)。売り文句ではない。前提だ。素の HTTP のページが「Web Crypto を使っている」と書いたら、アルゴリズムの前にアドレスバーの鍵マークを見る。

この端末の AES-256-GCM が意味すること

MyPassGen の初版は AES-256-GCM だけを使う。MDN の AesGcmParams と、そこに引かれる NIST SP 800-38D では、GCM は認証付き暗号だ。暗号文が改ざんされれば復号は失敗する。文書に見える文字化けは出てこない。推奨 IV は 96 ビット、つまり 12 バイト。同じ鍵では、暗号化のたびに新しい IV が要る。IV は秘密ではない。暗号文と一緒に運んでよい。認証タグの既定は 128 ビット。実装で照合できる数字だ。暗記するスローガンではない。

ファイル暗号は、パスフレーズから鍵も導出する。このサイトのファイル暗号は PBKDF2、反復 100,000 回、SHA-256、塩 16 バイト、そのあと 256 ビットの AES-GCM 鍵。1 ファイルは最大 5 GB。出力は .lock か .enc。パスフレーズがファイルと一緒に POST されれば、反復回数は救わない。最初に見る対象は、やはり通信だ。KDF ではない。

「ブラウザで暗号化する」は三種類ある

流れを開くと、少なくとも三つに分かれる。第一:タブはファイルを選ぶだけ。バイトはサーバーへ行き、暗号化は向こうで起きる。第二:タブが先に暗号化し、上げるのは暗号文だけ。鍵は別の経路(たとえば URL の #)を通る。第三:結果はこの端末のダウンロードに落ち、業務リクエストにファイル本体が無い。三つとも「ウェブページ上の暗号化」として売れる。平文を既定で上げないと名乗れるのは、後の二つだけだ。仕事は、目の前のページがどれかを、ネットワークで決めることだ。

始める前に、見る対象を決める

パスワード生成、強度チェック、リンクの掃除、ファイル暗号では、「平文が業務データとして出ていない」が期待値だ。ワンタイムリンクは違う。暗号文の欄は出てよい。今打った文は出てこない。リクエスト行に # の後ろの鍵も出てこない。二つの期待を混ぜると、普通の暗号文 POST が「漏れた」に見える。

シャープと疑問符は別物

URL はスキーム、ホスト、パス、クエリー(? の後ろ)、フラグメント(# の後ろ)に分かれる。RFC 3986 §3.5 は # の後ろを fragment と呼ぶ。主資源が届いたあと、クライアントが解釈する二次的な位置だ。MDN の URI フラグメントはもっと短い。その URI を要求するとき、フラグメントはサーバーへ送られない。資源が着いてから、クライアントが扱う。

クエリー文字列は逆だ。https を開くと、? の後ろの 名前=値 はリクエスト行に入り、向こうのアクセスログにも残る。前稿のリンクを転送するとき、消してよいパラメータと残すパラメータは、utm_source も id= もクエリーの名前として扱った。鍵を ?key= と書けば、サーバー、リバースプロキシ、CDN のログが見られる。# の後ろに書けば、既定のリクエスト行にその切片は無い。

Referer もフラグメントを落とす。MDN の Referer は、オリジン、パス、クエリーは持てると書く。URL フラグメントとユーザー名パスワードは持てない。W3C の Referrer Policy も、「リファラーとして使う前に剥がす」段階でフラグメントを空にする。だから「鍵を # の後ろに置けば、暗号文を預かる機械へ HTTP では届かない」はプロトコルの振る舞いだ。ベンダーの約束ではない。

このサイトのワンタイムリンクは s.html?id={id}#{key} の形だ。クエリーにあるのは暗号文の番号だけ。鍵はシャープの後ろだけ。作成時、タブは 32 バイトの乱数鍵を作り、IV 12 バイトで AES-256-GCM を走らせ、暗号文を POST する。閲覧時、スクリプトは location.hash から鍵を取り、この端末で復号する。平文の上限は 32 KB。確かめる点は二つ。作成の JSON に暗号文の欄があり、今打った文が無いこと。閲覧ページのドキュメント要求に id= があり、# の後ろが無いこと。

ネットワークパネルでその場確認

Chrome、Edge、Firefox、Safari はいずれもネットワークパネルを持つ。名前は少し違う。手順は同じだ。拡張は要らない。ファイルを第三者の「検査サイト」へ渡す必要もない。完全な URL やファイルを、もう一度上げるだけだ。露出面が広がる。

  1. 開発者ツールを開き、ネットワーク(Network)へ切り替える。「ログを保存 / Preserve log」を入れる。遷移で一覧が消えないようにする。
  2. フィルタを Fetch / XHR(または「XHR」)にする。静的資源は後でよい。業務のアップロードは、ほぼこの種類を使う。
  3. 確かめたい操作をする。パスワードを作る、捨ててよいテスト用パスフレーズを入れる、UTM の付いた URL を貼る、小さいファイルを選んで暗号化する、捨て文を書いてワンタイムリンクを作る。
  4. 新しい要求を一つずつ開く。Request URL を読む。ドキュメントと API の住所に、今打った平文は出ない。# と鍵も出ない。
  5. ペイロード / リクエストを開く。JSON やフォームに、原文、パスフレーズ、検査中のパスワード、ファイルの生バイトは出ない。ワンタイムの作成は暗号文の欄を出してよい。その欄は Base64 の乱数に見える。今打った文には見えない。
  6. 解析や計測の要求を一通り見る(名前に matomo、collect、g/collect が付くことが多い)。報告されたページ URL や独自パラメータが、location.href をハッシュごと送っていないかを見る。

ファイル暗号には、より硬い対照がある。回線を切るか機内モードにして、小さいファイルを暗号化する。計算がこの端末に残るなら、.lock / .enc のダウンロードは成功するはずだ。ワンタイムリンクはこの試験を通れない。暗号文をサーバーへ預ける必要がある。オフラインで作成が失敗するのは想定どおりだ。罠ではない。「要求がゼロ」だけを合格線にすると、ゼロ知識のワンタイムリンクを誤って落とす。

強度チェックのページは、静的な弱いパスワード名簿を取りに行くことがある(このサイトは leaked-top10k.txt)。単語表だ。あなたの入力ではない。ネットワークでは名簿ファイルは出てよい。入力欄のパスワードがクエリーや本文に出てはいけない。全網の HIBP 突合とは別だ。HIBP 型は、ハッシュやパスワードの切片を外部 API へ送る。

いま何をしているか ネットワークに出てよいもの 出てこないもの
ランダムパスワードを作る ページ自身のスクリプトとスタイル 生成結果や選んだ文字種の POST
パスフレーズを端末で見る 静的な弱いパスワード名簿 入力欄の検査対象パスワード
リンクを掃除するか、テキストをマスクする 原文を運ぶ業務リクエストが無いこと 完全な URL、番号、メールの原文
ワンタイムリンクを作る 暗号文、id、期限、閲覧回数 平文。リクエスト行の # 鍵
ファイルを暗号化する ファイル本体の業務アップロードが無いこと ファイルのバイト、パスフレーズ

MyPassGen はこの表で動く。ランダムパスワードは 6–128 文字(既定 16。8 未満は弱いと出す)。強度チェックは端末の名簿であり、全網の突合ではない。掃除とファイルの暗号化/復号は、原文を既定で業務データとして上げない。ワンタイムリンクが預かるのは暗号文だけ。ツールはすべて、登録なしで開く。「出てこない」列は、パネルで自分で印を付けられる。先にブランドの約束を飲む必要はない。

暗号文が出ることと、平文が出ることは違う

ゼロ知識の共有と「完全にオフライン」は、一文に潰されやすい。ファイル暗号は後者だ。結果はダウンロードフォルダに落ち、サーバーにそのファイルの業務コピーは無い。ワンタイムリンクは前者だ。受け手が自分のブラウザで復号するには、向こうが暗号文の塊を取り出せなければならない。サーバーが暗号文を見て、鍵を見ないのは設計だ。事故ではない。

漏れの試験は、そこで分ける。作成時、POST 本文の長い Base64 は普通だ。同じ欄が、今打った「テスト用 API キー」として読めるなら、平文が出た。閲覧ページのドキュメント要求は s.html?id=… になるはずだ。仕様どおりなら、ネットワークに並ぶ Request URL に # は乗らない。その要求のヘッダーを開き、リクエスト行と Referer のどちらにも鍵が無いことを見る。

GCM の認証タグは、「数バイトいじってから復号する」を難しくする。タグが合わなければ decrypt はその場で失敗する。守るのは完全性と真正性だ。リンクが転送されないことではない。完全な URL(シャープの後ろの鍵を含む)を持った人は、閲覧回数が尽きるまで復号できる。ネットワークが見るのは、サーバーが鍵を受け取ったかどうかだ。チャット、メール、スクリーンショットは別のリスクだ。次の節で単独に扱う。

完全なワンタイムリンクを、原文を上げる「検査サイト」へ貼らない

# の後ろの鍵は、HTTP サーバーには見えない。次に貼るサイトには、全部見える。確認は自分の開発者ツールでする。来歴の分からない検査ページへ s.html?id=…#… を渡さない。

仕様がカバーしない漏れ

HTTP がフラグメントを送らないことは、鍵がこのコンピュータを一生出ないことではない。ページのスクリプトは window.location.hash を読み、自分で fetch できる。実装の誤りだ。プロトコルの穴ではない。六番目の手順は、そのためのものだ。解析がページ URL として location.href をそのまま報告すれば、ハッシュの後ろは統計ログに入る。正しい報告はパスか、ハッシュを捨てた URL だ。「HTTP は送らない」は代替にならない。

ブラウザの履歴、同期したタブ、一部のクラッシュ報告は、完全な URL を残す。ワンタイムリンクをアドレスバーに置きっ放しにすると、鍵をローカル履歴に置くことになる。共有は一度限りの経路でする。読んだあと、# 付きの住所をチケットのひな型へ貼らない。Referer はフラグメントを剥がす。クエリーパラメータは第三者へ乗ることがある。鍵を ?key= へ移さない。

画面、クリップボード、グループチャットのログは、プロトコルの外だ。同僚が完全なリンクをチャットへ撮っても、サーバーはまだ鍵を持っていない。スレッドの他の人は、もう持っている。ブラウザ内暗号化が答えるのは「計算はどこで走ったか、既定で誰が平文を見られないか」だ。「完全な URL を受け取った人が転送するか」には答えない。ファイルのパスフレーズも同じだ。.lock はドライブへ置ける。パスフレーズは別の経路が要る。短い秘密はワンタイムリンク向きだ。パスフレーズと同じメール本文へ書かない。

よくある誤解

「オフラインで使えないなら、平文を上げている。」ワンタイムリンクは暗号文を預ける。オフライン失敗は、暗号文の転送が一度要ることだけを示す。見るのは、その転送の中身だ。「要求があった」で止めない。

「HTTPS がすでに経路を暗号化している。端末側の暗号化は重複だ。」HTTPS が守るのは、線上の盗聴者だ。サーバーは TLS の終点だ。リクエスト本文とクエリーは読める。ブラウザ内暗号化が止めるのは、「サーバーや途中の解析が、既定で平文を読む」層だ。TLS と重ねる。仕事は違う。

「ページに Web Crypto と書いてある。平文は上がっていない。」ウェブ暗号化 API が渡すのはプリミティブだけだ。先に encrypt して暗号文を上げる流れも、先に FormData を POST してサーバーで暗号化する流れも、同じ API 名をフッターに飾れる。パネルのペイロードが、スローガンより先だ。

「製品ホストへの要求が見えない。安全だ。」拡張、システムのプロキシ、一部の社内ゲートウェイは、ページのネットワーク一覧に自分を描かない。パネルは「何も上がらない」広告を反証できる。世界に二本目の経路が無いことは証明できない。日常のオンラインツール選びでは、反証で足りる。平文が見えたら、手を止める。

どこから始めるか

今日すでに要る、捨ててよい操作を一つ選ぶ。ファイル暗号が一番対照しやすい。機密の無いスクリーンショットを選び、仮のパスフレーズを入れ、ネットワークを開き、暗号化を押し、ファイル本体の POST が無いことを見てから、.lock か .enc を落とす。その絵を人へ渡すときは、暗号文はドライブ、パスフレーズは別のメッセージ。1 ファイルは 5 GB 以下。

一行のパスワードや復旧コードを渡すなら、ワンタイムリンクへ切り替える。テスト文を書き、リンクを作り、作成要求に暗号文だけがあることを見る。プライベートウィンドウで閲覧ページを開き、ドキュメントのリクエスト行が id= だけかを見る。閲覧ページを、索引される記事として配らない。一度限りの暗号文の場面だ。リンクの掃除は、前稿の表のまま。UTM とクリック ID を、この端末で外す。

一回終われば、見出しの問いに答えられる。ブラウザ内暗号化が本物かは、スローガンではない。この通信に平文、パスフレーズ、シャープの後ろの鍵があったかどうかだ。AES-256-GCM、Web Crypto、登録なしで開くツールは、並列で照合できる制約だ。登録してから聞く約束ではない。

よくある質問

なぜファイル暗号はオフラインで動き、ワンタイムリンクは動かないのか。

ファイル暗号の着地は、この端末のダウンロードだ。ワンタイムリンクは、あとから受け手が暗号文を取れる必要がある。だから作成は暗号文を POST する。どちらもブラウザ内で AES-256-GCM を終えられる。出てよい中身が違う。ファイル暗号にファイル本体は出ない。ワンタイムの作成は暗号文を出してよく、平文と鍵は出さない。

# の後ろの鍵を、サーバーは本当に受け取らないのか。

通常の URI と HTTP の実装では、ドキュメント要求と Referer はフラグメントを載せない。ネットワークの、その一行のドキュメント要求でその場で見える。スクリプトが location.hash を読んで報告すれば、それはページ自身の振る舞いだ。Fetch の一覧で探す。

鍵をクエリーに置いた方が簡単ではないか。

簡単だ。そしてサーバーログに残る。?id= は暗号文の位置を示してよい。?key= は復号能力を、ホストと経路上のログへ渡す。受け手は復号でき、ホストは復号できない必要があるなら、鍵は # の後ろへ置く。

この確認に登録は要るか。解析が原文を持っていくか。

登録は要らない。解析がページ種別や、ハッシュを捨てたパスだけを残すなら、入力欄の中身は持っていかない。完全な href を報告すれば、# の後ろの鍵は統計ログに入り得る。ネットワークで解析要求のクエリーを読む方が、「プライバシーを大切にしています」を読むより直接だ。