算術の影を暴く:タイミング攻撃によるRSA秘密鍵の特定と、定数時間実装の防衛哲学
暗号アルゴリズムの数学的安全性は、もはや疑う余地がない。RSAの素因数分解困難性や、楕円曲線暗号(ECC)の離散対数問題は、現代の計算機のパワーをもってしても正面突破は不可能だ。私たちは長年、「正しい鍵長(たとえばRSA 4096bitやAES-256)を使い、適切に乱数を生成していれば破られない」という神話を信じ続けてきた。
しかし、セキュリティの現場に身を置く人間なら知っているはずだ。数学的に堅牢なアルゴリズムも、それを動かす物理的なシリコンの挙動、すなわち「実装の隙」をつくことで容易に崩壊するということを。
今回は、数あるサイドチャネル攻撃(Side-Channel Attack: SCA)の中でも、最もエレガントかつ凶悪な手法の一つである「タイミング攻撃(Timing Attack)」を取り上げる。特に、RSAのコアであるべき乗剰余演算の処理時間差から秘密鍵が丸裸にされるメカニズムと、それを完全に封じ込めるための低レイヤにおける防衛アーキテクチャについて、現場の知見を交えて深く掘り下げていこう。
—
1. 脆弱性の根源:なぜ「計算時間」が鍵を漏らすのか
サイバー攻撃者は、ネットワークのパケットやAPIのレスポンスコードだけでなく、暗号処理が消費する「時間」「電力」「電磁波」「音」といった物理的副産物(サイドチャネル)を観測している。中でもタイミング攻撃は、特別なハードウェアを必要とせず、リモートからでもミリ秒単位、あるいはマイクロ秒単位の処理時間差を統計的に処理することで秘密情報を暴き出す。
RSAべき乗剰余演算のアルゴリズム的欠陥
RSAの復号や署名生成では、暗号文 $C$ に対し、秘密鍵 $d$ を用いて以下のべき乗剰余演算を行う。
$$M = C^d \pmod N$$
このべき乗計算(Modular Exponentiation)を素朴に実装すると、以下のような「右向きバイナリ法(Square-and-Multiply法)」になる。
// 脆弱な素朴なべき乗剰余演算の概念実装
bignum_t mod_exp(bignum_t base, bignum_t exp, bignum_t mod) {
bignum_t result = 1;
int bit_length = get_bit_length(exp);
for (int i = 0; i < bit_length; i++) {
// 鍵のビットが '1' の場合のみ、高負荷な乗算(Multiply)を実行する
if (get_bit(exp, i) == 1) {
result = mod_mul(result, base, mod);
}
// すべてのループで必ず二乗(Square)を実行する
base = mod_mul(base, base, mod);
}
条件分岐(if文)や乗算の有無により、CPUの処理時間やキャッシュヒット率が変動する
return result;
}
ここに決定的な脆弱性が潜んでいる。秘密鍵 $d$ のバイナリ表現において、あるビットが 1 であるか 0 であるかによって、ループ内の処理パス(乗算を行うか否か)が分岐する。大容量のビッグナンバー(多倍長整数)に対する乗算処理は、CPUサイクルを大量に消費するため、「1 が多いビット列の処理には時間がかかり、0 が多いビット列では処理が早い」という明確な時間差が生まれる。
ネットワーク経由の観測と統計的処理
「ネットワークのジッター(揺らぎ)がある環境で、そんなミリ秒単位の時間差など測定できるのか?」という疑問を持つアーキテクトも多いだろう。
答えは「ノー」であり「イエス」だ。単発のリクエストでは、パケット遅延のノイズにかき消されて秘密鍵の特定は不可能だ。しかし、攻撃者は数百万回から数千万回に及ぶ暗号化・復号リクエストを対象サーバーに送り続け、得られたレスポンスタイムを統計的に処理(平均化・ノイズ除去)する。パケットの揺らぎがランダムノイズであるのに対し、アルゴリズムに起因する時間差はシステマティックなバイアスとして現れるため、十分なサンプル数が集まれば、ノイズの壁を突き破って秘密鍵のビットを上から順に特定できてしまうのだ。
—
2. 攻撃者の視点:いかにして鍵の全貌を復元するか
タイミング攻撃は、一気に秘密鍵全体を当てるわけではない。上位ビットから下位ビットへ、まるでパズルのピースを埋めるように段階的に特定していく。
1. 初期プロービング: サーバーに対して無数の署名リクエストを送信し、それぞれのレスポンスタイムをミリ秒(あるいはサブミリ秒)単位で計測・記録する。
2. 統計分析: 測定データを秘密鍵の候補ビットごとにグループ分けし、平均処理時間の相関を解析する。
3. ビットの確定: 「このタイミングの揺らぎは、明らかにビットが 1 の場合の乗算処理に起因している」と判定し、そのビットを確定させる。
4. ループ: 確定したビットを前提に、次の下位ビットの推測へ移行する。これを繰り返すことで、最終的に秘密鍵 $d$ (あるいは中国剰余定理(CRT)を使用している場合は素因数 $p, q$ に関連するパラメータ)の全ビットが逆算される。
インフラストラクチャ側でWAFを導入していこうとも、アプリケーションレイヤや暗号ライブラリの処理時間が外部から観測可能である限り、この数学的・物理的侵略を防ぐことはできない。根本的な対策は、コードそのものの構造を書き換えることにある。
—
3. 防衛アーキテクチャ:定数時間アルゴリズム(Constant-Time Algorithm)の実装
タイミング攻撃を無力化するための唯一にして絶対の解法が、「定数時間アルゴリズム(Constant-Time Implementation)」の採用である。
これは、「秘密鍵の値がどのようなものであっても、実行されるCPU命令のフローやメモリアクセスのパターンが完全に一意(一定の時間)であること」を保証する設計手法だ。
1. Montgomery Ladder(モンゴメリー・ラダー)による実装
べき乗剰余演算において、条件分岐による処理時間のバラつきを排除する代表的なアルゴリズムがモンゴメリー・ラダー法である。鍵のビットが 0 であろうが 1 であろうが、常に「ダミーの演算」を含めた全く同じ演算ステップを強制する。
以下に、タイミング攻撃耐性を持つべき乗剰余演算の概念的なC言語実装を示す。実務においてはOpenSSLやLibgcryptなどの信頼されたハードニング済みライブラリを使用すべきだが、内部で何が行われているかを知ることはアーキテクトにとって必須の教養だ。
#include <stdio.h>
#include <stdint.h>
// 多倍長整数の型定義(概念的なサンプル)
typedef uint64_t bignum_t;
// ダミーの乗算(条件分岐を使わず、常に計算を実行して結果を捨てることでタイミングを一定にする)
void constant_time_select(bignum_t *dest, const bignum_t *src0, const bignum_t *src1, int condition) {
// condition は 0 または 1。分岐命令(if)を使わず、ビット演算マスクで選択する
bignum_t mask = -((bignum_t)condition); // 1なら 0xFFFFFFFFFFFFFFFF, 0なら 0x0000000000000000
dest[0] = (src0[0] & ~mask) | (src1[0] & mask);
}
// 定数時間べき乗剰余演算のフレームワーク
bignum_t constant_time_mod_exp(bignum_t base, bignum_t exp, bignum_t mod, int bit_length) {
bignum_t R[2];
R[0] = 1; // R0初期値
R[1] = base; // R1初期値
for (int i = bit_length - 1; i >= 0; i--) {
int bit = (exp >> i) & 1;
// 鍵のビット値に関わらず、必ず両方のレジスタを更新する(Montgomery Ladderの原理)
// これにより、CPUの実行パスやキャッシュアクセスの偏りを完全に排除する
if (bit == 0) {
R[1] = mod_mul(R[0], R[1], mod);
R[0] = mod_mul(R[0], R[0], mod);
} else {
R[0] = mod_mul(R[0], R[1], mod);
R[1] = mod_mul(R[1], R[1], mod);
}
}
return R[0];
}
2. キャッシュ攻撃対策としてのメモリ管理
定数時間アルゴリズムを実装したとしても、CPUのL1/L2キャッシュのヒット・ミスの差を利用した「キャッシュ攻撃(Cache-based Side-Channel Attack)」に足元をすくわれるケースがある。たとえば、ルックアップテーブル(S-boxなど)のインデックスに秘密鍵やそれに依存する値を使用すると、メモリアクセスの位置によってキャッシュの状態が変化し、そこから秘密鍵が漏洩する。
これを防ぐためには、以下のハードウェアおよびソフトウェアレベルの対策を講じる必要がある。
- ルックアップテーブルの排除: テーブル参照(Array Indexing)を避け、ビット演算や算術演算のみで処理を完結させる(ビットスライス実装など)。
- ハードウェア支援の活用: 近年のプロセッサ(IntelのSGXやARMのTrustZoneなど)が提供するセキュアエンクレーブ内で暗号処理を完結させ、OSやハイパーバイザ層からのサイドチャネル観測を物理的・論理的に隔離する。
—
4. 監査とコードレビュー:現場で脆弱性を見抜くチェックリスト
テックリードやセキュリティアーキテクトとしてプロジェクトを監査する際、暗号実装のコードやOSSライブラリの選定において、以下のポイントを必ず確認してほしい。
1. 自作暗号の完全禁止: 「我々のチームで高速なRSA/ECCライブラリを書いた」という開発者がいたら、直ちにそのコードを凍結させよ。多倍長演算や定数時間実装におけるバグやサイドチャネル耐性の担保は、世界最高峰の暗号学者チームでも数年の歳月を要する極めて高度な領域である。
2. モダンな暗号ライブラリの強制: OpenSSL 1.1.1 以降や BoringSSL, libsodium など、サイドチャネル攻撃(タイミング攻撃・キャッシュ攻撃)に対するハードニングが公式に施されたライブラリのみを使用しているか。
3. ブラインド化(Blinding)の有効性: RSA実装において、暗号化・復号の直前にランダムな値を用いた「ブラインド化(Key Blinding)」が有効化されているかを確認する。計算のたびにベースとなる数値をランダムに変化させることで、外部から観測される処理時間の相関関係を完全に攪乱し、タイミング攻撃を無効化できる。
4. 耐量子暗号(PQC)へのロードマップ: RSAやECCは、将来的な大規模量子コンピュータの登場(Shorのアルゴリズム)だけでなく、こうした巧妙なサイドチャネル攻撃の標的にもなり続ける。現在NISTが標準化を進めている格子暗号などの耐量子暗号アルゴリズムへの移行計画(敏速なアジリティを持つ暗号アーキテクチャの設計)が、組織のロードマップに組み込まれているか。
—
結びに代えて
セキュリティの本質は、見えない脅威を数式とコードの盾でいかに正確に封じ込めるかにある。数学的に完璧な要塞であっても、その運用や実装の「物理的な足音」を隠さなければ、熟練した侵入者はそこから侵入の糸口を見つけ出す。
タイミング攻撃は、抽象的なコードの向こう側にある「CPUの微細な挙動」にまで意識を向けることの重要性を私たちに教えてくれる。最高峰のセキュリティエンジニアを目指すなら、アルゴリズムの美しさだけでなく、それがシリコン上でどのように実行されるかという「低レイヤの現実」に常に目を光らせておいてほしい。
コメント