CSPRNG とエントロピー:安全な乱数生成の原理と落とし穴
鍵も nonce も署名も、根っこは乱数。予測できる乱数は暗号を一瞬で崩す。CSPRNG の要件・エントロピー源・実際の破壊事例を原理から押さえ、乱数事故を確実に防げる。
- 1.暗号用の乱数には予測不可能性が必須。過去の出力から次を当てられず(前方安全性)、内部状態が漏れても過去の出力を遡れない(後方安全性)ことが CSPRNG の要件。
- 2.エントロピー源(ハードウェア雑音や割り込みタイミング)で集めた真の乱雑さを seed にし、CTR_DRBG などの決定的アルゴリズムで高速に乱数列へ伸ばす。OS の /dev/urandom や getrandom が標準。
- 3.乱数の弱さは数学を破らずに鍵・nonce・署名を直接壊す。ECDSA の nonce 再利用は秘密鍵を露呈し、seed 不足は鍵衝突を生む。自前実装せず OS の CSPRNG を使うのが鉄則。
なぜ乱数が暗号の土台なのか
暗号の安全性は、最終的に攻撃者が予測できない秘密に依存します。共通鍵、公開鍵ペアの秘密鍵、初期化ベクトル(IV)、AEAD の nonce、署名で使う一時値、TLS のセッション鍵――これらはすべて乱数から作られます。つまり乱数が予測できれば、その上に積んだ暗号アルゴリズムがどれだけ強くても無意味になります。攻撃者はアルゴリズムを数学的に破る必要はなく、乱数を当てるだけでよいからです。
ここで重要なのは、プログラミング言語標準の rand() や Math.random() のような一般用途の擬似乱数生成器(PRNG)は暗号に使ってはいけないという点です。これらは統計的に「ばらけている」よう設計されていますが、予測されないことは目標にしていません。線形合同法(LCG)やメルセンヌ・ツイスタは、出力を数百個観測すれば内部状態を復元でき、以降の全出力を計算できます。
「分布が一様」「相関がない」といった統計的性質は、サイコロの公平さの話であって安全性の話ではありません。暗号で要るのは、攻撃者が過去の出力をいくら集めても次の1ビットを 1/2 より有利に当てられないこと。メルセンヌ・ツイスタは統計検定を楽々通過しますが、624 個の出力から内部状態が丸ごと復元できるため、暗号用途では完全に不適格です。
CSPRNG の要件:予測不可能性と二つの安全性
暗号論的に安全な擬似乱数生成器(CSPRNG)は、通常の PRNG に予測不可能性を加えたものです。満たすべき性質を分解します。
- 次ビット予測不可能性:これまでの出力列を全部見ても、次の出力ビットを 1/2 + ε より有意に当てられない(ε は無視できるほど小さい)。
- 前方安全性(forward secrecy / 前方予測不可能性):過去の出力から未来の出力を予測できない。出力を観測しても、内部状態を逆算できないことが前提。
- 後方安全性(backward secrecy / 状態危殆化耐性):ある時点で内部状態が漏れても、それ以前に生成した出力を遡って復元できない。これを実現するのが、生成のたびに状態を不可逆に更新する処理や、定期的に新しいエントロピーを混ぜる**リシード(reseeding)**です。
「過去の出力を守る」性質を forward secrecy と呼ぶ流儀(Diffie-Hellman の前方秘匿性と同じ向き)と、backward secrecy と呼ぶ流儀があり、文献によって名前が逆転します。名前で議論せず、「状態が漏れたとき、守られるのは過去か未来か」で具体的に確認してください。CSPRNG では通常、状態危殆化からの過去出力の保護と**未来出力の早期回復(リシードによる)**の両方を設計目標にします。
この予測不可能性は、結局暗号プリミティブの困難性に還元されます。CSPRNG は内部にブロック暗号やハッシュを持ち、「出力から状態を逆算する=そのプリミティブを破る」構図にして安全性を担保します。だから CSPRNG は新規発明ではなく、AES や SHA-2 の安全性を借りて作られます。
エントロピー源とシード:真の乱雑さをどこから得るか
CSPRNG はアルゴリズム自体は決定的です。同じ seed(種)を与えれば同じ列を返します。したがって、予測不可能性の源泉は seed に注入される**エントロピー(真の予測不可能性の量)**であり、ここが弱いと全体が崩れます。エントロピーは bit 単位で測られ、「攻撃者にとっての不確かさの量」を意味します。128 bit の seed でも、攻撃者から見て実質 20 bit 分しか不確かでなければ、100 万通り程度の総当たりで seed が割れます。
OS は物理的に予測しにくい事象からエントロピーを集めます。
- ハードウェア乱数生成器(熱雑音・ジッタを使う
RDRAND/RDSEEDや TPM) - 割り込みのタイミング(ディスク I/O、ネットワークパケット、キー入力の到着時刻の微小なゆらぎ)
- クロック間の位相差など
集めた生のエントロピーはバイアスを持つため、ハッシュなどで**抽出(whitening)**して均し、エントロピープールに蓄えます。
物理雑音・割り込みタイミング ← エントロピー源(真の乱雑さ)
│ サンプリング
▼
エントロピープール ← ハッシュで抽出・蓄積
│ seed(128〜256 bit)
▼
CSPRNG(DRBG) ← 決定的に高速展開
│
▼
暗号鍵・nonce・IV … ← 出力
歴史的に Linux には、エントロピーが「枯渇」するとブロックする /dev/random と、ブロックしない /dev/urandom がありました。現在の正しい理解は、一度十分な初期エントロピー(典型的に 256 bit)で seed されれば、CSPRNG は事実上無限に安全な出力を供給でき、それ以上ブロックする必要はないというものです。/dev/urandom の唯一の弱点は「ブート直後など、まだ初期 seed が完了する前に読むと弱い乱数が出る」ことでした。これを解決したのが getrandom(2) システムコールで、初期 seed が完了するまでだけブロックし、以降は決してブロックしない。アプリは原則 getrandom(や各言語の os.urandom/SecureRandom)を使えば正解です。
CTR_DRBG:決定的に乱数を伸ばす仕組み
NIST SP 800-90A が定める DRBG(Deterministic Random Bit Generator)の代表が CTR_DRBG です。名前の通り、ブロック暗号(通常 AES)のカウンタモードを乱数生成に流用します。骨格はシンプルです。
内部状態 = (Key, V) # V はカウンタ(ブロック長のレジスタ)
出力1ブロック:
V = V + 1
output_block = AES_Encrypt(Key, V) # カウンタを暗号化した値が乱数
更新(毎回):
V = V + 1; tmp1 = AES_Encrypt(Key, V)
V = V + 1; tmp2 = AES_Encrypt(Key, V)
Key = tmp1; V = tmp2 # Key と V を作り直す → 状態を不可逆更新
ポイントは二つです。第一に、出力は AES の暗号文なので、出力から (Key, V) を逆算するには AES を破る必要があり、ここで予測不可能性が担保されます。第二に、生成のたびに Key と V を新しい暗号化結果で上書きするため、仮に今の状態が漏れても、上書き前の Key を逆算できず過去の出力を遡れない(後方安全性)。さらに、一定量を生成したら新しいエントロピーでリシードし、状態が漏れても未来の出力が回復する設計にします。
| 生成器 | 種別 | 予測不可能性 | 暗号用途 | 備考 |
|---|---|---|---|---|
| LCG / rand() | 一般 PRNG | なし(数個で状態復元) | 不可 | 高速だが安全性ゼロ |
| メルセンヌ・ツイスタ | 一般 PRNG | なし(624個で復元) | 不可 | 統計検定は通るが暗号には不適格 |
| CTR_DRBG (AES) | CSPRNG | AES に帰着 | 適 | SP 800-90A 標準・ハードウェアと相性良 |
| HMAC_DRBG / Hash_DRBG | CSPRNG | SHA-2 に帰着 | 適 | SP 800-90A 標準・ソフトで一般的 |
| ChaCha20 ベース | CSPRNG | ChaCha20 に帰着 | 適 | Linux カーネルや BSD arc4random の中核 |
同じ SP 800-90A には、楕円曲線を使う Dual_EC_DRBG も含まれていましたが、これはバックドアを仕込める構造的欠陥が指摘され、後に標準から撤回されました。曲線パラメータを選んだ者が「特殊な定数」を握っていれば、わずかな出力から内部状態を予測できる――乱数生成器は、アルゴリズムだけでなくパラメータの素性まで検証が要るという教訓です。CSPRNG は CTR_DRBG / HMAC_DRBG / ChaCha20 系など、素性の確かなものを選びます。
乱数の弱さが暗号を破壊した実例の原理
CSPRNG が要件を満たさないと、数学を一切破らずに鍵が露呈します。原理ごとに代表例を見ます。
1. ECDSA / DSA の nonce 露呈・再利用。 署名アルゴリズムは署名ごとに一時値 k(per-signature nonce)を使います。この k が予測できるか、二つの署名で再利用されると、連立方程式から長期秘密鍵 d が代数的に解けてしまいます。再利用の場合、二つの署名 (r, s1), (r, s2)(r が同じ=同じ k)から k = (z1 - z2) / (s1 - s2)、続いて d = (s1·k - z1) / r が一発で求まります。2010 年の PlayStation 3 は k を固定値にしていたため署名鍵が抽出され、2013 年には Android の SecureRandom 実装不良で同一 k が再利用され、Bitcoin ウォレットの秘密鍵が盗まれました。
同じ k を使った2署名: s1 = k⁻¹(z1 + d·r), s2 = k⁻¹(z2 + d·r) (r 共通)
→ s1 - s2 = k⁻¹(z1 - z2)
→ k = (z1 - z2) / (s1 - s2)
→ d = (s1·k - z1) / r # 秘密鍵が一行で復元される
2. seed 不足による鍵衝突(Debian OpenSSL 事件, 2008)。 Debian の OpenSSL でエントロピー混入処理が誤って削除され、seed が実質プロセス ID(最大 32768 通り)程度しかなくなりました。結果、生成される鍵がごく少数の候補に偏り、世界中で同じ鍵が大量生成されました。攻撃者は全候補を事前計算するだけで該当鍵を割れます。エントロピー量が落ちると、鍵空間が指数的でなく総当たり可能な大きさに縮むという典型例です。
3. ブート直後・組み込み機器の seed 枯渇。 ディスクもキー入力もない組み込み機器や VM のブート直後は、エントロピー源が乏しく初期 seed が弱くなります。2012 年の大規模調査では、独立に生成されたはずの多数の RSA 公開鍵が素因数を共有しており、GCD を取るだけで秘密鍵が割れる事例が多数見つかりました。原因は、起動直後の同一に近い状態で鍵生成を走らせたことによる seed の重複でした。
(1) 暗号用乱数の要件は予測不可能性で、rand()/メルセンヌ・ツイスタは統計検定を通っても不適格。(2) CSPRNG はエントロピー源で seed → DRBG で決定的展開の二段構成。(3) 後方安全性は毎回の状態更新(不可逆)、未来の回復はリシードで担保。(4) ECDSA の k は再利用・予測で秘密鍵が代数的に露呈。(5) seed 不足は鍵空間の縮小・鍵衝突を招く(Debian, 共有素因数)。(6) 実務は自作せず OS の getrandom/os.urandom/SecureRandom を使う。
実務での原則
教訓は暗号の基礎と同じく「自作しない」に尽きます。具体的な指針は明快です。
- OS の CSPRNG を直接呼ぶ:Linux/macOS は
getrandom(2)、ライブラリ層なら Pythonsecrets/os.urandom、JavaSecureRandom.getInstanceStrong()、Node.jscrypto.randomBytes、Gocrypto/rand。Math.randomやjava.util.Randomを鍵・トークン・nonce に使わない。 - アプリ層で seed 管理をしない:自前でエントロピーを集めたり seed を保存したりするのは事故の元。OS のプールに任せる。
- nonce / IV は仕様の要件を厳守:AEAD の nonce は再利用厳禁。決定論的署名(RFC 6979 の deterministic ECDSA や Ed25519)を選べば、
kを乱数に頼らずメッセージと鍵から導出でき、nonce 事故を構造的に排除できる。 - 生成した鍵は安全に保管:強い乱数で作っても保存が雑なら無意味。鍵やトークンはシークレット管理の原則に従って扱う。
「乱数は単なるおまけ」ではなく、暗号の安全性が最後に依存する一点です。アルゴリズムの強度を議論する前に、その鍵がどこで・どれだけのエントロピーから生まれたかを確認する習慣を持ってください。
セキュリティ Article
CSPRNG とエントロピー:安全な乱数生成の原理と落とし穴を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
乱数
比較で見る軸
難易度: advanced / カテゴリ: セキュリティ / タグ数: 5
導入後に効く点
エントロピー源(ハードウェア雑音や割り込みタイミング)で集めた真の乱雑さを seed にし、CTR_DRBG などの決定的アルゴリズムで高速に乱数列へ伸ばす。OS の /dev/urandom や getrandom が標準。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- セキュリティ
- タグ数
- 5
判断チェックリスト
- 自社の用途が「乱数 / CSPRNG」に近いか確認する。
- 強みである「暗号用の乱数には予測不可能性が必須。過去の出力から次を当てられず(前方安全性)、内部状態が漏れても過去の出力を遡れない(後方安全性)ことが CSPRNG の要件。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。