はじめに:なぜプライバシー保護型URL検査が必要なのか
メッセンジャーでリンクをクリックするとき、そのリンクが安全かどうかをサーバーが検査しようとすると、「どのリンクをクリックしたか」をサーバーが知ることになります。エンドツーエンド暗号化(E2EE)で会話内容を保護していても、リンク自体がサーバーに露出すればプライバシーが損なわれるリスクがあります。
Meta(Facebook)のMessengerはこの問題を解決するために、Advanced Browsing Protection (ABP) 機能を導入しました。ABPはユーザーがクリックしたURLが悪意あるサイトかどうかを検査しながら、サーバーがそのURLの内容を知ることができないように設計されたシステムです。本記事では、ABPの核となる技術を一つひとつ解説します。
参考資料: 本記事はMeta Engineeringブログの How Advanced Browsing Protection Works in Messenger を基に分析しています。

ABPの技術スタック:PIR、OPRF、TEE、ORAMの協調
ABPは複数の暗号学的プリミティブとインフラ技術を階層的に組み合わせています。各技術の役割を見ていきましょう。
1. Private Information Retrieval (PIR) — クエリの出発点
PIRはクライアントがサーバーに「このデータはDBにありますか?」と問い合わせる際、サーバーが何を尋ねたのか全く分からないようにする技術です。理想的なPIRはサーバーがDB全体をクライアントに送れば解決しますが、ABPのDBは数百万件の悪意あるURLを含み頻繁に更新されるため、現実的ではありません。
2. Oblivious Pseudorandom Function (OPRF) — 完全一致ではないマッチングの問題
ABPが最初に検討したアプローチは、OPRFを使ってDBを複数のバケットに分割する方法でした。しかしOPRFは完全一致(exact match) クエリに最適化されています。URL検査では、'example.com'がDBにあるときに'example.com/a/b/index.html'もマッチする必要があるプレフィックスマッチングが求められます。
この問題を解決するため、クライアントがURLのすべてのパスプレフィックスに対してPIRクエリを並列に送る方法が考えられます。しかしこれでは、サーバーがクライアントの実際のURLに関する情報をより多く知ることになります(プレフィックス数だけ情報漏洩)。
3. Rulesetによるバケットバランシング
この問題を解決するためにMetaは、Rulesetと呼ばれる前処理ステップを導入しました。サーバーはDBを定期的に分析し、URLをハッシュする際にドメインだけでなく特定のパスセグメントを追加で含めるルールを生成します。
# Rulesetの例(擬似コード)
ruleset = [
{"hash_prefix": "08bd4dd11758b503", "path_segments": 2},
{"hash_prefix": "fe891588d205cf7f", "path_segments": 1},
{"hash_prefix": "c078e5ff2e262830", "path_segments": 4},
]
def get_bucket_id(url, ruleset):
"""Rulesetを適用してURLのバケットIDを計算"""
current_hash = hash(url.domain)
while True:
rule = find_matching_rule(current_hash, ruleset)
if rule is None:
break
# ルールに従ってパスセグメントを追加して再ハッシュ
current_hash = hash(url.domain + "/" + get_path_segments(url, rule.path_segments))
return current_hash[:2] # 最初の2バイトをバケットIDとして使用
この方式により、サーバーはバケットサイズを均一に保ちながら、クライアントが送信するバケットIDがURLの完全なパスを露出しないようにします。
4. Confidential Computing (TEE) — ハードウェアレベルの隔離
バケットIDさえもサーバー運営者に露出するのを防ぐため、MetaはAMD SEV-SNPベースの信頼実行環境(Confidential Virtual Machine, CVM)を使用します。クライアントはCVMのアテステーションレポートを検証した後、安全なチャネルを介してバケットIDをCVM内部に送信します。CVM外部のプロセスはこのIDを見ることができません。
5. Oblivious RAM (ORAM) — メモリアクセスパターンの隠蔽
TEEがメモリを暗号化しても、メモリアクセスパターンそのものは観察可能です。例えばサーバーが常にバケット5番だけを読んでいれば、攻撃者はどのバケットが検索されたかを推測できます。これを防ぐため、ABPはPath ORAMアルゴリズムを使用して、すべてのリクエストで全てのバケットを読んでいるように見せかけます。
# ORAMによる線形スキャンシミュレーション(概念コード)
class OramBucketAccess:
def __init__(self, num_buckets):
self.num_buckets = num_buckets
self.buckets = [None] * num_buckets
def read_bucket_privately(self, target_index):
"""
すべてのバケットを読むが、実際にはtarget_indexのみ返す。
メモリアクセスパターンを隠すためにPath ORAMを使用。
"""
# 実際にはPath ORAMツリーを探索するが、
# 概念的には以下のように全てのバケットを走査
result = None
for i in range(self.num_buckets):
data = self._access_memory(i)
if i == target_index:
result = data
return result
def _access_memory(self, index):
# ハードウェアメモリアクセス(実際には暗号化された状態)
return self.buckets[index]
6. Oblivious HTTP (OHTTP) — IPアドレスの隠蔽
最後に、クライアントのIPアドレスなどの識別情報をサーバーから隠すため、ABPはOblivious HTTPプロキシを使用します。クライアントはリクエストを暗号化して第三者プロキシに送り、プロキシは識別情報を除去した後サーバーに転送します。サーバーは誰がリクエストしたのかを知ることができません。

ABPの限界と注意点
ABPは非常に精巧なシステムですが、いくつかの重要な限界があります。
-
パフォーマンスオーバーヘッド: 全てのバケットをスキャンするORAMアクセスは計算コストが大きい。MetaはDBサイズがまだ十分小さいため、複数のコピーをメモリに載せて並列処理することでこの問題を緩和しています。
-
完全なプライバシーではない: OHTTPプロキシは第三者であるため、プロキシとサーバーが共謀すればプライバシーが破られる可能性があります。またTEEの証明チェーンに対する信頼が必要です。
-
更新の遅延: RulesetとDBバケットは定期的に更新されるため、新しい悪意あるURLが登録されるまでタイムラグが生じることがあります。
-
国内(日本)環境への適用: LINEやその他のメッセンジャーが同様の機能を導入する場合、TEEインフラ構築コストやOHTTPプロキシの法的・規制的課題(個人情報保護法、電気通信事業法)を考慮する必要があります。特にOHTTPプロキシを国内に置く場合、プロキシ運用者の信頼性とデータ保護措置が重要です。

まとめ:プライバシーとセキュリティのトレードオフ、そして未来
MetaのABPは、暗号学とシステムセキュリティの最先端技術を総合し、ユーザープライバシーを侵害せずに悪意あるリンクを検出する実用的なソリューションを提供します。PIR、OPRF、TEE、ORAM、OHTTPそれぞれの技術は完全ではありませんが、それらを組み合わせることで単一技術の限界を克服しています。
次のステップとしての学習方向
- PIR/OPRFの深堀り: Cryptographic Private Information Retrieval (Wikipedia) を読んでみましょう。
- TEEの実践: AWS Nitro EnclavesやAMD SEV-SNPを活用した簡単なアプリケーションを実際にデプロイしてみてください。
- ORAMの実装: Metaが公開している Path ORAMライブラリ をチェックしてみましょう。
合わせて読みたい記事