Q1パスワードリセットトークンを生成するときに使うべきモジュールはどれですか?
random と secrets — 乱数の使い分け
random.randint/choice/shuffle/sampleとseedによる再現可能な疑似乱数、secrets.token_hex/token_urlsafeによる予測不能なトークンを、用途で使い分ける基準を実行で学びます。
乱数を扱う 2 モジュールを整理します。randomはテスト・ゲーム・シミュレーション用の疑似乱数(seedで再現可能)、secretsはパスワードリセットトークン・API キーなどセキュリティ用の予測不能な乱数(OS 提供の暗号乱数源)。用途で必ず使い分けるのが鉄則です。
random と secrets の違い
両モジュールは「乱数を返す」点では同じですが、仕組みと用途が完全に分かれています。randomはメルセンヌ・ツイスター(広く使われている疑似乱数生成アルゴリズム)で、random.seed(値)を呼べば同じ並びを再現できます。一方secretsはOS が提供する暗号乱数源(/dev/urandom等)から取得するので、seedの概念はなく、毎回予測不能な値が返ります。
テストやゲームのように「結果が外部に漏れても被害が出ない」場面ではrandom、パスワード・トークン・セッション ID のように「外部に予測されたら被害が出る」場面ではsecretsを使う、と覚えてください。
| 観点 | random | secrets |
|---|---|---|
| 乱数源 | メルセンヌ・ツイスター (アルゴリズム) | OS の暗号乱数源 (/dev/urandom 等) |
| 再現性 | seed で同じ並びを再現できる | 再現不可 (毎回違う値) |
| 速度 | 高速 | 比較的遅い (用途的に問題にならない) |
| 向く用途 | テスト・ゲーム・シミュレーション | トークン・パスワード・セッション ID |
random — 基本の乱数 (randint / uniform / choice)
randomモジュールの基本は整数の乱数 / 小数の乱数 / リストから 1 件選ぶの 3 つです。random.randint(下限, 上限)で両端を含む整数、random.uniform(下限, 上限)で指定範囲の float、random.choice(リスト)でリストから要素を 1 つ取り出すことができます。
まずはseedを使わずに、これらの基本関数の振る舞いを確認します。
| 関数 | 戻り値 | 使いどころ |
|---|---|---|
| random.randint(a, b) | a 〜 b の整数 (両端含む) | サイコロ、テスト用 ID |
| random.uniform(a, b) | a 〜 b の float | ノイズ、確率的シミュレーション |
| random.choice(seq) | シーケンスから 1 つ | メニューから 1 件抽出 |
random — シードによる再現性とコレクション操作
ここからはrandom.seedによるテスト再現性と、コレクション操作系の関数(shuffle / sample)を扱います。random.seed(N)を呼んでから乱数関数を使うと同じシード値なら必ず同じ並びの値が出ます。random.shuffle(リスト)は元のリストの並びをランダムに入れ替え、random.sample(リスト, k)は重複なしで k 件取り出した新しいリストを返します。
| 関数 | 戻り値 | 使いどころ |
|---|---|---|
| random.seed(value) | None (内部状態を初期化) | テストの再現性確保 |
| random.shuffle(seq) | None (元のリストを並べ替える) | リストの順番をランダムに入れ替える |
| random.sample(seq, k) | 重複なし k 件のリスト | アンケートから k 人抽選 |
secrets — セキュリティに使える強い乱数
前セクションのrandomはテスト用の疑似乱数で、内部状態が漏れると次の値が予測されてしまいます。パスワードリセットトークン・API キー・セッション IDのような「攻撃者に予測されたら被害が出る用途」では、OS 提供の暗号学的乱数源を使うsecrets モジュールを選びます。
secretsの API はrandomよりずっと小ぶりで、ランダムなバイト列を 16 進文字列や URL セーフな文字列に変換するヘルパー関数が中心です。
| 関数 | 戻り値 | 使いどころ |
|---|---|---|
| secrets.token_bytes(n) | n バイトのランダムな bytes | 暗号鍵、内部用バイナリトークン |
| secrets.token_hex(n) | 2*n 文字の 16 進文字列 | ログインセッション ID |
| secrets.token_urlsafe(n) | URL セーフな文字列 (Base64 風) | リセットリンクの URL に埋める |
| secrets.choice(seq) | seq から暗号学的に 1 件 | ランダム表示順を予測されたくない場面 |
| secrets.compare_digest(a, b) | True / False (タイミング攻撃耐性) | ハッシュ・トークン比較 (== の代わり) |
理解度チェック
まずは1問ずつ答えてみましょう。
Q2random.seed(42)を呼んでからrandom.randint(1, 100)を実行し、もう一度同じことをすると何が起きますか?
Q3ランダムに並び替えたリストをテスト用に再現できるようにしたいとき、最も適切なのはどれですか?