はじめに
「HSTS Preload list に急いで申請しなかった理由」 の記事を書いている途中、ふと疑問が湧きました。
「世界中の HTTPS サイトをリストとしてハードコードすると、ブラウザは重くならないのだろうか?」
私の素朴な疑問でした。 そう考えると、確かに気になります。
- HSTS Preload list には現在 約 12 万ドメイン が登録されている (ローカルでもダウンロードできる公開リストです)
- それが Chrome / Firefox / Safari / Edge の すべてに hardcode されている
- ブラウザを起動するたびにこのリストが読み込まれているとしたら、メモリも消費するし、毎回 URL を開くたびに 12 万件と照合するなら、画面が表示されるまで時間がかかるはず...
直感的にはそう感じます。 でも、ブラウザはサクサク動いている。何が起きているのか。
調べてみたら、ブラウザベンダーの最適化が想像以上に賢くて、同時に 「重くなる問題」よりも深刻な別の問題 が浮き上がってきました。
本記事はその発見の記録です。
現状のリスト規模
Chromium のリポジトリ で実際の JSON ファイルが公開されています。サイズは数 MB、エントリ数は約 12 万件。これが Chromium ビルドの一部として生成・配布されています。
Firefox も同じリストを取り込んで、独自の format で生成済みのソースファイルに変換し、バイナリに組み込みます。Safari / Edge も Chromium ベースなので、結果として 世界のほぼ全ブラウザが同じ 12 万ドメインのリスト を抱えていることになります。
ここで湧くのが冒頭の疑問です。
「これ、ブラウザ重くないんですか?」
「12 万件を毎回引く」という想像
ナイーブに実装したら、確かに重そうです。
ユーザーが https://example.com にアクセスしようとする
↓
ブラウザ: 「example.com は preload list にあるか?」
↓
リストを 1 件ずつ調べる
↓
12 万件のループ完了 (遅い)
↓
ようやくページ読み込みへ
これを各 URL アクセスのたびに繰り返したら、Chrome が秒間 100 URL を処理するシーンでは、毎秒 1,200 万件の比較が走ることになります。
実装上、完全にダメです。
ブラウザベンダーの実装 — perfect hash function
実際の Chromium の実装は、ビルド時に JSON を「perfect hash function」という仕組みに変換します。
ナイーブな説明をすると、こういう仕組みです。
- 12 万件のドメイン名を入力に、衝突しないハッシュテーブル を事前に計算する
- ハッシュ関数の係数 / バケットサイズを、12 万件の入力に対して衝突が起きないように選ぶ (これがビルド時に計算済み)
- ブラウザ実行時には、入力ドメインを 1 回ハッシュして、計算結果のテーブルを 1 回引くだけ
結果として、12 万件のリストへの問い合わせは「毎回およそ数ナノ秒」で終わります。リストが 12 万から 100 万に増えても、ほぼ計算時間は変わりません。
メモリ的には、リスト全体で 1〜2 MB。Chrome のメモリ使用量が数百 MB から GB レベルなので、誤差です。
Firefox は同じ目的を別のアプローチ (bloom filter + secondary lookup) で達成しています。手法は違っても結論は同じ: 「重くなる問題」は実装で解決済み。
perfect hash function が「賢い」と感じるポイント
少し脱線しますが、perfect hash function は、コンピュータサイエンスの中でも特にエレガントな構造の 1 つです。
通常のハッシュテーブルは「衝突が起きた時の対処」を含めて設計します (chaining, open addressing 等)。一方 perfect hash function は、事前に「衝突が起きないハッシュ関数の係数」を計算で求める ことで、衝突対処自体を不要にします。
これが可能なのは、入力の集合 (= 12 万件のドメイン名) が 事前に確定している からです。動的に増減するハッシュテーブルでは perfect hash は使えませんが、ブラウザのリリースバイナリに焼き込まれる「ビルド時に確定するリスト」では、まさに ideal な活用先です。
12 万件の入力からこのような係数を計算で求めること自体は重い処理ですが、それは Chromium のビルド時に 1 回やれば良い だけで、ユーザーのブラウザでは「結果のテーブル」だけが走ります。
設計の世界に「実行時のコストをビルド時に押し付ける」というイディオムがありますが、これはその典型例です。
それでも残る別の問題
「重くならない」ことは分かりました。じゃあ、preload list は気軽に申請できるのか? と聞かれると、そうでもありません。
調べていくうちに、ブラウザベンダーが懸念しているのは「重さ」ではなく、別の問題でした。
1. 取り消しが遅い
Preload list に入ったドメインを 取り消す には、各ブラウザの release cycle を経る必要があります。Chromium が次の安定版をリリースするまで 4〜6 週、その後 Firefox / Safari / Edge が取り込むまで、合計で 3〜6 ヶ月 ユーザーの手元に残ります。
「誤って申請してしまった」「サブドメインを HTTPS 化したくない事情ができた」のような場面で、半年戻せない。これは小さくない不利益です。
2. wildcardize できない
「*.example.com を一括登録」ができないため、サブドメインを増やすたびに preload 申請が必要になります。リストが肥大化する一因です。
3. リスト全体の管理コスト
Chromium / Mozilla の人間レビュアーが申請を 1 件ずつ審査しています。リストが肥大化するほど、この負担は増えます。最近は申請審査が 厳格化 していると言われています (Google Online Security Blog や hstspreload.org の guidance を見ると、近年「気軽な申請」を抑制する文面が増えています)。
つまり、ブラウザベンダーの懸念は「ユーザー側のリソース」ではなく「運用上のリスク管理」と「自分たちのレビュー負荷」でした。
「素朴な疑問」が大事だった
最初の「ブラウザ重くなりませんか?」は、答えが No であっても、その答えに至るまでに見えてきた風景 が価値でした。
- perfect hash という美しい仕組みが、現実の問題を解決している
- それでも別の問題 (取り消しの困難さ) が残っていて、それが niyase の慎重な判断 (前記事 参照) の根拠になっている
- ブラウザベンダーが「リスト肥大化」を懸念して申請審査を厳格化している現状
素朴な疑問は、調べる前は「初心者っぽい質問」に見えることがあります。 調べてみると、その業界の最深部の課題に直結していることが多い、というのが、毎回学びです。
おわりに
niyase は、こうした 「業界の現実を、調べた範囲で正直に伝える」 記事を書き続けたいと思っています。技術解説のように見えるかもしれませんが、本質は「わからないことを わからないまま放置しない」という姿勢です。
経営判断 (preload 申請の保留) も、技術的調査 (perfect hash の理解) も、同じ思考のスタンスから生まれています。
私たちが ISO 27001 / SOC 2 Type II (認証取得を準備中) を取りに行く理由も、同じです。「強い設定にしているから OK」ではなく、「なぜその設定にしているか、その判断のプロセスを言語化できる」ことを証明したい。
セキュリティの世界は、強い設定が美しいだけではありません。 強い設定を持ち、それを言語化できる組織 が、本当に強い組織です。