乱数生成器の障害と nonce 再利用の致命性
鍵も署名も、乱数が一度でも予測可能になれば代数的に解けて崩れる。ECDSA nonce 漏洩・Debian OpenSSL・PS3 の実例から、決定論的 nonce と再播種の設計までを原理で押さえ、乱数事故を確実に断てる。
- 1.ECDSA/DSA は署名ごとの一時値 k が再利用・予測されると、二つの署名から秘密鍵が一行の連立で解ける。数学を破らず鍵が露呈する。
- 2.弱い seed は鍵空間を縮める。Debian OpenSSL は seed が実質 PID 程度まで落ち、世界中で同一鍵が量産された。PS3 は k を固定値にして署名鍵を失った。
- 3.対策は乱数に頼らない設計。RFC 6979 の決定論的 ECDSA や Ed25519 は k を鍵とメッセージから導出し、起動直後は再播種完了まで生成をブロックする。
なぜ乱数の障害は「数学を破らずに」鍵を壊すのか
暗号アルゴリズムの安全性証明は、ほぼ例外なく「乱数が理想的に予測不可能である」ことを前提に組まれています。鍵生成のランダム性、署名の一時値、AEAD の nonce、IV――これらが前提どおりなら数学的困難性が守る。しかしこの前提が崩れると、攻撃者は離散対数や素因数分解を解く必要がなくなり、漏れた構造を線形代数で直接解くだけで秘密が出てきます。つまり乱数の障害は、暗号の最弱点を「困難な問題」から「初等的な計算」へすり替える攻撃です。
本稿は乱数の良し悪しそのものよりも、乱数が壊れたときに何が・なぜ起きるかに焦点を当てます。生成器の品質要件についてはCSPRNG とエントロピーを参照してください。
乱数の障害は3類型に整理できます。(1) 弱い RNG:出力から内部状態を逆算でき、以降が予測される。(2) 状態複製:VM スナップショットや fork で同一内部状態が複数に分裂し、別々のはずの出力が衝突する。(3) 予測可能 seed:種に十分なエントロピーがなく、鍵空間が総当たり可能な大きさへ縮む。いずれも分布の一様性は保たれることが多く、統計検定では見抜けません。
ECDSA / DSA の nonce 再利用:秘密鍵が一行で解ける
ECDSA の署名は、署名ごとに秘密の一時値 k(per-signature nonce)を引き、r = (kG).x mod n、s = k⁻¹(z + d·r) mod n を計算します(z はメッセージのハッシュ、d は長期秘密鍵、n は曲線の位数)。ここで k が二つの署名で再利用されると、両署名の r が一致し、攻撃者にそれが観測できてしまいます。
同じ k を使った2署名: s1 = k⁻¹(z1 + d·r), s2 = k⁻¹(z2 + d·r) (r 共通)
s1 - s2 = k⁻¹(z1 - z2)
k = (z1 - z2) / (s1 - s2) mod n # まず nonce が求まる
d = (s1·k - z1) / r mod n # 続いて秘密鍵が一行で復元される
割り算はすべて位数 n を法とするモジュラ逆元です。署名は公開情報なので、攻撃者は曲線の難しさに一切触れず d を得ます。さらに恐ろしいのは、k の一部ビットが漏れるだけでも危険な点です。各署名で k の上位数ビットが既知なら、複数署名を「隠れた数問題(Hidden Number Problem)」に落とし込み、格子簡約(LLL/BKZ)で d を回収できます。数ビットの偏りが数十署名で命取りになります。
nonce 再利用は明示的なバグだけでなく、状態複製でも起きます。RNG の内部状態を持つプロセスが fork したり、VM がスナップショットから復元・クローンされたりすると、複製後の各インスタンスは同じ状態から同じ k を引きます。別ホスト・別接続のつもりでも nonce が衝突し、ECDSA なら鍵露呈、AEAD なら認証鍵の漏洩につながります。
三つの実例とその原理
1. PlayStation 3(2010)。 ソニーはゲーム機の実行ファイルを ECDSA で署名していましたが、本来署名ごとに変えるべき k を**定数(固定値)**にしていました。結果、異なるファイルの二署名で r が常に一致し、上式そのままで署名用の長期秘密鍵が抽出されました。任意のコードに正規署名を付けられる状態となり、署名システム全体が崩壊しました。乱数を引き忘れて定数化するという、最も単純で最も破壊的な障害です。
2. Debian OpenSSL(2008)。 Debian の OpenSSL でエントロピー混入処理が誤って削除され、乱数の seed が実質プロセス ID 程度(最大 32768 通り)しか持たなくなりました。鍵生成はアルゴリズム的には正しく動くため、見た目は正常な鍵が出ます。しかし種が数万通りしかないため、生成され得る鍵は事前に全列挙でき、世界中で同一の SSH/SSL 鍵が量産されました。攻撃者は候補表を引くだけで該当鍵を割れます。エントロピー不足が鍵空間を指数サイズから総当たり可能サイズへ縮めた典型例です。
3. ブート直後・組み込み機器の seed 枯渇。 ディスク I/O もキー入力もない組み込み機器や VM の起動直後は、エントロピー源が乏しく初期 seed が弱くなります。2012 年の大規模調査では、独立に生成されたはずの多数の RSA 公開鍵が素因数 p を共有しており、無関係な二鍵の積から最大公約数を取るだけで両者の秘密鍵が割れました。原因は、起動直後の似通った状態で鍵生成が走り、seed が重複したことです。Android の SecureRandom 実装不良(2013)で同一 k が再利用され、Bitcoin 鍵が盗まれた事件も同じ系譜です。
| 事例 | 障害の種類 | 壊れた前提 | 結果 |
|---|---|---|---|
| PS3 (2010) | nonce 固定 | k が毎回変わる | ECDSA 署名鍵を完全復元 |
| Android SecureRandom (2013) | nonce 再利用 | k の一意性 | Bitcoin 秘密鍵の流出 |
| Debian OpenSSL (2008) | 予測可能 seed | seed のエントロピー量 | 同一鍵の世界規模での量産 |
| 共有素因数 (2012) | seed 枯渇/複製 | 鍵生成時の独立性 | GCD で RSA 秘密鍵が露呈 |
| VM クローン/fork | 状態複製 | 状態の非共有 | nonce 衝突・鍵露呈 |
設計で乱数事故を構造的に断つ
最も確実な対策は、危険なステップから乱数依存そのものを取り除くことです。
決定論的 nonce(RFC 6979)。 決定論的 ECDSA は k を乱数から引かず、秘密鍵 d とメッセージ z を HMAC で展開して導出します。同じ鍵・同じメッセージなら常に同じ k、異なるメッセージなら異なる k が出るため、再利用も偏りも構造的に起こり得ません。署名検証側は通常の ECDSA と互換で、生成側の乱数源が壊れていても安全です。Ed25519 も同様に、k をハッシュから決定論的に作る設計を最初から内蔵しており、nonce 事故を仕様レベルで排除しています。詳しくは署名スキームの内部を参照してください。
RFC 6979 の核心:
k = HMAC_DRBG(seed = d || z) # 秘密鍵とメッセージから決定論的に導出
→ 乱数源が壊れていても k は安全に一意。再利用・偏りが原理的に不可能
決定論的 nonce は乱数事故には強い一方、同じ k を毎回使うため、消費電力やタイミングを観測しつつ計算を意図的に乱す故障注入(fault injection)攻撃には注意が要ります。実装によっては、決定論導出に少量の乱数を混ぜるハイブリッド(hedged signing)で両者の利点を取ります。また鍵やコンテキストごとに導出入力を分けるドメイン分離を欠くと、別用途で同じ k が出る恐れがあります。
再播種(reseeding)と起動時ブロック。 乱数を使わざるを得ない箇所(鍵生成や IV)では、生成器が初期 seed を十分なエントロピーで完了する前に出力させないのが鉄則です。Linux の getrandom(2) は初期播種が完了するまでだけブロックし、以降は決してブロックしません。起動直後の組み込み機器・VM では、ハードウェア乱数(RDSEED/TPM)や保存エントロピーを早期注入し、播種完了まで鍵生成を待たせます。稼働中も定期的に新しいエントロピーを混ぜる再播種で、内部状態が漏れても未来の出力を回復させます。
(1) ECDSA の k 再利用は k = (z1−z2)/(s1−s2)、続いて d = (s1·k−z1)/r で秘密鍵が代数的に露呈。(2) k の数ビット漏洩も**格子攻撃(HNP)**で危険。(3) PS3=nonce 固定、Debian=seed の実質 PID 化、2012 RSA=共有素因数を GCD で回収。(4) 状態複製(fork/VM クローン)でも nonce は衝突する。(5) 恒久対策は RFC 6979 決定論的 ECDSA/Ed25519 で k を乱数から外す。(6) 乱数が要る箇所は getrandom で初期播種完了までブロック+再播種。
実務での原則
乱数障害の本質は「毎回独立に予測不可能な値が出る」という前提が、コードのバグ・パッチ・仮想化・組み込み環境で静かに破れることです。確認すべき習慣は具体的です。
- 署名は決定論的方式を既定にする:新規実装では Ed25519 か RFC 6979 の決定論的 ECDSA を選び、
kを乱数源から切り離す。これだけで nonce 再利用クラスの事故が消える。 - 乱数は OS の CSPRNG を直接呼ぶ:
getrandom(2)/os.urandom/SecureRandom.getInstanceStrong()/crypto/randを使い、Math.randomやjava.util.Randomを鍵・nonce・トークンに絶対使わない。アプリ層で seed を自作・保存しない。 - 複製と起動時を疑う:VM スナップショット・コンテナイメージ・fork 後は RNG 状態の独立性が崩れる前提で、復元時の再播種や鍵再生成を組み込む。組み込み機器は播種完了まで鍵生成を待たせる。
- 鍵そのものの素性も検証する:生成済み鍵に対し、既知の弱鍵リスト照合や、フリート全体での共有素因数チェック(GCD バッチ)を回す。鍵の保管は鍵管理ライフサイクルとHSM による鍵保護の原則に従う。
アルゴリズムの強度を語る前に、その鍵と nonce がどこで・どれだけのエントロピーから・本当に毎回独立に生まれたかを確認する。乱数は暗号の安全性が最後に依存する一点であり、最も静かに壊れる一点でもあります。
セキュリティ Article
乱数生成器の障害と nonce 再利用の致命性を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
乱数
比較で見る軸
難易度: advanced / カテゴリ: セキュリティ / タグ数: 6
導入後に効く点
弱い seed は鍵空間を縮める。Debian OpenSSL は seed が実質 PID 程度まで落ち、世界中で同一鍵が量産された。PS3 は k を固定値にして署名鍵を失った。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- セキュリティ
- タグ数
- 6
判断チェックリスト
- 自社の用途が「乱数 / nonce」に近いか確認する。
- 強みである「ECDSA/DSA は署名ごとの一時値 k が再利用・予測されると、二つの署名から秘密鍵が一行の連立で解ける。数学を破らず鍵が露呈する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。