乱数を作る方法!PRNGとCSPRNGの違いと使い分け!

Webアプリやゲーム開発、セッションIDの生成など、「乱数」はさまざまなシーンで使われています。プログラミングの正解では複数の乱数生成方法がありますが、用途によって使い分けることがとても大切です。

この記事では『乱数の生成方法』について、以下の内容をシンプルなサンプルコードを用いてわかりやすく解説します。

  • 乱数の生成方法
  • 疑似乱数生成器(PRNG)とは?
    • 疑似乱数生成器(PRNG)の例
  • 暗号論的疑似乱数(CSPRNG)とは?
    • 暗号論的疑似乱数(CSPRNG)の例
  • 疑似乱数生成器(PRNG)と暗号論的疑似乱数(CSPRNG)の使い分け

乱数を作る方法

乱数を作る方法

乱数は一見して「規則性がないように見える」数のことです。プログラミングにおいては、以下に示す2種類の乱数生成器を用いて乱数を生成します。

  • 疑似乱数生成器(PRNG)
    • アルゴリズムによって計算された再現性のある乱数を生成する。
    • JavaScriptでは、Math.random()がPRNGに該当します。
    • Pythonでは、標準ライブラリのrandomモジュールがPRNGに該当します。
    • PRNGは「Pseudo-Random Number Generator」の略です。
  • 暗号論的疑似乱数生成器(CSPRNG)
    • 予測困難な高品質の乱数を生成する。
    • JavaScriptでは、crypto.getRandomValues()がCSPRNGに該当します。
    • Pythonでは、secretsモジュールやos.urandom()がCSPRNGに該当します。
    • CSPRNGは「Cryptographically Secure Pseudo-Random Number Generator」の略です。

補足

他にも乱数を生成するものとして「真性乱数生成器(TRNG: True Random Number Generator)」があります。これは、自然界の物理現象(熱雑音、大気ノイズ、放射性崩壊など)を使って完全にランダムな乱数を生成するものです。ハードウェアが必要なのでソフトウェアだけでは真性乱数生成器で乱数を生成することはできません。

疑似乱数生成器(PRNG)とは?

疑似乱数生成器(PRNG)は、アルゴリズムによって計算された再現性のある乱数を生成します。完全にランダムではなく、決まった手順に基づいて生成されるため、初期値(シード)が同じであれば同じ乱数を生成するという特徴があります。軽くて高速ですが、再現可能な仕組みであるため予測されるリスクがあり、セキュリティ用途には不向きです。

疑似乱数生成器(PRNG)の特徴

  • 高速かつ軽量である
  • 初期値(シード)が同じであれば同じ乱数を生成する
  • セキュリティ用途には向かない(予測される可能性があるため)

疑似乱数生成器(PRNG)を使ってはいけないケース

以下のようなセキュリティが関わる処理では、疑似乱数生成器(PRNG)は使わないでください。

  • ワンタイムパスワード(OTP)
  • 暗号鍵の生成
  • セッションID・アクセストークンの生成

これらに疑似乱数生成器(PRNG)を使うと、悪意ある攻撃者に予測されるリスクがあります。

疑似乱数生成器(PRNG)の例

JavaScriptにおける疑似乱数生成器(PRNG)

JavaScriptでは、Math.random()が疑似乱数生成器(PRNG)に該当します。

const r = Math.random();
console.log(r); // 例: 0.4123456789

Pythonにおける疑似乱数生成器(PRNG)

Pythonでは、標準ライブラリのrandomモジュールが疑似乱数生成器(PRNG)に該当します。

import random

r = random.random()
print(r)  # 例: 0.7423827611191788

暗号論的疑似乱数(CSPRNG)とは?

暗号論的疑似乱数生成器(CSPRNG)は、予測困難な高品質の乱数を生成します。セキュリティが求められる場面では、こちらの使用が推奨されます。

暗号論的疑似乱数(CSPRNG)の特徴

  • 予測困難な高品質な乱数を生成する
  • セッションIDやトークンなどに適している
  • 安全な乱数を必要とする暗号化処理に向いている

暗号論的疑似乱数(CSPRNG)の例

JavaScriptにおける暗号論的疑似乱数(CSPRNG)

JavaScriptでは、crypto.getRandomValues()が暗号論的疑似乱数(CSPRNG)に該当します。crypto.getRandomValues()はブラウザ組み込みの安全な乱数生成関数です。

const arr = new Uint32Array(1);
crypto.getRandomValues(arr);
console.log(arr[0]); // セキュアな乱数(整数)

Pythonにおける暗号論的疑似乱数(CSPRNG)

Pythonでは、secretsモジュールやos.urandom()が暗号論的疑似乱数(CSPRNG)に該当します。secretsは暗号論的に安全な乱数を提供するために設計されており、パスワード生成やセッションIDの作成などに最適です。os.urandom()はランダムなバイト列を返す関数で、暗号用途でも使用できます。

import secrets

r = secrets.randbelow(1000)
print(r)  # 0〜999 のセキュアな乱数
import os

r = int.from_bytes(os.urandom(4), 'big')
print(r)  # セキュアな乱数(整数)

疑似乱数生成器(PRNG)と暗号論的疑似乱数(CSPRNG)の使い分け

乱数を生成するシーンによって、適切な乱数生成器を使い分ける必要があります。以下のようなケースでは、疑似乱数生成器(PRNG)を使ってはいけません。必ず暗号論的疑似乱数(CSPRNG)を使うようにしましょう。

  • 複数回・高速に呼び出される乱数処理
  • 並列・非同期で実行されるコード内の乱数
  • 暗号化・セキュリティを伴う処理(トークン、セッションID、パスワードなど)
  • 暗号鍵の生成・交換

これらのケースでは、予測困難で安全な乱数を使わないと、情報漏洩やなりすましのリスクが生じる可能性があります。

なぜ疑似乱数生成器(PRNG)はセキュリティに不向きなのか?

疑似乱数生成器(PRNG)を使うと以下の問題があります。

  • 実行環境によっては似たような乱数が出ることがある
  • 並列処理で連続して呼び出すと似たような乱数が出ることがある
  • 初期シードが簡単に予測できてしまう可能性がある

暗号処理では「推測されないこと」が重要です。そのため、セキュリティが関わる処理では必ず暗号論的疑似乱数(CSPRNG)を使いましょう。

本記事のまとめ

この記事では『乱数の生成方法』について、以下の内容を説明しました。

  • プログラミングにおいて乱数生成器は主に2種類ある
    • 疑似乱数生成器(PRNG)
    • 暗号論的疑似乱数生成器(CSPRNG)
  • 疑似乱数生成器(PRNG)
    • アルゴリズムに基づいた再現性のある乱数
    • 高速かつ軽量で処理性能に優れる
    • 初期値(シード)を指定すると同じ乱数列になる
    • JavaScript: Math.random()
    • Python: random.random()
  • 暗号論的疑似乱数生成器(CSPRNG)
    • 予測困難な高品質な乱数
    • セキュリティが求められる場面で必須
    • JavaScript: crypto.getRandomValues()
    • Python: secrets.randbelow(), os.urandom()

お読み頂きありがとうございました。