金曜日の夕方、デプロイも終わってほっと一息ついた開発チームの雑談タイムでのことでした。
「ところで、Math.random()
って本当にランダムなんですか?」
50代後半のベテランエンジニア、滝沢さんが苦笑いを浮かべました。「またこの話題か。これ、うちの会社では2年に1度は必ず誰かが言い出すんだよね」
同じく50代の愛田さんも懐かしそうに頷きます。興味深くその会話を聞いていました。
そう、この「乱数は本当にランダムか?」という問いは、エンジニアにとって避けて通れない哲学的かつ実践的なテーマなのです。今回は、この永遠の問いに改めて向き合ってみましょう。
そもそも「ランダム」とは何か
予測不可能性という幻想
まず、私たちが日常的に使っている「ランダム」という言葉の意味を考えてみましょう。
# よくあるコード
import random
random_value = random.random()
print(random_value) # 0.7394508971214333
このコードを実行するたびに異なる値が出力されます。一見すると完全にランダムに見えますが、実はこれは「疑似乱数」と呼ばれるものです。
なぜ「疑似」なのでしょうか。それは、コンピュータが生成する乱数は、必ず何らかのアルゴリズムに基づいているからです。つまり、理論的には予測可能なのです。
決定論的カオスの世界
現代のプログラミング言語で使われる疑似乱数生成器の多くは、「線形合同法」1や「メルセンヌ・ツイスタ」2といったアルゴリズムを使用しています。
# 線形合同法の簡単な実装例
class SimpleRandom:
def __init__(self, seed=1):
self.seed = seed
def next(self):
# X(n+1) = (a * X(n) + c) mod m
self.seed = (1103515245 * self.seed + 12345) % (2**31)
return self.seed / (2**31)
# 同じシードからは同じ系列が生成される
rng = SimpleRandom(42)
print([rng.next() for _ in range(5)])
# 常に同じ結果: [0.3707, 0.2641, 0.5435, 0.0016, 0.8981]
このコードが示すように、同じ初期値(シード)から始めれば、常に同じ数列が生成されます。これが疑似乱数の本質です。
実は深刻な問題「乱数の品質」
PlayStation 3 の暗号鍵流出事件
疑似乱数の予測可能性は、時として深刻なセキュリティ問題を引き起こします。
2010年、PlayStation 3のセキュリティが破られた事件を覚えているでしょうか。この事件の原因の一つは、ソニーがECDSA3署名に使用する乱数(nonce4)として、実際には固定値を使用していたことでした(参考文献1)。
# ECDSA署名における脆弱な実装の概念図
def vulnerable_ecdsa_sign(message, private_key, curve):
# 本来は毎回異なるkを生成すべき
k = 42 # nonce再利用の脆弱性!
# 楕円曲線上の点の計算
R = curve.scalar_mult(k, curve.G)
r = R.x % curve.n
# 署名の計算
z = hash(message)
s = (pow(k, -1, curve.n) * (z + private_key * r)) % curve.n
return (r, s)
このような実装では、複数の署名から秘密鍵を逆算することが可能になってしまいます。
「それで思い出したんだけど」と口を開きました。「以前、本番環境で疑似乱数のシードを固定したまま3ヶ月気づかなかったことがあってね…」
愛田さんが深くうなずきます。「あー、あるある。俺も昔、乱数で生成したはずのユーザーIDが妙に偏ってるって指摘されて、調べたらテスト用のシード値が残ってたことがあったよ」
Debian OpenSSLの惨事
2008年、Debian LinuxのOpenSSLパッケージに重大な脆弱性が発見されました(参考文献2)。メンテナが「不要」と判断してコメントアウトしたコードが、実は乱数生成のエントロピー5源だったのです。
// 問題のあったコード(概念図)
// MD_Update(&m, buf, j); /* このコメントアウトが大惨事に */
MD_Update(&m, &dummy, 1); /* 実質的にエントロピーゼロ */
この結果、生成可能な鍵の種類が激減し、総当たり攻撃が現実的になってしまいました。
エンジニアの永遠の悩み「どの乱数を使うべきか」
身近な例、オンラインゲームのダイスロール
「そういえば、去年うちのゲームチームが面白いクレームを受けたんだ」と思い出したように言いました。「『このゲームのサイコロ、絶対イカサマしてる!6が出る確率が低すぎる!』って」
元橋さんが興味深そうに聞きます。「実際に偏りがあったんですか?」
「いや、完全に正常だった。でも人間の認知バイアスってやつでね」愛田さんが説明を引き継ぎました。「本当にランダムな結果って、人間には偏って見えることがあるんだ」
# よくあるダイス実装
import random
def roll_dice():
return random.randint(1, 6)
# 1万回振った結果を集計
results = {}
for _ in range(10000):
roll = roll_dice()
results[roll] = results.get(roll, 0) + 1
print(results)
# {1: 1667, 2: 1654, 3: 1672, 4: 1668, 5: 1665, 6: 1674}
# ほぼ均等だが、人は連続して同じ目が出ると「おかしい」と感じる
「実はもっと深刻な問題もあってね」滝沢さんが続けました。「昔、あるオンラインカジノで、疑似乱数の周期が短すぎて、プレイヤーにパターンを読まれた事件があった」
# 危険な実装例、予測可能なダイス
class PoorDice:
def __init__(self):
self.state = 12345 # 固定初期値
def roll(self):
# 単純すぎる線形合同法
self.state = (self.state * 1103515245 + 12345) & 0xFFFF
return (self.state % 6) + 1
# 同じパターンが繰り返される
dice = PoorDice()
pattern = [dice.roll() for _ in range(20)]
print(pattern) # 毎回同じ: [4, 4, 5, 1, 1, 2, 3, 3, 4, 5, 5, 6, 2, 2, 3, 4, 4, 5, 1, 1]
物理サイコロ vs デジタルダイス
「でも物理的なサイコロだって完全にランダムじゃないですよね」元橋さんが鋭い指摘をしました。
「その通り!」と興奮気味に答えました。「カジノでは精密に作られたサイコロを使うけど、それでも重心の偏りや投げ方で結果に影響が出る。だから定期的に交換してるんだ」
用途別乱数選択ガイド
実際の開発では、用途に応じて適切な乱数生成方法を選ぶ必要があります。
// ゲームやシミュレーション用
const gameRandom = Math.random();
// セキュリティが重要な場合
const crypto = require('crypto');
const secureRandom = crypto.randomBytes(32);
// より高品質な疑似乱数が必要な場合(Node.js)
const { randomInt } = require('crypto');
const highQualityRandom = randomInt(0, 100);
しかし、どんなに優れた疑似乱数生成器を使っても、それは所詮「疑似」でしかありません。
真のランダムを求めて「熱雑音」という希望
物理現象を利用した真の乱数
「実は、真のランダムを生成する方法はあるんだよ」と切り出しました。
ここで登場するのが「真性乱数生成器(TRNG6)」です。これは、予測不可能な物理現象を利用して乱数を生成します。
最も一般的なのが「熱雑音」を利用した方法です。抵抗器に流れる電流には、原子の熱運動による微小なゆらぎ(ジョンソン・ナイキスト雑音7)が含まれています。
「おお、物理屋の血が騒ぐ話だ」愛田さんが身を乗り出しました。「確か絶対零度でない限り、必ず熱雑音は発生するんだよな」
熱雑音の原理
├─ 原子の熱運動
│ ├─ 統計的には予測可能
│ └─ 個々の粒子レベルでは予測不可能
└─ 電気信号への変換
├─ アナログ信号の測定
└─ デジタル値への変換
現実世界での実装
IntelのCPUには「RDRAND」命令が実装されており、チップ内の熱雑音を利用した真性乱数を生成できます(参考文献3)。ただし、2013年にNSAバックドア疑惑が浮上し、Linuxカーネルは予防措置としてRDRAND出力を直接使用せず、内部エントロピープールにXOR混入する設計を採用しています(参考文献4)。
# Linux環境でのハードウェア乱数取得
import os
def get_hardware_random(num_bytes=32):
"""
/dev/urandomは最新のLinuxカーネルでは
必要に応じてハードウェア乱数を使用
"""
return os.urandom(num_bytes)
# より直接的にハードウェア乱数を使う場合
def get_true_random(num_bytes=32):
"""
/dev/random はLinux 5.6以降では十分にシードされた後は
ブロックしなくなったが、ブート直後などエントロピープールが
初期化されていない状態では依然としてブロックする
"""
with open('/dev/random', 'rb') as f:
return f.read(num_bytes)
また、専用のハードウェア乱数生成器も存在します。これらは放射性崩壊、光量子、大気雑音などを利用して、真のランダム性を実現しています。
「うちもセキュリティ監査で指摘されて、去年HSM8(Hardware Security Module)導入したよ」滝沢さんが実体験を語ります。「月額料金見て腰抜かしたけど、セキュリティには代えられないからね」
クラウド時代の乱数生成
「そういえば、AWS KMSの乱数生成機能使ったことある?」と話題を振りました。
「あるある!」愛田さんが反応しました。「CloudFormationでランダムなパスワード生成するときに使ったよ」
import boto3
# AWS KMSを使った暗号学的に安全な乱数生成
kms_client = boto3.client('kms', region_name='ap-northeast-1')
def generate_secure_random_aws(num_bytes=32):
response = kms_client.generate_random(NumberOfBytes=num_bytes)
return response['Plaintext']
# Secrets Managerでのランダムパスワード生成
sm_client = boto3.client('secretsmanager', region_name='ap-northeast-1')
def create_random_password():
response = sm_client.get_random_password(
PasswordLength=32,
ExcludeCharacters=' %+~`#()|[]{}:;?!\'/@"\\',
ExcludePunctuation=False,
RequireEachIncludedType=True
)
return response['RandomPassword']
「Cloudflareも面白いですよ」元橋さんが意外な知識を披露しました。「Lava Lampを使った乱数生成って知ってますか?」
「ああ、あのカラフルなやつか!」滝沢さんが笑いました。「Cloudflareのサンフランシスコオフィスで、ラバランプの動きをカメラで撮影して、その映像から乱数を生成してるんだよな」
# Cloudflare API経由でのランダムデータ取得(概念的な例)
import requests
def get_cloudflare_random():
"""
実際のCloudflare APIとは異なりますが、
彼らのエントロピー源は物理現象ベース
"""
# Cloudflareは実際にはdrand(分散型乱数ビーコン)なども提供
response = requests.get('https://drand.cloudflare.com/public/latest')
return response.json()['randomness']
「物理現象を使うっていう意味では、熱雑音もラバランプも同じ発想だよね」と総括しました。「予測不可能な自然現象をデジタル化する」
なぜこの話題は2年に1度盛り上がるのか
エンジニアの性(さが)
経験上、この「乱数は本当にランダムか?」という話題は、確かに定期的に社内で盛り上がります。その理由を考えてみました。
「面白いことに、だいたい2年周期なんだよね」と滝沢さんが振り返ります。「前回は2年前の夏、その前は2019年の忘年会だったかな」
1. 新人エンジニアの素朴な疑問
技術に真摯に向き合う新人ほど、この本質的な問いを投げかけてきます。そして、それをきっかけにベテランエンジニアたちの議論が始まるのです。
「元橋くんみたいに素直に疑問を持つのは大事だよ」と愛田さんが元橋さんに声をかけました。「俺も30年前、同じ質問して先輩に3時間説教されたからね」
2. 実装での失敗体験
# よくある失敗例
import random
# テストで固定シードを使ったまま本番にデプロイ
random.seed(42) # これを削除し忘れる
# 結果、「ランダム」なはずの処理が毎回同じ結果に...
このような失敗を経験したエンジニアは、乱数の本質について深く考えるようになります。
3. セキュリティインシデントのニュース
定期的に発生する乱数関連のセキュリティ事件が、この話題を再燃させます。
哲学的な魅力
さらに、この話題には哲学的な魅力があります。
「完全にランダムなものは存在するのか?」
「決定論的な宇宙で真のランダムは可能なのか?」
「量子力学的な不確定性は本当にランダムなのか?」
「ラプラスの悪魔って知ってる?」愛田さんが哲学モードに入りました。「宇宙のすべての原子の位置と運動量がわかれば、未来は完全に予測できるって話。もしそれが本当なら、真のランダムなんて存在しないことになる」
「でも量子力学では…」と元橋さんが反論しようとすると、滝沢さんがニヤリと笑いました。「お、2年目のくせに量子力学まで持ち出すか。議論がさらに2時間延長だな」
これらの問いは、エンジニアリングの枠を超えて、私たちの世界観にまで踏み込んできます。
実践的なアドバイス
今すぐチェックすべきこと
あなたのプロジェクトで以下のような実装をしていないか、確認してみてください。
# NGパターン、セキュリティに関わる処理でrandomモジュールを使用
import random
import string
chars = string.ascii_letters + string.digits
session_id = ''.join(random.choices(chars, k=16))
# OKパターン、暗号学的に安全な乱数を使用
import secrets
session_id = secrets.token_hex(16)
# NGパターン、パスワード生成に通常の乱数を使用
import random
password = ''.join(random.choices(chars, k=16))
# OKパターン、secrets モジュールを使用
import secrets
password = ''.join(secrets.choice(chars) for _ in range(16))
用途に応じた使い分け
- ゲーム・シミュレーション → 疑似乱数で十分
- 統計的サンプリング → 高品質な疑似乱数(メルセンヌ・ツイスタ等)
- 暗号・セキュリティ → 暗号学的に安全な乱数9(/dev/urandom、CryptoAPI等)
- 超高セキュリティ → ハードウェア乱数生成器の検討
おわりに「不確実性を受け入れる勇気」
「乱数は本当にランダムか?」
この問いに対する答えは、「何をもってランダムとするか」によって変わります。疑似乱数は決定論的ですが、実用上は十分にランダムです。真性乱数は物理法則に基づきますが、その物理法則自体が確率的です。
「結局のところ」愛田さんがぽつりと呟きました。「世界は灰色の光に包まれているんだよ。完全な白でも黒でもない。決定論と非決定論の狭間で、俺たちはコードを書いている」
その言葉に、一同は静かに頷きました。
重要なのは、この不確実性を理解し、適切に扱うことです。完璧なランダムを追求するのではなく、用途に応じた「十分に良い」ランダムを選択する。これこそが、エンジニアとしての実践的な知恵なのかもしれません。
次回、あなたの職場でこの話題が持ち上がったとき、あなたはどんな視点で議論に参加しますか?そして、その議論の中で、新たな発見があることを願っています。
きっと2年後、また誰かが同じ質問をするでしょう。そのとき、この記事が議論の出発点になれば幸いです。
「じゃあ、そろそろ帰るか」滝沢さんが伸びをしながら立ち上がりました。「元橋くん、良い質問だったよ。おかげで久しぶりに熱い議論ができた」
時計を見て驚きました。もう20時を過ぎています。乱数談義に花が咲いて、すっかり時間を忘れていたようです。
参考文献
-
fail0verflow. (2010). Console Hacking 2010: PS3 Epic Fail. 27th Chaos Communication Congress (27C3). Retrieved from https://events.ccc.de/congress/2010/Fahrplan/events/4087.en.html
-
Debian Security Team. (2008). DSA-1571-1 openssl — predictable random number generator. Debian Security Advisory. Retrieved from https://www.debian.org/security/2008/dsa-1571
-
Intel Corporation. (2018). Intel Digital Random Number Generator (DRNG) Software Implementation Guide. Retrieved from https://www.intel.com/content/www/us/en/developer/articles/guide/intel-digital-random-number-generator-drng-software-implementation-guide.html
Views: 0