忘却 RAM(ORAM)とアクセスパターン秘匿
データを暗号化してもどこを読んだかという足跡から中身が漏れます。この見落とされがちなアクセスパターン漏洩と、それを消す ORAM の仕組みを Path ORAM の木構造から原理で理解できます。
- 1.暗号化は中身(plaintext)を隠すが、どのアドレスをいつ読み書きしたかというアクセスパターンは隠さない。攻撃者はこの足跡だけで検索語・暗号鍵・データ構造を高確率で復元でき、暗号化済みクラウドストレージや TEE でも残る実害のある漏洩経路になる。
- 2.ORAM(Oblivious RAM)は、論理アクセスを毎回「読んで・再暗号化して・別の場所へ書き戻す」物理アクセス列へ変換し、どの論理アドレスを触ったかと物理アクセス列を統計的に独立にする。同じ番地を続けて読んでも観測される物理アクセスは毎回違って見える。
- 3.Path ORAM は二分木の各ノードを小箱(bucket)にし、ブロックを「あるリーフへの根からの経路(path)上のどこか」に配置する。アクセスごとに経路全体を読み、対象ブロックの割り当てリーフを乱択し直して経路へ書き戻す。1 アクセスあたり O(log N) 倍の帯域コストを払う。
解きたい問題:暗号化しても「どこを読んだか」が漏れる
データを暗号化すれば安全、という直感には大きな穴があります。暗号化が隠すのは各ブロックの中身(plaintext)だけで、どのアドレスを、いつ、どの順で読み書きしたかというアクセスパターンは隠しません。サーバーやクラウド事業者、あるいは同じ物理マシンに同居する攻撃者は、暗号文を一切復号できなくても、この物理アクセスの足跡を観測できます。
この足跡は驚くほど多くを語ります。暗号化された検索インデックスに対し「ある検索語でどのブロック群が触られるか」を観測し続ければ、頻度や共起の統計から検索語そのものを高確率で復元できます(access-pattern leakage 攻撃、暗号化検索の代表的破綻)。アルゴリズムの分岐や配列添字が秘密値に依存していれば、メモリアクセス列から秘密鍵が漏れます。これは サイドチャネル攻撃 のうちキャッシュ系・メモリ系が突くまさにその漏洩で、AES の T-table 実装が鍵を漏らした古典例もアクセスパターン依存が原因です。
厄介なのは、この漏洩が暗号化やハードウェア隔離では塞げない点です。TEE とセキュアエンクレーブ はメモリ内容を暗号化しますが、ページフォルトやキャッシュ経由でエンクレーブのアクセス列が外から観測可能で、controlled-channel 攻撃が成立します。**忘却 RAM(Oblivious RAM, ORAM)**は、この「アクセスパターンそのものを秘匿する」ことを正面から扱う技術です。
ORAM の安全性定義:アクセス列が中身から独立であること
ORAM の目標は厳密に定義できます。クライアントが行いたい論理アクセス列を (op1, op2, …, opM)(各 op は read(addr) または write(addr, data))とします。ORAM 方式が安全であるとは、任意の二つの同じ長さの論理アクセス列が、サーバーから見て計算量的に区別できない物理アクセス列を生むことです。
論理列 A = read(5), read(5), read(5) # 同じ番地を 3 回
論理列 B = read(2), write(7,x), read(99) # 全部バラバラ
⇒ ORAM 通過後、サーバーが観測する物理アクセス列の分布は
A 由来でも B 由来でも区別がつかない(statistically/computationally close)
ここから ORAM が満たすべき二条件が導けます。第一に、論理アドレスと物理アクセス位置の対応が毎回変わること(同じ番地を二度読んでも、観測される物理位置が前回と相関してはならない)。第二に、各操作が読みか書きかも漏らさないこと(読みでも必ず書き戻し、書きでも必ず読む)。
よくある誤解が「全ブロックを毎回再暗号化すれば足跡は消える」というものです。中身を確率的暗号で再暗号化しても、同じ物理位置を読みに行く限りアクセス位置の相関は残り、頻度解析で論理アドレスが特定されます。逆に位置だけシャッフルしても再暗号化しなければ、暗号文の一致からブロックの移動が追跡できます。ORAM は「アクセスのたびに対象ブロックを別の物理位置へ移し、かつ再暗号化する」両方を必須とします。
素朴な解と Square-Root ORAM
最も素朴な ORAM は毎回ストレージ全体を線形走査することです。どの 1 ブロックが欲しくても N ブロック全部を読めば、触った位置は常に全体で一定なので何も漏れません。しかし 1 アクセスに O(N) の帯域がかかり実用外です。ORAM 研究の歴史は、この O(N) をいかに下げるかの競争でした。
Goldreich と Ostrovsky による古典的な Square-Root ORAM は O(√N) 程度のコストに下げました。N ブロックに √N 個のダミーを足してランダム置換で並べ、√N 個の小さな**シェルター(stash)**を別に持ちます。アクセスのたびにシェルターを全走査し、目的ブロックがそこになければ本体の該当位置を、あれば未使用のダミー位置を読む(どちらに見えるかを揃える)。読んだブロックはシェルターに移し、√N 回ごとに全体を再シャッフルします。この「ダミーを混ぜて毎回違う位置を読み、定期的に再配置する」発想が、後続のすべての ORAM に受け継がれる核です。
Goldreich–Ostrovsky は、ORAM のアクセスあたりオーバーヘッドには Ω(log N) の下界があることも示しました(クライアント記憶が小さい一定の計算モデル下で)。つまり「アクセスパターンを完全に隠す」には、原理的に 1 アクセスにつき対数倍の物理アクセスを払わざるを得ません。後年 Larsen–Nielsen がより一般のモデルでもこの対数下界を裏づけました。ORAM が高コストなのは実装の未熟さではなく、問題の本質的な難しさです。
Path ORAM:二分木で対数コストを実現する
実務でデファクトとなったのが Path ORAM(Stefanov ら, 2013)です。単純で、対数コストの下界にほぼ届き、証明も明快です。構造は次の通りです。
- サーバー側ストレージを二分木にする。木の高さは約
log N、ノード数はN程度。各ノードは数ブロック(典型的にZ = 4)を入れられる**バケット(bucket)**で、空きはダミーブロックで埋める(暗号文上は本物と区別不能)。 - クライアントは小さな**ポジションマップ(position map)**を持つ。これは「論理ブロック
a→ どのリーフxに紐づくか」の対応表。 - クライアントは小さなスタッシュ(stash)(あふれた数ブロックを一時保持する領域)を持つ。
不変条件はただ一つ――論理ブロック a は、ポジションマップが指すリーフ x への、根からの経路(path)上のいずれかのバケット、またはスタッシュに必ず存在する。木の経路は根から一意なので、a を探すには「リーフ x への経路を根からたどる log N 個のバケットを全部読む」だけで足ります。
Path ORAM の 1 アクセス access(op, a):
1. x = PositionMap[a] # 現在 a が紐づくリーフ
2. PositionMap[a] = UniformRandom(0 .. L-1) # ★即座に新しいリーフへ乱択し直す
3. read_path(x) → スタッシュへ # リーフ x への経路上の全バケットを読み、復号して stash に取り込む
4. a を stash 内で見つけ、read なら値を返す / write なら値を更新
5. write_back(x): # 経路を根→葉の逆順で書き戻す
各バケットに、stash 内のブロックのうち
「新しい割り当てリーフへの経路がこのバケットを通る」ものを
なるべく深い位置へ詰め、残りはダミーで埋めて再暗号化
肝は 2 行目 です。アクセスのたびに対象ブロック a の割り当てリーフを一様乱数で引き直すため、次に a を触るときの経路は前回と無相関になります。同じ番地を 100 回読んでも、サーバーが見る経路は毎回独立にランダムなリーフへの経路で、どれも区別がつきません。これがアクセスパターン秘匿の数学的な根拠です。
| 要素 | 役割 | サイズの目安 | 保持場所 |
|---|---|---|---|
| 二分木バケット | 暗号化ブロックの実体を格納(高さ log N) | O(N) ブロック | サーバー(信頼不要) |
| ポジションマップ | 論理ブロック→割り当てリーフの対応 | O(N) エントリ | クライアント(小さいので再帰的に ORAM 化も可) |
| スタッシュ | 経路に書き戻せず溢れたブロックの待避 | O(log N) で十分(高確率) | クライアント(信頼領域) |
書き戻し(5 行目)では、各ブロックを「自分の新リーフへの経路と、いま書き戻している経路 x が共有する最も深いバケット」に押し込みます。根に近いバケットほど多くの経路が共有するため、押し込めなかったブロックは一時的にスタッシュへ残ります。Stefanov らはバケット容量 Z = 4 ならスタッシュ溢れ確率が log N に対して指数的に小さいことを証明しており、O(log N) サイズのスタッシュで実用上あふれません。
性能コストと PIR との関係
ORAM の代償は明確です。1 論理アクセスごとに log N 個のバケット(各 Z ブロック)を読み書きするため、帯域オーバーヘッドは概ね O(log N) 倍(定数込みで実測は数十倍に達することも多い)。さらにラウンドトリップが経路の深さ分だけ増え、レイテンシも悪化します。ポジションマップ自体が O(N) で大きい場合は、それを再帰的に別の小さな Path ORAM に格納する(recursive Path ORAM)ことでクライアント記憶を O(log N) まで圧縮できますが、その分アクセスが多段になりコストが乗ります。
ここで混同しやすいのが PIR(Private Information Retrieval) との違いです。両者とも「何を読んだかをサーバーに隠す」点は同じですが、前提とコスト構造が異なります。
| 観点 | ORAM | PIR(Private Information Retrieval) |
|---|---|---|
| 隠すもの | アクセスパターン(読み書きの位置と種類) | クエリのインデックス(どの項目を引いたか) |
| 書き込み | 対応する(read/write 両方を秘匿) | 基本は読み取り専用(書き込みは PIW など別枠) |
| データの持ち主 | クライアント自身のデータを預けて使う(外部ストレージ) | サーバー側の公開/共有 DB を私的に引く |
| 状態 | クライアントが状態(ポジションマップ・スタッシュ)を持つ | 多くはステートレス、サーバーが毎回全 DB を計算処理 |
| 主コスト | 帯域 O(log N) 倍・多ラウンド | サーバー計算 O(N)(全 DB を準同型演算で走査) |
ざっくり言えば、ORAM はクライアントの状態と帯域でアクセスを隠し、PIR はサーバーの計算(多くは準同型演算による全 DB 走査)でクエリを隠す、という重心の違いです。単一サーバー型の計算量 PIR は 準同型暗号と秘密計算(MPC) を土台にすることが多く、サーバー計算が O(N) 重い一方でラウンドが少ない。ORAM はサーバーは単純なストレージで済むがラウンドと帯域が嵩む。用途(自分のデータか共有 DB か、書き込みの要否、ボトルネックが帯域か計算か)で選び分けます。
ORAM はクラウド暗号化ストレージ、暗号化検索インデックス、そして TEE 内のメモリアクセス秘匿(oblivious data structures)で使われます。一方で帯域 O(log N) 倍は重く、全データを無条件に ORAM 化するのは非現実的です。実務では「アクセスパターンが本当に秘密を漏らす経路」だけを ORAM 化し、定数倍を詰めた Ring ORAM や、複数サーバー非結託を仮定して定数倍を大幅に下げる方式、TEE と組み合わせてラウンドを削る方式などを選びます。Ω(log N) 下界がある以上、「タダで隠す」方法は存在しないことを前提に設計します。
暗号化は中身を隠すがアクセスパターン(どのアドレスをいつ読み書きしたか)は隠さず、頻度・共起の統計から検索語や鍵が復元される。ORAM は論理アクセスを「読んで・再暗号化して・別位置へ書き戻す」物理列へ変換し、論理アドレスと物理アクセス列を統計的に独立にする方式。安全性は「同じ長さの任意の二つの論理アクセス列が区別不能な物理列を生む」ことで定義。素朴解は O(N)、Square-Root ORAM で O(√N)、Path ORAM は二分木+ポジションマップ+スタッシュで O(log N)。Path ORAM の核はアクセスごとに対象ブロックの割り当てリーフを一様乱択し直す点。Goldreich–Ostrovsky の Ω(log N) 下界により対数オーバーヘッドは原理的に不可避。PIR は「クエリのインデックス」を隠し、サーバー側 DB を私的に引く点で ORAM と前提・コスト構造が異なる(PIR はサーバー計算が重い)。
まとめ
暗号化はデータの中身を守りますが、どこを読んだかという足跡=アクセスパターンは無防備に残り、そこから検索語・鍵・データ構造が高確率で漏れます。これは サイドチャネル攻撃 の一種であり、TEE とセキュアエンクレーブ のようにメモリを暗号化しても塞げません。
ORAM はこの漏洩を正面から消す技術で、論理アクセスを「読み・再暗号化・別位置へ書き戻し」の物理列に変換し、論理アドレスと物理アクセスを統計的に独立にします。素朴な全走査の O(N) から、Square-Root ORAM の O(√N)、そして二分木とポジションマップ・スタッシュで O(log N) を実現する Path ORAM へと洗練され、その核心はアクセスのたびに対象ブロックの割り当てリーフを一様乱択し直すことにあります。Ω(log N) の下界がある以上、対数倍のコストは原理的に避けられません。クエリのインデックスを隠す PIR は、サーバー側 DB を私的に引くために重いサーバー計算を払う点で ORAM とは別物です。「見せずに使う」を別アプローチで追う技術として 準同型暗号と秘密計算(MPC)、統計公開での個人秘匿を担う 差分プライバシー と併せて押さえると、データを使いながら漏らさない技術群の見取り図が描けます。
セキュリティ Article
忘却 RAM(ORAM)とアクセスパターン秘匿を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
ORAM
比較で見る軸
難易度: advanced / カテゴリ: セキュリティ / タグ数: 5
導入後に効く点
ORAM(Oblivious RAM)は、論理アクセスを毎回「読んで・再暗号化して・別の場所へ書き戻す」物理アクセス列へ変換し、どの論理アドレスを触ったかと物理アクセス列を統計的に独立にする。同じ番地を続けて読んでも観測される物理アクセスは毎回違って見える。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- セキュリティ
- タグ数
- 5
判断チェックリスト
- 自社の用途が「ORAM / アクセスパターン」に近いか確認する。
- 強みである「暗号化は中身(plaintext)を隠すが、どのアドレスをいつ読み書きしたかというアクセスパターンは隠さない。攻撃者はこの足跡だけで検索語・暗号鍵・データ構造を高確率で復元でき、暗号化済みクラウドストレージや TEE でも残る実害のある漏洩経路になる。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。