公平な抽選のための乱数の話
抽選結果が「本当に運に従っているか」は、使う乱数の品質で決まります。MDNの公式仕様から順に整理します。
参照した一次情報
- ・MDN「Crypto: getRandomValues() メソッド」(developer.mozilla.org/ja/docs/Web/API/Crypto/getRandomValues)
- ・MDN「Math.random()」(暗号学的に安全ではない乱数値・関連情報リンク)
- ・Web Cryptography Level 2 仕様書(w3c.github.io/webcrypto)
Math.randomは「擬似乱数」
JavaScript標準の Math.random() は0以上1未満の数値を返しますが、その生成は決定論的なアルゴリズムに基づきます。 内部状態(シード)から計算で次の値を出すため、シードが同じなら必ず同じ列が出ます。これを「擬似乱数(PRNG)」と呼びます。
MDNの公式ドキュメントは Math.random() の関連情報セクションで「暗号学的に安全ではない乱数値」と明記し、暗号鍵生成や偏りを許容できない用途には使うべきでないと案内しています。 一般用途には十分でも、プレゼント企画や順番決めで「公平性が後で疑われる」可能性がある場面では別の選択肢が必要です。
crypto.getRandomValuesはOSのエントロピーを使う
一方 crypto.getRandomValues() は「暗号強度の強い乱数値」を返すAPIです。MDNは引数のTypedArrayが乱数で埋められると説明し、Web Cryptography Level 2 仕様書を引用しています。
実装は完全な真の乱数ではなく、十分なエントロピーを有する シード値 を用いた擬似乱数発生器(CSPRNG)を使用します。 ただし、シード値はプラットフォーム固有の乱数関数、UnixなどOSの /dev/urandom デバイス、他の擬似乱数データのソースから取得されるため、外部から予測することはできません。
つまり、JavaScriptで実行する瞬間にOS側から「予測不可能なノイズ」を吸い上げて乱数を作るため、Math.randomと違って結果を後から再現することができません。これが「公平性を後で疑われない」性質を生みます。
2つを比較した早見表
| 項目 | Math.random() | crypto.getRandomValues() |
|---|---|---|
| 分類 | 擬似乱数(PRNG) | 暗号学的擬似乱数(CSPRNG) |
| エントロピー源 | 内部シード | OS(/dev/urandom 等) |
| 予測可能性 | 条件次第で予測可能 | 予測困難 |
| 適した用途 | ゲーム演出・サンプル生成 | プレゼント抽選・順番決め・ID生成 |
| 注意点 | 暗号用途には使わない | 暗号鍵生成にはgenerateKey()を使う |
getRandomValuesでも「暗号鍵生成」は別物
MDNの「使用上の注意」セクションは、getRandomValues() を暗号鍵を生成するために使ってはならないと明記しています。 鍵生成は SubtleCrypto.generateKey() を使うべきで、その理由のひとつとして「getRandomValues() が保護されたコンテキストで動作することが保証されていない」点が挙げられています。
本ツールが扱うのは「抽選結果の公平性」であって「暗号鍵」ではないため、getRandomValues() は適切な選択肢です。ただし、本人確認用ID等に流用する場合は別途、専用APIを使ってください。
本ツールの実装方針
本ツールは crypto.getRandomValues() で生成したUint32値をFisher-Yatesシャッフルのインデックス選定に使っており、 MDN仕様に従って Uint32Array を介して乱数を取得しています。応募者が何人いても、入力した順番や名前の長さに関係なく当選確率は均一です。
再度抽選すると別の結果が出るのは、毎回OSのエントロピーから新しいシードを引いているため。 逆に言えば「同じ結果を再現する」ことはできない設計なので、必要ならスクリーンショットや結果コピーで証跡を残してください。