Pythonでパスワード生成:標準ライブラリだけで使える10パターン

Pythonの標準ライブラリだけで、パスワード生成に必要なものは全部揃います。pip installは不要。この記事では、基本的なランダム文字列から実務レベルの一括生成まで、10パターンのパスワード生成コードを紹介します。すべてのコードはPython 3.6以降でそのまま動きます。

最初にひとつだけ。セキュリティ関連の値を生成するときはrandomではなくsecretsを使いましょう。見た目はほぼ同じですが、randomはシミュレーション用で、認証情報の生成には向いていません。見た目が似ているからこそ、間違えやすいポイントです。

1. 基本形:ランダムパスワード

英小文字・大文字・数字・記号から、指定した長さのパスワードを生成する最もシンプルな形です。

basic_password.py
import secrets

def generate_password(length=16):
    chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%"
    return "".join(secrets.choice(chars) for _ in range(length))

if __name__ == "__main__":
    for i in range(5):
        print(f"Password {i+1}: {generate_password()}")

十分実用的ですが、「必ず英大文字・数字・記号を含む」とは限りません。文字集合には含まれていても、たまたま数字が1文字も入らないことは普通にあります。サービス側に「記号必須」等の条件があるなら、パターン5の方が確実です。

2. 紛らわしい文字を除外

0O1lのような、見分けがつきにくい文字を除外したい場合に便利です。

no_confusing.py
import secrets
import string

def generate_password(length=16, excluded="0OIl1"):
    all_chars = string.ascii_letters + string.digits + "!@#$%"
    chars = "".join(c for c in all_chars if c not in excluded)
    if not chars:
        raise ValueError("使用可能な文字がありません。")
    return "".join(secrets.choice(chars) for _ in range(length))

if __name__ == "__main__":
    for i in range(5):
        print(f"Password {i+1}: {generate_password()}")

除外しすぎると候補数が減って強度が少し下がります。長さを1〜2文字増やせば簡単に補えますが、「見やすさ」と「総当たり耐性」は少し綱引きになります。

現場では「強いが読めないパスワード」よりも、少し長めで読み間違えにくいパスワードの方が運用全体として安全なことがあります。電話口で「大文字のアイです」「小文字のエルです」が始まると、だいたい平和が遠のきます。

3. プレフィックス付きランダム

USER_APIKEY_のような固定文字列の後ろに、ランダム文字列を付けたいときに使えます。

prefixed.py
import secrets
import string

def generate_with_prefix(prefix="USER_", random_length=12):
    chars = string.ascii_letters + string.digits
    tail = "".join(secrets.choice(chars) for _ in range(random_length))
    return prefix + tail

if __name__ == "__main__":
    for i in range(5):
        print(f"Key {i+1}: {generate_with_prefix()}")

接頭辞が固定なので、実質的なランダム部分の長さだけが強度を決めます。全体18文字でも、固定6文字+ランダム12文字なら、強度はランダム12文字相当です。プレフィックスはログの判別用であって、攻撃者を混乱させるものではありません。

4. プレフィックス付き一括生成

同じ接頭辞のキーやコードをまとめて複数件作りたい場合向けです。

bulk_prefixed.py
import secrets
import string

def generate_batch(prefix="APIKEY_", random_length=16, count=5):
    chars = string.ascii_letters + string.digits
    return [
        prefix + "".join(secrets.choice(chars) for _ in range(random_length))
        for _ in range(count)
    ]

if __name__ == "__main__":
    for i, key in enumerate(generate_batch(), 1):
        print(f"Key {i}: {key}")

重複チェックは入っていません。ランダム部分が十分に長ければ衝突確率は天文学的に低いですが、数百万件レベルならパターン10の方が安心です。

大量発行で本当に怖いのは、重複そのものより「誰にどの値を渡したかの対応表が壊れること」だったりします。強いコードを作るより、生成した一覧を「とりあえずデスクトップに平文保存」しない方が、だいたい先に効きます。

5. 文字種必須の強力版

英大文字・小文字・数字・記号を必ず1文字以上含める、条件付きの強力パスワード生成です。ログインパスワード用途ではこのパターンがかなり扱いやすいです。

strong_password.py
import secrets
import string
import random

def generate_strong_password(length=16):
    if length < 4:
        raise ValueError("length は 4 以上である必要があります。")
    lower = string.ascii_lowercase
    upper = string.ascii_uppercase
    digits = string.digits
    symbols = "!@#$%^&*"
    parts = [
        secrets.choice(lower),
        secrets.choice(upper),
        secrets.choice(digits),
        secrets.choice(symbols),
    ]
    all_chars = lower + upper + digits + symbols
    parts += [secrets.choice(all_chars) for _ in range(length - 4)]
    random.shuffle(parts)
    return "".join(parts)

if __name__ == "__main__":
    for i in range(5):
        print(f"Password {i+1}: {generate_strong_password()}")

シャッフルにrandom.shuffle()を使っている点が気になるかもしれません。すでにランダムに選ばれた文字の「位置」を決めるだけなので実務上の影響はまずありませんが、厳密にそろえたければFisher–Yatesシャッフルをsecrets.randbelow()で回す手もあります。

