SS-Infra ← ポータルに戻る

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は、推測されると不正アクセスにつながる値を生成する場面で利用されます。

例えば、次のような場面が代表的な利用例です。

Web開発者にとって、CSPRNGは「推測されたら困る値」を扱うすべての場面で前提となる仕組みです。

2. CSPRNGの仕組み・規格・主な特徴

CSPRNGは、OSが収集したエントロピー(予測困難な情報源)を種(シード)として内部状態を初期化し、暗号学的な関数を繰り返し適用して出力を生成します。

通常の擬似乱数生成器との違いは、アルゴリズムの計算速度や統計的な性質ではなく、「出力から内部状態を割り出せないこと」が設計目標に含まれている点にあります。

ここでは、通常の擬似乱数生成器との違いや、実際の記述例を確認します。

通常の擬似乱数生成器との違いと特徴

通常の擬似乱数生成器(PRNG)とCSPRNGには、主に次のような違いがあります。

値が生成される流れを具体的に追うと、次のようになります。

  1. OSが、ハードウェアの割り込みタイミングや専用命令などから予測困難な情報を集め、エントロピープールに蓄積する。
  2. そのプールから得た値を種として、生成器の内部状態を初期化する。
  3. 内部状態に暗号学的な関数(ハッシュ関数やブロック暗号など)を適用し、出力を取り出す。
  4. 出力した分だけ内部状態を更新し、過去の出力を再現できない状態にする。

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を適切に使用することで、次のようなメリットが生まれます。

ただし、CSPRNGを使っていても、出力の長さが短ければ総当たりで突破されます。「安全な生成器」と「十分な長さ」の両方が必要です。

システム開発や運用で活用する

実際の開発では、以下のような場面で活用されます。

一方で、抽選演出のアニメーションやゲームの見た目の揺らぎなど、推測されても問題のない用途では通常の乱数で構いません。判断の基準は「その値を言い当てられたときに被害が出るか」です。

安全な乱数生成を使っているかの確認方法

自分のコードが適切な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と混同されやすい関連用語として、次のようなものがあります。

CSPRNGが「予測できない値を作る仕組み」であるのに対し、ハッシュ関数は「入力を要約する仕組み」です。

役割が異なるため、現在時刻やIDをハッシュ化した値をトークンとして使うことは避けてください。入力が推測できれば、同じ値を再現されてしまいます。

4. CSPRNGの導入・運用時の注意点と課題

CSPRNGは標準APIを呼ぶだけで利用できますが、周辺の扱いを誤ると強度が損なわれます。

また、問題が起きても動作上はエラーにならないため、気づかないまま運用され続けやすい点にも注意が必要です。

ここでは、実務上注意すべきポイントを説明します。

よくあるミス・設定ミス

よくある問題の一つが、手軽さから通常の乱数生成関数を使ってしまうことです。

Math.random() や mt_rand() は呼び出しが簡単で、生成される値も一見ランダムに見えます。動作上は何のエラーも出ないため、レビューで見落とされるとそのまま本番環境へ入り込みます。

また、次のようなミスも頻出します。

運用・セキュリティ上の注意点

通常の擬似乱数生成器が危険である理由は、出力を一定量集めると内部状態そのものを復元できるからです。

【よくある失敗・注意点の例】

パスワード再設定用のトークンを、通常の乱数生成関数で作ってしまっている例です。

危険な実装

// 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個の出力から内部状態を完全に復元でき、以降の値だけでなく過去の値も再現できることが知られています。

そのほか、運用面では次の点にも注意が必要です。

安全に利用するための対策

CSPRNGを適切に運用するためには、以下の対策が有効です。

特に、通常の乱数生成関数を使っていても動作そのものは正常に見えるため、コードレビューやテンプレートの整備といった仕組みで防ぐことが重要です。

5. まとめと関連ツールのご案内

ここまで解説したCSPRNGについて、重要なポイントを振り返ります。

【お役立ちツールのご案内】

ここまで解説したCSPRNGによって生成された、推測されにくいパスワードが必要な場合は、当サイトの セキュアパスワード生成ツール をぜひご活用ください。

文字種や長さを指定するだけで、暗号論的に安全な乱数をもとにしたパスワードを生成できます。初期パスワードの発行や、各種アカウントのパスワードを見直す際にお役立てください。