【実務・中級編】 暗号実装における乱数生成器(CSPRNG)の重要性とエントロピー不足 – 暗号理論・認証基盤 & エンドポイントセキュリティ防御ガイド

乱数は「暗号の心臓」だ。その鼓動が止まれば、堅牢な鍵もただの紙屑になる

エンジニア諸君、セキュリティの現場で一番「やってはいけないこと」は何だと思う? SQLインジェクション? クロスサイトスクリプティング? もちろんそれらも致命的だが、多くのシニアエンジニアが鼻で笑うほど初歩的で、かつ「気づかないうちに全てを台無しにする」のが、乱数生成器の選定ミスだ。

君たちがせっせとAES-256でデータを暗号化しようが、RSAで鍵交換しようが、その「鍵」を作るための乱数が予測可能であれば、攻撃者は玄関の鍵をピッキングするまでもなく、合鍵を自分で複製して堂々と入ってくる。

今日は、暗号の根幹を支える「CSPRNG(暗号論的擬似乱数生成器)」について、教科書には載っていない「なぜ現場で事故るのか」という視点で話そう。

—

1. なぜ「普通の乱数」を使ってはいけないのか

プログラミング言語の標準ライブラリにある rand() や Math.random() は、基本的に「統計的な分布」を整えるために作られている。高速で、再現性がある(シード値を固定すれば同じ乱数が出る)。

だが、暗号の世界で求められるのは「予測不可能性」だ。攻撃者は君たちが使っている srand(time()) のような古い手法を笑いながら、ミリ秒単位の総当たりでシード値を特定する。

攻撃者の視点:PoCのロジック

攻撃者は、君たちのアプリケーションが生成した「トークン」をいくつかサンプリングする。もし time() ベースの古い乱数生成器を使っていれば、数千回のリクエストを投げて時刻のズレを特定し、次のトークンを予測するスクリプトを回す。これが「予測可能性による突破」の正体だ。

—

2. 実践:OSの力を借りたセキュアな実装

現代の開発において、自分で乱数アルゴリズムを実装しようなどと考えてはいけない。OSが提供する「エントロピー源(デバイスドライバのノイズやCPUの熱雑音など)」を活用した、CSPRNGを利用するのが鉄則だ。

Pythonでの実装例

Pythonの secrets モジュールを使えば、暗号論的に強固な乱数を簡単に扱える。

import secrets
import string

# パスワードリセット用のトークンなどを生成する場合
def generate_secure_token(length=32):
    # アルファベットと数字を混ぜた安全な文字列
    alphabet = string.ascii_letters + string.digits
    # secrets.choice はOSのCSPRNGを使用する
    return ''.join(secrets.choice(alphabet) for _ in range(length))

# 使用例
token = generate_secure_token()
print(f"セキュアなトークン: {token}")

PHPでの実装例

PHP 7以降であれば、迷わず random_bytes() を使え。

<?php
// 32バイトのランダムなバイナリを生成し、16進数で出力
// これにより、予測不可能なIDや鍵の元データが作れる
$secure_random_key = bin2hex(random_bytes(32));

echo "生成された鍵: " . $secure_random_key;

// もし `random_bytes` が失敗した場合(稀だがOSの乱数源が枯渇した場合)、
// 内部で例外が投げられる。これをキャッチしないコードは論外だ。
?>

JavaScript (Node.js/Browser) での実装例

Webアプリでトークンを生成する際、 Math.random() は絶対に禁止だ。ブラウザの crypto API を使う。

// ブラウザ環境
function getSecureRandomValue() {
    const array = new Uint32Array(1);
    // window.crypto.getRandomValues はCSPRNGを利用する
    window.crypto.getRandomValues(array);
    return array[0];
}

console.log("セキュアな乱数:", getSecureRandomValue());

—

3. 「エントロピー不足」という見えない罠

クラウド環境やDockerコンテナで運用している諸君、特に注意が必要だ。

乱数生成は、OSの「エントロピー・プール」から供給される。起動直後のコンテナや、負荷の低いサーバーでは、このプールが十分に溜まっておらず、乱数生成が一時的にブロックされたり、予測可能な値が返される可能性がある(特に古いLinuxカーネル環境)。

  • 対策:
  • /dev/urandom を積極的に利用すること。(/dev/random はブロッキングが発生しやすく、現代の暗号論的要件では /dev/urandom で十分であるとされている)
  • クラウド環境では、haveged 等のデーモンを導入し、エントロピーを積極的に補給する構成を検討する。

—

結論:エンジニアとしての心得

「動けばいい」というコードは、いつか必ず踏み絵になる。暗号実装において、乱数の選択は「妥協してはいけない最後の聖域」だ。

1. Math.random() や rand() を見たら、即座に修正対象としてチケットを切ること。
2. その言語の標準ライブラリに「cryptographically secure」という名前の関数やモジュールがあるか確認する癖をつけること。
3. 何かを作るとき、「この値は攻撃者に推測されるリスクがあるか?」と常に自問自答すること。

セキュリティは、派手なファイアウォール製品を買うことではなく、こうした地味な実装の積み重ねによってのみ守られる。諸君のコードが、攻撃者の野望を砕く堅牢な壁であることを期待している。

何か実装で迷うことがあれば、またいつでも聞きに来てくれ。理論と現場の板挟みになった時こそ、エンジニアの真価が問われるのだから。

コメント

タイトルとURLをコピーしました