コース目次 / 第2章

予測できる乱数 — 「ランダム」の中身を、疑う

時刻を種にした疑似乱数トークンを、同じ種を使って再現します。random.seed()による予測可能性と、暗号的に安全な乱数(crypto.randomBytes)との違いを理解します。

第2章 / 全4章目安 約10分この章のゴール: 予測できる乱数生成を見破り、暗号的に安全な乱数との違いを言えるようになる

sec-auth で「セッションIDは推測できてはいけない」と学びました。この章は、その推測可能性が、どこから生まれるかを、実際に手を動かして見ます。

トークンを、発行する

gen_token.py として保存します。「ランダムな」トークンを発行する、よくある実装です。

import random

SEED_TIME = 1700000000  # 本来は time.time() のような「発行した瞬間の時刻」
random.seed(SEED_TIME)
token = ''.join(random.choice('0123456789abcdef') for _ in range(16))
print("発行されたトークン:", token)
print("フラグ: flag{" + token[:8] + "}")
python3 gen_token.py
発行されたトークン: aa6d3ac56007ec95
フラグ: flag{aa6d3ac5}

一見、ランダムに見えるトークンです。でも、これは本当にランダムでしょうか?

種(seed)を、知っていれば

random.seed(...) の中身に注目してください。「発行した瞬間のおおよその時刻」が使われています。もし攻撃者が、「だいたいこの時間帯に発行された」と分かっていたら——同じ種を、自分でも試せます。

import random
random.seed(1700000000)  # 発行時刻を推測して、同じ種を使う
token = ''.join(random.choice('0123456789abcdef') for _ in range(16))
print("再現したトークン:", token)
再現したトークン: aa6d3ac56007ec95

寸分違わず、同じトークンが再現できました。「ランダムに見える」ことと、「本当に予測不能である」ことは、まったく別です。random.seed(値) で種を固定すると、その種さえ分かれば、生成される値の並びは完全に決まってしまいます。時刻は、正確な値までは分からなくても、ある程度の範囲(数秒〜数分)まで絞り込めることが多く、実務ではその範囲を総当たりされます。

疑似乱数と、暗号的に安全な乱数

Python の random モジュールや、多くの言語の標準の乱数関数は、「見た目はランダムだが、種が分かれば完全に予測できる」疑似乱数(PRNG)です。速くて便利ですが、セキュリティトークンの生成には向きません。
sec-auth で使った crypto.randomBytes()(Node.js)や、Python の secrets モジュール(secrets.token_hex())は、暗号的に安全な乱数生成器です。予測を困難にする専用の仕組みを使っていて、種を知っていても再現できません。「乱数が要る」場面が、セキュリティに関わるかどうかで、使うべき関数が変わります。

持ち帰る一言

「ランダム」の中身を、確かめる。 疑似乱数は、種が分かれば完全に再現できます。セキュリティに関わる値(セッションID、トークン、パスワードリセット用のコードなど)には、暗号的に安全な乱数生成器を使う——sec-authで見た「強いセッションID」の裏にある理屈が、いま手を動かして分かりました。次はまとめて、このコースを締めます。

こうなっていればOK

卒業まであと1章です。

この章はまだ完了していません。