よくあるミスは、先頭1文字目を必ず大文字、2文字目を必ず数字、のように位置を固定してしまうこと。規則性を作るのは、ランダム性の真逆です。

6. 読みやすさ重視版

紛らわしい文字と記号を除いた、人に伝えやすいパスワード。初期パスワードや一時発行コードとの相性が良い方式です。

readable.py
import secrets
import string

def generate_readable(length=16):
    excluded = "0OIl1"
    chars = "".join(
        c for c in string.ascii_letters + string.digits
        if c not in excluded
    )
    return "".join(secrets.choice(chars) for _ in range(length))

if __name__ == "__main__":
    for i in range(5):
        print(f"Password {i+1}: {generate_readable()}")

セキュリティは理論値だけではなく、人間が正しく扱えるかでも決まります。最強の文字列でも、3回打ち間違えて付箋に書かれたら、だいぶ雲行きが怪しいです。

7. 単語つなぎパスフレーズ

ランダムな単語をつないで、覚えやすくて長い文字列を作ります。XKCD #936で有名になったアプローチです。

passphrase.py
import secrets

WORDS = [
    "river", "cloud", "apple", "stone", "forest",
    "ocean", "light", "shadow", "iron", "spark",
    "maple", "crane", "drift", "ember", "frost",
    "bloom", "cedar", "ridge", "pearl", "storm",
]

def generate_passphrase(word_count=4):
    return "-".join(secrets.choice(WORDS) for _ in range(word_count))

if __name__ == "__main__":
    for i in range(5):
        print(f"Passphrase {i+1}: {generate_passphrase()}")

サンプルの単語リストは意図的に短くしています。本気で使うならEFF Dicewareリスト(7,776語)のようなものを使いましょう。4語で約51ビットのエントロピーが得られます。

パスフレーズで一番やってはいけないのは、自分で単語を選ぶこと。「覚えやすいから」と好きな単語を並べた瞬間、推測されやすさが跳ね上がります。ランダム16文字は強いですが、毎日入力するには手首ではなく気力が先に疲れます。こういう場面では、パスフレーズの方が現実的な選択肢です。

8. URL安全トークン

APIキーやメール認証トークン、ワンタイムURLなどに向いた、URL内にそのまま埋め込めるランダム文字列です。

url_token.py
import secrets
import base64

def generate_token(byte_length=24):
    raw = secrets.token_bytes(byte_length)
    return base64.urlsafe_b64encode(raw).decode().rstrip("=")

if __name__ == "__main__":
    for i in range(5):
        print(f"Token {i+1}: {generate_token()}")

「URL安全」は「URLで扱いやすい」という意味であって、「漏れても安全」という意味ではありません。URLに含めたトークンは、ブラウザ履歴、サーバーログ、Refererヘッダーなどに残る可能性があります。可能であれば有効期限を短くしましょう。

9. 桁数・件数指定の一括生成

長さと件数を指定してまとめて作る、シンプルなユーティリティです。

batch.py
import secrets
import string

def generate_batch(length=16, count=5):
    chars = string.ascii_letters + string.digits + "!@#$%"
    return [
        "".join(secrets.choice(chars) for _ in range(length))
        for _ in range(count)
    ]

if __name__ == "__main__":
    for i, pw in enumerate(generate_batch(length=20, count=5), 1):
        print(f"Password {i}: {pw}")

件数が多い場合は、標準出力ではなくファイルに直接書き出すことをおすすめします。ターミナルのスクロールバッファは意外としぶとく残りますし、共有セッションで500個のパスワードをスクロールバックされるのは、誰も嬉しくないサプライズです。

10. 重複なし一括生成

複数ユーザーに一括発行するときなど、重複を確実に避けたい場合のパターンです。

unique_batch.py
import secrets
import string

def generate_unique(length=16, count=10):
    chars = string.ascii_letters + string.digits + "!@#$%"
    result = set()
    while len(result) < count:
        result.add("".join(secrets.choice(chars) for _ in range(length)))
    return list(result)

if __name__ == "__main__":
    for i, pw in enumerate(generate_unique(length=16, count=5), 1):
        print(f"Password {i}: {pw}")

16文字の英数字+記号なら衝突確率は天文学的に低いですが、length=4, count=100000のような極端な条件を渡すと、候補の母集団に対して要求件数が大きすぎてループが伸びます。入力値のサニティチェックを入れておくと安心です。

一括発行で本当に怖いのは、パスワードの重複ではなく、「誰にどのパスワードを渡したかの対応表が壊れること」です。乱数生成器は簡単な部分。台帳管理の方が先に機能停止しがちです。

まとめ

今回紹介した10パターンはすべてPython標準ライブラリ(secretsstringrandombase64)だけで動作します。追加のpip installは不要です。

ログインパスワードにはパターン5(文字種必須版)、APIトークンにはパターン8(URL安全版)、人間が毎日入力するマスターパスワードにはパターン7(パスフレーズ)がおすすめです。

そして忘れてはいけないのは、強いパスワードを生成するのは簡単な方だということ。安全に保存し、安全に渡し、適切なタイミングでローテーションする——本当の仕事はそこから始まります。

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です