先に結論です。# の後ろが鍵に向くのは、ブラウザが HTTP を組み立てるときにフラグメントを落とすからです。新しい暗号プロトコルが増えたわけではありません。確認方法は、アドレスバーと Network のリクエスト行を照合することです。完全なリンクをチャット、チケット、ブラウザ履歴に置くと、文字列を見た人に対して、この層はもう成り立ちません。
検索意図に一文で答える
URL の # 以降に鍵を置けるのは、RFC 9110 が、リクエストの対象 URI にフラグメントを含めないと定めているからです。ブラウザがサーバーへ文書を取りに行くとき、# とその後ろを落とします。ホスト、CDN、アクセスログが見るのは、パスと疑問符の後ろのクエリです。# 以降の鍵は、設計どおり見えません。
この記事は開発者、運用、そして一度きりのリンクを同僚へ送る前に、「サーバーからは見えない」と「チャット履歴からは見えない」を分けたい人向けです。ワンタイムリンク の操作説明の代わりにはならず、「リンクの作り方」も繰り返しません。持ち帰るべきなのは、疑問符とハッシュがそれぞれどの層に入るか、Network での照合の仕方、そして URL 全体を転送したときに消える層です。
疑問符は HTTP に入る。# はデフォルトでタブに残る
一本のアドレスに、クエリとフラグメントを同時に置けます。形は https://example.com/s.html?id=abc#key です。疑問符の後ろの id=abc が query、# の後ろの key が fragment です。ブラウザが文字列を読むとき、フラグメントは最初の # から末尾までです。それより前の部品にリテラルのハッシュを書きたいときは %23 にします。そうしないと、解析が途中で切れます。
文書リクエストを出すとき、ブラウザが組み立てるのは Host、パス、クエリです。MDN の URI フラグメント の説明は率直です。資源を取りに行くとき、フラグメントはサーバーへ送られず、文書が届いたあとクライアントが処理します。HTML では、よくある使い方はある id までスクロールすることです。一度読んで消えるページでは、同じ場所を借りて、このタブが復号に使う鍵を載せます。
したがって「サーバーは暗号文だけを持つ」が成り立つ条件は三つです。探す番号は query に置く。復号鍵は fragment に置く。ページのスクリプトは location.hash を後続リクエストに書き戻さない。一つでも欠けると、ハッシュは飾りです。
仕様が書くのは既定動作であり、追加の暗号化ではない
RFC 3986 §3.5 は、フラグメントを「主資源の上にある副次資源」の識別子とし、文書型に応じてクライアントが解釈すると定義します。RFC 9110 §7.1 は続けて、対象 URI は参照内のフラグメントを除くと書きます。フラグメント識別子はクライアント側の処理に残すからです。二文書を重ねると、意味は一つです。# の後ろは HTTP のリクエスト対象の一部ではありません。
アクセスログに # が出ない理由も、ここにあります。運用が欄を隠したのではありません。リクエスト行に、もともとこの区間がないのです。「ユーザーがどの見出しをクリックしたか」をサーバーログで数えようとすると、同じ理由で空振りします。同じログで「鍵はサーバーに入っていない」を示すなら、仕様と一致します。
Referer も同じ原則です。MDN の Referer は、このヘッダがオリジン、パス、クエリを持てると書き、URL フラグメントとユーザー名パスワードは含めてはならないと書いています。Referrer Policy は、URL を Referer に収める前にフラグメントを空にします。したがって閲覧ページが画像やスクリプトを取りに行っても、デフォルトでは # 以降の鍵は第三者ホストへ渡りません。ブラウザの既定の切り離しであり、このサイトが発明したプロトコルではありません。
ページのスクリプトは、フラグメントを読めます。MDN の URL.hash は # とフラグメント本体です。今のページなら location.hash も使えます。閲覧ページは、このインターフェースで鍵を取り、Web Crypto で AES-256-GCM の復号を呼びます。同じインターフェースは誤用もできます。スクリプトは hash を query に繋いだり、解析報告へ書いたりできます。仕様が止めるのは「ブラウザが既定で出すリクエスト行」です。「ページが読んでから送る」は止めません。
本物のパスワード、API キー、マスキングしていない接続文字列で実験しないでください。この回だけ使うテスト文を用意してください。確認するのはリクエスト行と本文であり、資格情報をもう一度晒すことではありません。
Network でアドレスバーとリクエスト行を照合する
スローガンは証拠になりません。その場で終わる確認は、開発者ツールの Network を開き、アドレスバーの全体と各リクエストの URL を並べることです。前回のノートはカナリアで平文を探しました。今回見るのはフラグメントだけです。# の後ろはアドレスバーにあり、リクエスト行には出てはいけません。
- 閲覧ページを開き、アドレスバーが
s.html?id={id}#{key}の形であることを確認します。query にあるのは番号だけ、鍵は#の後ろだけです。 - Network を開き、Preserve log をオンにし、フィルタはまず All にします。文書リクエスト、後続の API、アクセス解析を、先に隠さないでください。
#の後ろの鍵をカナリアにして、フィルタに貼ります。赤いヒットが出たら止まって、そのリクエストを読んでください。実装がフラグメントを query と取り違えたか、スクリプトが読んでから送ったかです。- ヒットがなければ、文書リクエストと
/secretsのような API を一つずつ開き、リクエスト行にidだけがあるか、本文に原文がないかを照合します。
作成側でも一度できます。この端末で AES-256-GCM により暗号化したあと、出域の欄は設計どおり暗号文、有効期限、閲覧回数だけです。乱数鍵は、そのあと組み立てる共有リンクにだけ現れ、POST の JSON には出てはいけません。FastPwd のワンタイムリンクはこの境界で動きます。作成も閲覧も開いてすぐ使え、アカウントは不要です。約束は、あくまで約束です。信じるべきなのは、今回 Network で見えたものです。
%23 と本物の # を分けてください。リクエスト行に %23 の後ろの文字が出たら、その区間はパスかクエリとして送られており、サーバーから見えます。フラグメントが始まるのは、エンコードされていない # だけです。鍵を ?key= や /%23key と書くと、「ハッシュに隠した」ように見えても、ログにとってはすでに平文のクエリです。
ページのスクリプトと解析報告は、まだ hash を読める
既定の HTTP リクエストにフラグメントがないことは、「このタブのどのスクリプトもそれを見ない」ではありません。今の文書の JavaScript は location.hash を持てます。閲覧ページは、この端末で復号するためにそれを読む必要があります。解析スクリプトが location.href 全体を報告すると、鍵は統計リクエストの query か body に出ます。
したがってカナリア検索は、業務 API だけでは足りません。アクセス解析のパスを別に絞り、query と body を開いてください。ページタイトルとパスは出てかまいません。# 以降の鍵、診断したパスワード、洗浄前の原文は出てはいけません。FastPwd のアクセス解析はサイト自身の tracker を使い、localhost ではデフォルトで送りません。それでも同じカナリアで、報告にフラグメント鍵がないことを確認してください。
拡張機能、企業の DLP、一部の「AI ブラウザ」も、アドレスバー全文を読めます。それらは、いま見ている HTTP のリクエスト行を通りません。Network パネルは、それらが読んでいない証明にはなりません。同僚に渡せる文は、ここまでです。「リクエスト行と報告を検索した。# 以降の鍵はなかった。」これを「このコンピュータのどのソフトも、この hash を見ていない」へ引き上げないでください。
この層の保護が崩れる場面
フラグメントが止めるのは、「この文書リクエストが鍵をホストへ渡した」です。次の場面では、URL 全体を見た人はすでに鍵を持っています。仕様は助けになりません。
第一に、完全なリンクをチャット、メール、チケットに貼ることです。メッセージ本文の文字列に、# の後ろが含まれます。相手のクライアント、部屋の履歴、チケット検索、将来の書き出しは、いずれも全体を見られます。サーバーへは、鍵が HTTP に入っていないことがあります。チャット履歴を開ける人に対しては、保護はすでにありません。完全な URL は、持っている人が使える資格情報です。
第二に、ブラウザ履歴と同期です。アドレスバーに出た # フラグメントは、通常ローカル履歴に入ります。同期をオンにしたブラウザは、同じ記録を別の端末へ送ることがあります。タブを閉じても、履歴は消えません。
第三に、リダイレクトがフラグメントを連れて行くことがあります。行き先の Location にフラグメントがなければ、多くのブラウザは元のフラグメントを新しいアドレスの後ろへ付けます。鍵は、「もう安全なページへ跳んだ」と思ったアドレスバーに残ります。閲覧ページが解析ドメインやログインドメインへ飛ばされるなら、先に Network で最終リクエスト行を見、次にアドレスバーに何が残っているかを見てください。
第四に、リンクプレビューとセキュリティスキャンは、「# より前」を取りに行きます。チャットアプリがプレビューを作るとき、仕様どおりならフラグメントをプレビュー先ホストへ送りません。ただし s.html?id=… は取りに行きます。実装が、文書の最初の GET か暗号文の最初の GET で削除するなら、プレビュー用のボットが受信側より先に回数を使い切ることがあります。受信側が開くと「削除済み」だけが見えます。鍵はプレビュー先ホストへ渡っていません。燃えたのは閲覧回数であり、フラグメントの秘匿性ではありません。
FastPwd の閲覧ページは、この二段を分けています。ページを開いたときは、暗号文がまだあるかだけを尋ね、暗号文は取りません。受信側が「開いて見る」を押したあと、番号で暗号文を取り、# の後ろの鍵でこの端末で復号します。作成ページも閲覧ページも、アカウントは不要です。プレビュー用のボットは、通常ランディングページだけを取り、ボタンは押しません。「プレビュー即削除」の確率は下がります。すべてのスキャナがランディングで止まる保証にはなりません。回数はサーバー側のカウンタであり、フラグメントの一部ではありません。
| 誰が見ているか | # 以降の鍵は見えるか | この層はまだ成り立つか |
|---|---|---|
| 文書リクエストのリクエスト行 | デフォルトでは見えない | 成り立つ。Network で照合する |
| ホストのアクセスログ / CDN | デフォルトでは見えない | 成り立つ。前提は鍵が query に入っていないこと |
| 第三者へ送る Referer | デフォルトでは見えない | 成り立つ。仕様がフラグメントを落とす |
| ページのスクリプト / 解析報告 | location.hash を読める |
実装次第。カナリアで body を検索する |
| チャット、メール、チケット本文 | 文字列全体が見える | 転送先に対しては成り立たない |
| ブラウザ履歴 / 同期 | 通常見える | このブラウザを開ける人に対しては成り立たない |
| リンクプレビューのボット | 仕様どおりならフラグメントは取れない | 閲覧回数を消費することがある |
証明できること、できないこと
この点検が支えられる結論は狭いです。支えられること:使っているこのブラウザ、この回のオープンでは、文書リクエストと観察できた API のリクエスト行に # 以降の鍵はなく、作成リクエストの body に原文はなかった。
支えられないこと:グループの管理者が完全なリンクを保存していない。拡張機能がアドレスバーを読んでいない。相手が開いたあと画面を撮らない。暗号文が取られたあと、別のバックアップに再び出ない。一度読んで消える仕組みが消すのは、サーバー上の暗号文です。画面に復号済みの平文でも、チャット履歴の URL 全体でもありません。
フラグメントを「受信側だけが開ける」とも読まないでください。?id=…#鍵 の全体を持った人は、公開の閲覧ページで復号できます。閲覧ページにログイン障壁はありません。これが設計です。相手は先に登録しなくてよい。代償は、文字列全体を資格情報として扱うことであり、「普通に転送してよい文書リンク」として扱わないことです。
よくある質問
URLの#以降はサーバーに送られますか?
デフォルトでは送られません。RFC 9110 は、リクエストの対象 URI にフラグメントを含めないと書いています。ブラウザが文書を取りに行くとき、# とその後ろを落とします。サーバー、リバースプロキシ、アクセスログが見るのは、パスと疑問符の後ろのクエリだけです。リクエスト行に # 以降の鍵が出たら、実装の誤りであり、仕様の例外ではありません。
復号鍵を疑問符の後ろに置いてはいけない理由は?
疑問符の後ろのクエリは HTTP のリクエスト行に書かれます。ホスト、CDN、アクセスログから見えます。鍵が query に入った時点で、「サーバーは暗号文だけを持つ」はログの層ですでに成り立ちません。番号は query に置けます。暗号文を取るために、もともと必要だからです。鍵は置けません。
# を含む完全なリンクをチャットに貼っても安全ですか?
サーバーは、その HTTP では鍵を見ません。チャット履歴、チケット、ブラウザ履歴は見ます。完全な URL は、持っている人が使える資格情報です。文字列全体を持った人は、閲覧ページで復号できます。隔離を厚くするなら、「id を含むリンク」と「# の後ろの鍵」を別の経路で渡し、同じ部屋へ全体を投げないでください。
ワンタイムリンクの閲覧ページを開くのにログインは必要ですか?
不要です。閲覧ページは受信側に公開されています。疑問符の id で暗号文を取り、# の後ろの鍵でこの端末で復号します。FastPwd にアカウントもパスワード保存機能もありません。作成も閲覧も開いてすぐ使えます。ログイン障壁はフラグメントの境界を直しません。この層の点検にも書き込まないでください。
捨ててよいテストリンクで、その場で照合する
query と fragment を分けたページで練習するなら、FastPwd のワンタイムリンクから始めてください。開いてすぐ使え、登録は不要です。捨ててよいテスト文を書き、この端末で暗号化したリンクを作ります。作成リクエストに見えるべきなのは、暗号文と回数、有効期限のような欄です。共有リンクこそ s.html?id={id}#{key} です。
この文字列で閲覧ページを開きます。アドレスバーには疑問符とハッシュの両方があるはずです。Network では、文書リクエストと状態照会が運ぶのは id だけです。「開いて見る」のあと、暗号文を取るリクエストにも # 以降の鍵は出てはいけません。平文は、この端末で復号したあとのページにだけ出るはずです。アルゴリズムは AES-256-GCM、計算は Web Crypto です。テキスト一件の上限は 32 KB です。ファイル全体は ファイル暗号化 を使い、単ファイルは 5 GB まで、出力は .lock / .enc です。
これらの練習の目的は、あるサイトが「絶対に安全」だと証明することではありません。二層の事実を分けることです。第一層:ホストは仕様どおりフラグメントを見ません。第二層:完全なリンクを見た人は、鍵を持っています。「鍵は # の後ろ」とうたうどのページでも、手順は同じです。アドレスバーとリクエスト行を照合し、解析報告を別に見、最後にこの URL がチャット履歴へ入るかを自分に聞いてください。
次に送る前に、三つ覚えてください。第一に、番号は query に入れてよい。鍵は hash にしか入れない。第二に、リクエスト行がきれいなことは、サーバーが鍵を受け取っていない、という意味だけです。第三に、完全なリンクは資格情報として扱い、普通の文書リンクとして転送しない。繰り返せる観察だけが、自分たちの引き継ぎ説明に書く価値があります。
関連記事
前回のノートは、「平文がこのタブを出たか」を繰り返せる Network 点検に落としています。本文と並べて読むとよいです。ブラウザのローカル暗号化で、平文が端末を出ていないことを確認する。本稿は疑問符とハッシュを分け、完全なリンクを転送したときに消える層だけを書きます。