CSPRNG
1. CSPRNGの概要と目的
CSPRNG(Cryptographically Secure Pseudo-Random Number Generator:暗号論的擬似乱数生成器)は、生成した値から次の値を予測できないように設計された乱数生成の仕組みです。
プログラムでランダムな値が必要になる場面は多くありますが、セッションIDやパスワード再設定用のトークンのように「第三者に推測されては困る値」については、通常の乱数生成機能を使ってはいけません。CSPRNGを利用すると、過去の出力から将来の値を言い当てられない乱数を得られます。
この章では、CSPRNGがどのようなものなのか、また、なぜ知っておく必要があるのかを確認します。
CSPRNGとは?
CSPRNGとは、暗号技術の用途に耐える強度を持つ擬似乱数生成器のことです。乱数の要件については RFC 4086(Randomness Requirements for Security)、生成器の具体的な方式については NIST SP 800-90A などで規定されています。
例えば、プログラムで乱数を扱うとき、次のような記述を目にします。
// 通常の擬似乱数生成器
Math.random();
この関数は手軽ですが、内部状態を数回の出力から復元できる方式が使われており、以降の値を計算で言い当てられます。統計的には均等に散らばって見えても、予測できてしまう点が問題です。
一方、次のように記述すると、暗号用途に適した乱数が得られます。
// 暗号論的に安全な擬似乱数生成器
crypto.getRandomValues(new Uint8Array(16));
これにより、出力をいくら集めても次の値を予測できない、推測に耐える乱数を取得できます。
このように、「偏りがない」ことに加えて「予測できない」ことを保証する点がCSPRNGの大きな特徴です。
なお、CSPRNGも内部では決まった計算手順で値を作り出す擬似乱数です。物理現象から直接値を得る真の乱数生成器(TRNG)とは異なりますが、実用上は適切なCSPRNGで十分な強度が得られます。
CSPRNGはどのような場面で登場するのか?
CSPRNGは、推測されると不正アクセスにつながる値を生成する場面で利用されます。
例えば、次のような場面が代表的な利用例です。
- セッションIDの発行: ログイン状態を識別する値を、他者が推測できない形で生成する。
- 各種トークンの生成: パスワード再設定用のURL、CSRF対策トークン、APIキー、招待コードなど。
- パスワードの自動生成: 初期パスワードや、パスワード生成ツールが出力する文字列。
- 暗号処理のための値: 暗号鍵、ソルト、初期化ベクトル(IV)、ノンスなど。
Web開発者にとって、CSPRNGは「推測されたら困る値」を扱うすべての場面で前提となる仕組みです。
2. CSPRNGの仕組み・規格・主な特徴
CSPRNGは、OSが収集したエントロピー(予測困難な情報源)を種(シード)として内部状態を初期化し、暗号学的な関数を繰り返し適用して出力を生成します。
通常の擬似乱数生成器との違いは、アルゴリズムの計算速度や統計的な性質ではなく、「出力から内部状態を割り出せないこと」が設計目標に含まれている点にあります。
ここでは、通常の擬似乱数生成器との違いや、実際の記述例を確認します。
通常の擬似乱数生成器との違いと特徴
通常の擬似乱数生成器(PRNG)とCSPRNGには、主に次のような違いがあります。
- 予測可能性: PRNGは出力をある程度集めると内部状態を復元でき、以降の値を計算できます。CSPRNGは、出力から内部状態を割り出すことが計算量的に困難になるよう設計されています。
- 内部状態が漏れた場合の影響: CSPRNGには、内部状態が判明しても過去の出力を遡れない性質(後退探索耐性/backtracking resistance)が求められます。さらに定期的な再シードによって、将来の出力も守る性質(予測耐性/prediction resistance)を備えたものもあります。
- シードの供給元: PRNGは現在時刻などを種にすることがありますが、CSPRNGはOSが管理するエントロピープールから種を取得します。
- 速度と用途: PRNGは高速でシミュレーションや演出用途に適します。CSPRNGはわずかに重いものの、セキュリティ用途では必須です。
値が生成される流れを具体的に追うと、次のようになります。
- OSが、ハードウェアの割り込みタイミングや専用命令などから予測困難な情報を集め、エントロピープールに蓄積する。
- そのプールから得た値を種として、生成器の内部状態を初期化する。
- 内部状態に暗号学的な関数(ハッシュ関数やブロック暗号など)を適用し、出力を取り出す。
- 出力した分だけ内部状態を更新し、過去の出力を再現できない状態にする。
NIST SP 800-90A では、この生成器の方式として Hash_DRBG・HMAC_DRBG・CTR_DRBG が規定されています。かつて同規格に含まれていた Dual_EC_DRBG は、設計上の疑義が指摘されて2015年の改訂で削除されており、現在使用すべきではありません。
各言語での記述
実際の開発では、OSが提供する乱数機能を各言語の標準APIを通じて利用します。自分でアルゴリズムを実装する必要はありません。
例えば、次のような記述になります。
【記述・構造の具体例】
各言語で、暗号論的に安全な乱数からトークンを生成する例です。
// JavaScript(ブラウザ) const bytes = new Uint8Array(16); crypto.getRandomValues(bytes); const token = Array.from(bytes, b => b.toString(16).padStart(2, "0")).join("");// Node.js const crypto = require("node:crypto"); const token = crypto.randomBytes(16).toString("hex");# Python(secretsモジュール) import secrets token = secrets.token_hex(16) # 16進数の文字列 url_token = secrets.token_urlsafe() # URLに使える文字列// PHP $token = bin2hex(random_bytes(16)); $number = random_int(0, 99); // 範囲を指定した整数
このように、言語ごとに用意された専用のAPIを呼び出すだけで、OSのCSPRNGを利用できます。
なお、トークンの長さは「バイト数」で考える必要があります。上の例の16バイトは128ビット分の強度があり、16進数に変換すると32文字になります。推測に耐えるトークンとしては128ビット以上が一つの目安です。
3. CSPRNGが使われる理由・メリットと活用シーン
CSPRNGが必要とされる理由は、セキュリティに関わる値の安全性が「推測できないこと」に全面的に依存しているからです。
パスワードをいくら厳重に保護していても、再設定用のトークンが推測できてしまえば、そこから不正にログインされてしまいます。
CSPRNGを使うメリット
通常の擬似乱数生成器で生成した値は、一見ランダムに見えても、出力を観測されると以降の値を計算されてしまいます。
CSPRNGを適切に使用することで、次のようなメリットが生まれます。
- なりすましの防止: セッションIDやトークンを推測されず、他人のアカウントへの侵入を防げる。
- 総当たりへの耐性: 十分な長さを確保すれば、試行を繰り返しても現実的な時間で当てられない。
- 実装の容易さ: 言語やOSが提供するAPIを呼ぶだけで済み、独自の工夫を加える必要がない。
- 偏りの排除: 範囲を指定する専用APIを使えば、特定の値が出やすくなる偏りも避けられる。
ただし、CSPRNGを使っていても、出力の長さが短ければ総当たりで突破されます。「安全な生成器」と「十分な長さ」の両方が必要です。
システム開発や運用で活用する
実際の開発では、以下のような場面で活用されます。
- 認証・セッション管理: ログインセッションの識別子、ログイン状態を保持する値。
- 使い捨てのURL: パスワード再設定リンク、メールアドレス確認リンク、限定公開ページのアドレス。
- APIの認証情報: APIキー、クライアントシークレット、アクセストークン。
- 暗号処理: 暗号鍵の生成、パスワードハッシュに付与するソルト、暗号化のIVやノンス。
- パスワード生成: 初期パスワードの発行、パスワード生成ツールの出力。
一方で、抽選演出のアニメーションやゲームの見た目の揺らぎなど、推測されても問題のない用途では通常の乱数で構いません。判断の基準は「その値を言い当てられたときに被害が出るか」です。
安全な乱数生成を使っているかの確認方法
自分のコードが適切なAPIを使っているかは、関数名で判断できます。主要な言語での対応は次のとおりです。
# 使うべきAPI / 使ってはいけないAPI
JavaScript : crypto.getRandomValues(), crypto.randomUUID() / Math.random()
Node.js : crypto.randomBytes(), crypto.randomInt() / Math.random()
Python : secrets, os.urandom(), random.SystemRandom / random.random(), random.choice()
PHP : random_bytes(), random_int() / rand(), mt_rand(), uniqid()
Java : java.security.SecureRandom / java.util.Random
Go : crypto/rand / math/rand
Ruby : SecureRandom / Random, rand()
C# : RandomNumberGenerator / System.Random
既存のコードを点検する場合は、右側の関数名をプロジェクト全体から検索し、セキュリティに関わる値の生成に使われていないかを確認します。
# 問題のある可能性がある箇所を検索する例
grep -rn "Math.random\|mt_rand\|uniqid" ./src
注意したいのは、生成された値を統計的な検定ツールにかけても、暗号用途に適しているかは判定できないという点です。予測可能な生成器であっても、偏りに関する検定は問題なく通過します。「出力が偏っていないか」ではなく「どのAPIを呼んでいるか」で判断してください。
関連技術・関連用語との違い
CSPRNGと混同されやすい関連用語として、次のようなものがあります。
- PRNG(擬似乱数生成器): 決まった計算手順で乱数列を作る仕組み全般。CSPRNGはそのうち予測困難性を満たすものを指す。メルセンヌ・ツイスタなどは高速だが予測可能であり、暗号用途には使えない。
- TRNG(真の乱数生成器): 熱雑音などの物理現象から値を得る仕組み。生成速度が限られるため、実務ではこれをシードとしてCSPRNGを動かす構成が一般的。
- ハッシュ関数: 入力から固定長の値を求める一方向の関数。出力は散らばって見えるが、同じ入力からは常に同じ値が得られるため、乱数の代わりにはならない。
- UUID: 重複しない識別子の規格。バージョン4は乱数由来だが、生成に使われた乱数が安全かは実装次第で、
Math.random()を使った実装では推測できてしまう。現行の規格(RFC 9562)では、推測されないことが求められる場面ではCSPRNGを用いるべきとされている。
CSPRNGが「予測できない値を作る仕組み」であるのに対し、ハッシュ関数は「入力を要約する仕組み」です。
役割が異なるため、現在時刻やIDをハッシュ化した値をトークンとして使うことは避けてください。入力が推測できれば、同じ値を再現されてしまいます。
4. CSPRNGの導入・運用時の注意点と課題
CSPRNGは標準APIを呼ぶだけで利用できますが、周辺の扱いを誤ると強度が損なわれます。
また、問題が起きても動作上はエラーにならないため、気づかないまま運用され続けやすい点にも注意が必要です。
ここでは、実務上注意すべきポイントを説明します。
よくあるミス・設定ミス
よくある問題の一つが、手軽さから通常の乱数生成関数を使ってしまうことです。
Math.random() や mt_rand() は呼び出しが簡単で、生成される値も一見ランダムに見えます。動作上は何のエラーも出ないため、レビューで見落とされるとそのまま本番環境へ入り込みます。
また、次のようなミスも頻出します。
- 剰余による範囲の指定:
random_bytes()の結果を% 100のように丸めると、特定の値が出やすくなる偏りが生じます。範囲を指定する場合はrandom_int()やsecrets.randbelow()など、偏りを排除した専用APIを使用します。 - 現在時刻によるシード:
srand(time(NULL))のような初期化では、生成時刻が分かれば値を再現できます。 - バイト数と文字数の混同: 「16文字のトークン」を作るつもりで8バイトしか取得していない、といった取り違えが起こります。
- IDやメールアドレスからの生成: ユーザーIDや時刻をハッシュ化した値は、入力を推測されれば再現されます。
uniqid()の使用: PHPのuniqid()は現在時刻をもとにした値であり、乱数ではありません。
運用・セキュリティ上の注意点
通常の擬似乱数生成器が危険である理由は、出力を一定量集めると内部状態そのものを復元できるからです。
【よくある失敗・注意点の例】
パスワード再設定用のトークンを、通常の乱数生成関数で作ってしまっている例です。
危険な実装
// Math.random() は暗号用途に使えない function createResetToken() { return Math.random().toString(36).slice(2); }安全な実装
// CSPRNGから16バイト(128ビット)を取得する const crypto = require("node:crypto"); function createResetToken() { return crypto.randomBytes(16).toString("hex"); }
この例では、攻撃者が自分のアカウントで再設定を何度か実行して出力を収集すると、生成器の内部状態を割り出せます。すると、同じ時期に発行された他の利用者のトークンまで計算できてしまい、任意のアカウントのパスワードを変更される恐れがあります。
同様の問題は他の言語でも起こります。広く使われているメルセンヌ・ツイスタは、連続する624個の出力から内部状態を完全に復元でき、以降の値だけでなく過去の値も再現できることが知られています。
そのほか、運用面では次の点にも注意が必要です。
- トークンの長さ: 安全な生成器を使っていても、短ければ総当たりで突破されます。128ビット(16バイト)以上を目安にしてください。
- 起動直後のエントロピー不足: 仮想マシンやコンテナを起動した直後は、OSのエントロピーが十分に蓄積されていない場合があります。Linuxの
/dev/urandomは初期化が完了していなくても値を返してしまうため、getrandom()を利用するAPI(各言語の標準APIは通常こちらを使います)を選ぶとこの問題を避けられます。 - プロセスの複製と状態の重複: プロセスをフォークした場合や、仮想マシンのスナップショットから復元した場合、アプリケーション内部に保持した生成器の状態が複製され、同じ値が生成される恐れがあります。OSのAPIを都度呼び出す実装にしておくと安全です。
- ノンスやIVの再利用: 暗号化で使うノンスを使い回すと、暗号方式によっては内容を復元されるなど深刻な影響が出ます。生成するたびに新しい値を使ってください。
- 生成した値の取り扱い: トークンをURLに含めると、ブラウザの履歴やリファラー、アクセスログに残ります。有効期限を設け、使用後は無効化する運用が必要です。
安全に利用するための対策
CSPRNGを適切に運用するためには、以下の対策が有効です。
- セキュリティに関わる値は、必ず言語標準のCSPRNG用APIから生成する。
- 範囲を指定して整数を得る場合は、剰余で丸めず専用のAPIを使用する。
- トークンは128ビット以上の長さを確保し、用途に応じて有効期限と使い捨ての仕組みを設ける。
- 乱数生成アルゴリズムを自分で実装せず、OSや標準ライブラリに委ねる。
- 既存コードを点検し、
Math.random()などが認証やトークンの生成に使われていないか確認する。 - 生成した値をログに出力しない。障害調査のためにトークンを記録すると、そこから漏えいします。
特に、通常の乱数生成関数を使っていても動作そのものは正常に見えるため、コードレビューやテンプレートの整備といった仕組みで防ぐことが重要です。
5. まとめと関連ツールのご案内
ここまで解説したCSPRNGについて、重要なポイントを振り返ります。
CSPRNGは、出力から次の値を予測できないように設計された擬似乱数生成器である。- 通常の擬似乱数生成器との違いは速度や偏りではなく、内部状態を割り出せないという設計目標にある。
- セッションID、各種トークン、暗号鍵、ソルト、パスワード生成などで必須となる。
- 各言語に専用のAPIが用意されており、自分でアルゴリズムを実装する必要はない。
Math.random()やmt_rand()などは出力から内部状態を復元でき、暗号用途には使えない。- 範囲を指定する際に剰余で丸めると偏りが生じるため、専用のAPIを使用する。
- 安全な生成器を使っていても、トークンが短ければ総当たりで突破される。128ビット以上を目安にする。
【お役立ちツールのご案内】
ここまで解説したCSPRNGによって生成された、推測されにくいパスワードが必要な場合は、当サイトの セキュアパスワード生成ツール をぜひご活用ください。
文字種や長さを指定するだけで、暗号論的に安全な乱数をもとにしたパスワードを生成できます。初期パスワードの発行や、各種アカウントのパスワードを見直す際にお役立てください。