コース目次 / 第2章
予測できる乱数 — 「ランダム」の中身を、疑う
時刻を種にした疑似乱数トークンを、同じ種を使って再現します。random.seed()による予測可能性と、暗号的に安全な乱数(crypto.randomBytes)との違いを理解します。
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章です。
この章はまだ完了していません。
保存できませんでした(プライベートブラウズ中かもしれません)。この端末に進捗は残りませんが、先へは進めます。