(1時間4,000円の伴走型ビデオチャット指導も受付中!)
安全なパスワード生成の鍵:Python "secrets" モジュール徹底解説
![]()
【Yuki】
Hirokiくん、こんにちは。今日はPythonで「セキュリティ」に関わる、とても大切なお話をしようと思います。プログラミングをしていると、ランダムな数字や文字列を作りたい場面がよくありますよね。でも、その使い道を間違えると、少し危ないことになってしまうかもしれないんです……。
![]()
【Hiroki】
Yukiさん、こんにちは!ランダムな値、よく使います。ゲームのサイコロの目を作るときに import random を使って random.randint() とかを使っています。それが危ないってことですか……?
![]()
【Yuki】
そうですね、ゲームなら問題ないのですが……もしそれが「パスワード」や「認証用のトークン」を作るためのものだとしたら、random モジュールは使わないほうがいいんです。今回は、セキュリティのために用意された "secrets" モジュール について、一緒に学んでいきましょう。
なぜ "random" ではいけないのか
![]()
【Yuki】
まず、なぜ random モジュールがセキュリティに向かないのかをお話ししますね。random モジュールで生成される乱数は「擬似乱数」と呼ばれていて、実は「決定論的」なアルゴリズムに基づいています。
![]()
【Hiroki】
決定論的……? つまり、予測ができるってことですか?
![]()
【Yuki】
ええ、その通りです。random モジュールは「メルセンヌ・ツイスタ」というアルゴリズムを使っているのですが、これは生成された値をいくつか観察していると、次に出る値を高い確率で予測できてしまうという性質があるんです。悪い人がそれを解析したら、ユーザーのパスワードや秘密のURLを言い当てられてしまうかもしれません……。
![]()
【Hiroki】
それは怖いですね。ゲームのアイテムドロップなら予測されても致命的ではないですけど、銀行の暗証番号やログインパスワードがバレたら大変です。
![]()
【Yuki】
そうなんです。だからこそ、Python 3.6から "secrets" モジュール が導入されました。このモジュールは、OSが提供する「より安全な乱数生成器」を使っていて、予測が極めて困難なようになっています。これを「暗号論的な擬似乱数生成器(CSPRNG)」と呼びます。
secrets モジュールの基本的な使い方
![]()
【Yuki】
では、実際に secrets を使ってみましょう。まずは、リストの中から要素をランダムに選ぶ方法です。random.choice() と使い方は似ていますが、中身の安全性は全然違います。
![]()
【Hiroki】
使い方が似ているなら、僕でもすぐに覚えられそうです!
![]()
【Yuki】
そう言ってもらえると嬉しいです。まずはコードを書いてみますね。
import secrets
import string
# パスワードに使う文字の候補を定義します
alphabet = string.ascii_letters + string.digits
# リストから1つ安全に選択します
password_char = secrets.choice(alphabet)
print(f"選ばれた文字: {password_char}")
![]()
【Hiroki】
本当だ、random.choice() と同じ感覚で使えますね。これなら、Webサービスで仮パスワードを発行するときなんかに役立ちそうです。
![]()
【Yuki】
ええ、その通りだと思います。さらに、特定の範囲の数字を生成するときは secrets.randbelow() を使います。
# 0以上100未満の整数を安全に生成します
number = secrets.randbelow(100)
print(f"安全な数値: {number}")
![]()
【Hiroki】
random.randint(0, 99) みたいな感じですね。でも、randbelow(100) の方が「100未満」というのが直感的で分かりやすい気がします。
セキュリティトークンの生成
![]()
【Yuki】
ここからは、より実践的な使い方を見ていきましょう。Web開発などをしていると、ユーザーのメールアドレス確認用URLや、APIキーのような「トークン」が必要になることがありますよね。
![]()
【Hiroki】
はい!よくメールで送られてくる「こちらのリンクをクリックして登録を完了してください」というURLの末尾についている、長い英数字のことですよね。
![]()
【Yuki】
そうです。あのような文字列を生成するために、secrets には専用の関数が用意されています。代表的なものを2つ紹介しますね。
# 16進数の文字列を生成します(引数はバイト数です)
token_hex = secrets.token_hex(16)
print(f"16進数トークン: {token_hex}")
# URLに適した文字列を生成します(Base64エンコード)
token_url = secrets.token_urlsafe(16)
print(f"URL用トークン: {token_url}")
![]()
【Hiroki】
へぇ!token_urlsafe() って便利ですね。Base64っていうと、記号とかも混ざるイメージですけど、URLで使っても大丈夫なんですか?
![]()
【Yuki】
はい。token_urlsafe() は、URLの中で特別な意味を持ってしまう記号(「/」や「+」など)を、安全な記号に置き換えてくれるんです。わたしもWebの仕組みを考えるとき、この関数にはいつも助けられています……。
![]()
【Hiroki】
なるほど。自分で変換処理を書かなくていいのは、ミスも減らせて安心ですね。
実践:安全なパスワード生成器を作ってみる
![]()
【Yuki】
せっかくなので、これまでの知識を組み合わせて「安全なパスワード生成器」の関数を作ってみましょう。Hirokiくん、どんなルールで作りたいですか?
![]()
【Hiroki】
えーっと、英大文字、英小文字、数字、あと記号が最低1つは入っている、8文字以上のパスワードがいいです!
![]()
【Yuki】
いい条件ですね。それを実現するには、単純にループで secrets.choice() を使うだけではなく、条件を満たすまで繰り返すような工夫が必要になるかもしれません。少し複雑ですが、一緒に見てみましょう。
import secrets
import string
def generate_secure_password(length=12):
if length < 8:
return "パスワードは8文字以上を推奨します"
# 使用する文字のセット
alphabet = string.ascii_letters + string.digits + string.punctuation
while True:
# 指定された長さのパスワードを生成
password = ''.join(secrets.choice(alphabet) for _ in range(length))
# 条件を満たしているかチェック
# (少なくとも1つの小文字、大文字、数字、記号が含まれているか)
if (any(c.islower() for c in password)
and any(c.isupper() for c in password)
and any(c.isdigit() for c in password)
and any(c in string.punctuation for c in password)):
return password
# 実行
new_password = generate_secure_password(16)
print(f"生成されたパスワード: {new_password}")
![]()
【Hiroki】
おお……! while True で、条件を満たすまで作り直しているんですね。secrets.choice を使っているから、一つ一つの文字が予測されにくいし、全体としても強固なパスワードになりそうです。
![]()
【Yuki】
はい。このように、「絶対に予測されてはいけないもの」を作るときは、secrets を使うのが今のPythonの標準なんです。
タイミング攻撃を防ぐ "compare_digest"
![]()
【Yuki】
最後にもう一つ、少し高度な機能を教えますね。パスワードを確認するとき、普通は if input_password == saved_password: のように書くと思います。でも、実はこれすらも、厳密にはセキュリティリスクになることがあるんです。
![]()
【Hiroki】
ええっ! 文字列を比べるだけでリスクになるんですか?
![]()
【Yuki】
「タイミング攻撃」という手法があるんです。文字列の比較(==)は、左から順番に文字をチェックしていって、違う文字が見つかった瞬間に「偽」と判断して処理を終えます。つまり、「最初の1文字目が合っている場合」と「全く合っていない場合」では、処理が終わるまでの時間に、ごくごく僅かな差が出るんです。
![]()
【Hiroki】
その僅かな時間の差を測って、パスワードを推測しちゃうってことですか……? 人間の感覚では無理でも、コンピュータならできちゃいそうですね。
![]()
【Yuki】
その通りです。それを防ぐために、secrets.compare_digest() という関数があります。これは、内容がどうであれ、常に同じ時間をかけて比較を行ってくれるんです。
# 秘密のトークンを安全に比較します
actual_token = "secret-api-key-12345"
user_input = "secret-api-key-12345"
if secrets.compare_digest(actual_token, user_input):
print("認証成功です")
else:
print("認証失敗です")
![]()
【Hiroki】
なるほど……。セキュリティの世界って、本当に奥が深いんですね。ただランダムにするだけじゃなくて、比べる時まで気を配る必要があるなんて。
![]()
【Yuki】
そうですね。わたしも、こういう細かい配慮が積み重なって、誰かの大切なデータを守っているんだと思うと、なんだか少し誇らしい気持ちになります……。
まとめ
![]()
【Yuki】
今日のまとめです。
1. セキュリティに関わる用途(パスワード、トークン、秘密のURLなど)には、random ではなく必ず secrets を使うこと。
2. secrets.choice() や secrets.randbelow() で安全な値を選べる。
3. secrets.token_hex() や secrets.token_urlsafe() は、APIキーやURL用トークンの生成に最適。
4. 文字列の比較には、タイミング攻撃を防ぐ secrets.compare_digest() を検討する。
もっと詳しく知りたいときは、公式ドキュメントを見てみてくださいね。 Python 公式ドキュメント: secrets --- 機密を扱うための安全な乱数を生成する
![]()
【Hiroki】
ありがとうございます!これからは、使い道に合わせて random と secrets をしっかり使い分けようと思います。Yukiさん、今日も丁寧に教えてくれてありがとうございました!
![]()
【Yuki】
どういたしまして。Hirokiくんがセキュリティに興味を持ってくれて嬉しいです。また分からないことがあったら、いつでも聞いてくださいね……。夜の方が、わたしもゆっくりお話しできるかもしれません。
![]()
【Hiroki】
はい!またよろしくお願いします!
この記事では基礎を解説しましたが、実務においては「もっと複雑なデータを扱いたい」「独自のシステムに組み込みたい」といった、個別の課題に直面することも多いはずです。
「自分で書く時間は最小限に抑え、プロの品質でツールを完成させたい」という方は、ぜひ一度ご相談ください。
- 専門家の知見に基づいた、保守性の高いコード設計
- AIの専門家による、Gemini API等の最新AIを組み合わせた高度な自動化
- ChatGPT等が生成したコードのデバッグ・最適化
「教わる」だけでなく「形にする」パートナーとして、フリーランスエンジニアのmei_13が最短ルートでの解決をサポートします。
初心者から始められるPythonレッスン
● 月額4,000円で質問し放題!!
● 完全オンライン
● 翌日までには必ず返信
● 挫折しない独自の学習メソッド
● 圧倒的高評価!!
● テキストベースで時間を選ばない
● 4,000円/60分の伴走型ビデオチャット指導も可能
● 高品質なサンプルコード
詳細はこちら

