Build a Secure Password Generator in Python

Published on: July 10, 2026
Reading time: 5 minutes
Criptografia e segurança de dados em Python

A password generator is a useful beginner project because it combines strings, loops, functions, command-line input, and validation. It is also a security tool, so one design decision matters immediately: passwords must come from a cryptographically secure random source. Python’s ordinary random module is designed for simulations and games, not secrets.

In this tutorial, you will build a secure password generator in Python with the standard secrets and string modules. You will add configurable length, character rules, validation, a command-line interface, and tests. The project generates passwords locally and never stores them.

Why use secrets instead of random?

The random module is deterministic and can reproduce sequences from its internal state. That is useful for testing and modeling, but dangerous for passwords, reset links, API keys, and authentication tokens. The official Python secrets documentation explicitly recommends secrets for security-sensitive randomness.

The secrets.choice() function selects an item using the operating system’s secure randomness source. The Python secrets guide covers tokens and related use cases.

Build the simplest secure generator

import secrets
import string

alphabet = string.ascii_letters + string.digits + string.punctuation
password = "".join(secrets.choice(alphabet) for _ in range(20))

print(password)

The string module provides stable ASCII character groups such as lowercase letters, uppercase letters, digits, and punctuation. The official string constants reference lists each group.

A length of 20 characters from a broad alphabet produces a strong random password for most ordinary user accounts, but a website may reject certain punctuation or impose a maximum length. The generator should allow policies without weakening the randomness source.

Create a reusable function

import secrets
import string

def generate_password(length: int = 20) -> str:
    if length < 12:
        raise ValueError("length must be at least 12")

    alphabet = string.ascii_letters + string.digits + string.punctuation
    return "".join(secrets.choice(alphabet) for _ in range(length))

The function validates its input and returns the password instead of printing it. That separation makes the logic reusable in a graphical interface, CLI, web application, or test. The Python functions guide explains parameters and return values.

Guarantee required character categories

Some systems require at least one lowercase letter, uppercase letter, digit, and punctuation character. One safe strategy is rejection sampling: generate a complete secure candidate, test it, and repeat only when it does not meet the policy.

import secrets
import string

def meets_policy(password: str) -> bool:
    return (
        any(character.islower() for character in password)
        and any(character.isupper() for character in password)
        and any(character.isdigit() for character in password)
        and any(character in string.punctuation for character in password)
    )

def generate_password(length: int = 20) -> str:
    if length < 12:
        raise ValueError("length must be at least 12")

    alphabet = string.ascii_letters + string.digits + string.punctuation

    while True:
        candidate = "".join(
            secrets.choice(alphabet)
            for _ in range(length)
        )

        if meets_policy(candidate):
            return candidate

The loop does not “fix” positions after generation, so every accepted result still comes from uniformly selected characters conditioned on the policy. For ordinary lengths, valid candidates are found quickly.

The calls to any() combine naturally with generator expressions. Review Python comprehensions to understand the iteration syntax.

Allow selected character groups

A configurable generator can include or exclude categories. However, it must reject an empty alphabet and impossible policies.

import string

def build_alphabet(
    *,
    lowercase: bool = True,
    uppercase: bool = True,
    digits: bool = True,
    symbols: bool = True,
) -> str:
    parts = []

    if lowercase:
        parts.append(string.ascii_lowercase)
    if uppercase:
        parts.append(string.ascii_uppercase)
    if digits:
        parts.append(string.digits)
    if symbols:
        parts.append(string.punctuation)

    alphabet = "".join(parts)

    if not alphabet:
        raise ValueError("select at least one character group")

    return alphabet

Keyword-only arguments make calls self-documenting. A site that rejects symbols can request letters and digits, but increasing length then becomes even more important because the alphabet is smaller.

Exclude ambiguous characters

Some passwords are typed manually from paper or another screen. Characters such as zero and capital O, or lowercase l and capital I, may be confused. You can remove them for usability while compensating with sufficient length.

AMBIGUOUS = set("0O1lI")

def remove_ambiguous(alphabet: str) -> str:
    return "".join(
        character
        for character in alphabet
        if character not in AMBIGUOUS
    )

A set makes repeated membership checks clear and efficient. Learn why in the guide to the Python in operator.

Create a complete generator

import secrets
import string

AMBIGUOUS = set("0O1lI")

def generate_password(
    length: int = 20,
    *,
    symbols: bool = True,
    avoid_ambiguous: bool = False,
) -> str:
    if length < 12:
        raise ValueError("length must be at least 12")

    groups = [
        string.ascii_lowercase,
        string.ascii_uppercase,
        string.digits,
    ]

    if symbols:
        groups.append(string.punctuation)

    alphabet = "".join(groups)

    if avoid_ambiguous:
        alphabet = "".join(
            character
            for character in alphabet
            if character not in AMBIGUOUS
        )

    while True:
        candidate = "".join(
            secrets.choice(alphabet)
            for _ in range(length)
        )

        required = [
            any(character.islower() for character in candidate),
            any(character.isupper() for character in candidate),
            any(character.isdigit() for character in candidate),
        ]

        if symbols:
            required.append(
                any(character in string.punctuation for character in candidate)
            )

        if all(required):
            return candidate

The function uses a secure choice for every character, enforces a minimum length, and validates the selected policy. It does not write the generated value to a file or log.

Add a command-line interface

A CLI makes the project convenient without mixing parsing logic into the generator. The standard argparse module provides help text and validation.

import argparse

def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Generate a secure random password."
    )
    parser.add_argument(
        "--length",
        type=int,
        default=20,
        help="password length, minimum 12",
    )
    parser.add_argument(
        "--no-symbols",
        action="store_true",
        help="exclude punctuation characters",
    )
    parser.add_argument(
        "--avoid-ambiguous",
        action="store_true",
        help="exclude characters such as 0, O, 1, l, and I",
    )
    return parser.parse_args()

def main() -> None:
    args = parse_args()
    password = generate_password(
        args.length,
        symbols=not args.no_symbols,
        avoid_ambiguous=args.avoid_ambiguous,
    )
    print(password)

if __name__ == "__main__":
    main()

Save the code in password_generator.py and run python password_generator.py --length 24. The complete argparse CLI guide explains flags, choices, subcommands, and packaging.

Generate passphrases

A passphrase combines randomly selected words. It can be easier to type and remember, but security depends on the word list size, number of independently selected words, and whether selection is truly random. Never use a short hand-picked list for an important credential.

import secrets

WORDS = [
    "anchor", "bamboo", "copper", "dolphin",
    "ember", "forest", "galaxy", "harbor",
    "island", "jungle", "kernel", "lantern",
]

def generate_demo_passphrase(words: int = 5) -> str:
    if words < 4:
        raise ValueError("use at least four words")

    return "-".join(secrets.choice(WORDS) for _ in range(words))

This small list is only a programming demonstration, not a recommendation for real passwords. A production passphrase generator needs a large, reviewed word list and a documented entropy calculation.

Passwords are generated, not stored

An authentication system should not save recoverable passwords in plain text, logs, configuration files, analytics, or reversible encryption. It should store a password hash produced by a password-specific algorithm and a unique salt. Use a maintained authentication framework or a well-reviewed password-hashing library instead of inventing cryptography.

The article on secure password hashing in Python explains the distinction between generating a password and storing a verifier.

Test the generator

Tests should verify rules, not predict secure random output. Do not seed or replace the secure source in normal production code merely to make a specific password appear.

def test_default_password() -> None:
    password = generate_password()

    assert len(password) == 20
    assert any(character.islower() for character in password)
    assert any(character.isupper() for character in password)
    assert any(character.isdigit() for character in password)
    assert any(character in string.punctuation for character in password)

def test_rejects_short_length() -> None:
    try:
        generate_password(8)
    except ValueError:
        pass
    else:
        raise AssertionError("expected ValueError")

For a cleaner test suite, use pytest.raises(). The Pytest beginner guide covers assertions, exceptions, fixtures, and test organization.

Common mistakes

  • Using random.choice() for passwords or tokens.
  • Generating a short password because it contains symbols.
  • Logging or saving generated passwords automatically.
  • Building a predictable pattern such as one uppercase letter followed by fixed categories.
  • Using a tiny word list for a supposedly secure passphrase.
  • Confusing password generation with secure password hashing.
  • Copying a generated password through untrusted tools or shared clipboard history.

Frequently asked questions

How long should a generated password be?

Longer is generally stronger when each character is selected independently and securely. Twenty or more characters is a practical default for many accounts, but follow the service’s rules and your organization’s security policy. Avoid reducing length merely to satisfy a complicated composition pattern.

Can I use random.SystemRandom?

It can access operating-system randomness, but secrets communicates security intent directly and provides convenient token functions. Prefer secrets for new security-sensitive code.

Should the generator guarantee every character type?

Only when a target system requires it. Security comes primarily from unpredictable selection and sufficient length, not from decorative complexity rules. A configurable generator can meet site requirements without weakening its random source.

Final thoughts

A secure Python password generator is small because the standard library handles the difficult randomness source. Use secrets, choose a generous default length, validate policies, avoid storing output, and keep password hashing as a separate authentication responsibility. The project is a good lesson in how a few correct design decisions matter more than a large amount of code.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Jogo da forca para iniciantes desenvolvido com Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Build a Hangman Game in Python

    Build a complete Hangman game in Python with random words, input validation, repeated-letter checks, lives, ASCII art, and replay support.

    Ler mais

    Tempo de leitura: 5 minutos
    10/07/2026
    Quiz interativo no terminal desenvolvido com Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Build a Terminal Quiz Game in Python

    Build a terminal quiz game in Python with questions, input validation, scoring, shuffled answers, replay support, JSON loading, and tests.

    Ler mais

    Tempo de leitura: 4 minutos
    10/07/2026
    Jogo de adivinhação de números desenvolvido com Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Build a Number Guessing Game in Python

    Build a number guessing game in Python with random numbers, input validation, hints, limited attempts, replay support, and clean functions.

    Ler mais

    Tempo de leitura: 5 minutos
    10/07/2026
    Desenhos para iniciantes usando Turtle em Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Python Turtle Drawing: Complete Beginner Guide

    Learn Python Turtle drawing from scratch: move the turtle, draw shapes, use colors, handle keys, and create a complete geometric

    Ler mais

    Tempo de leitura: 5 minutos
    10/07/2026
    Criação de jogos para iniciantes usando Pygame em Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Pygame for Beginners: Build Your First Game

    Learn Pygame from scratch: install it, create a window, handle events, move a player, detect collisions, add scoring, and build

    Ler mais

    Tempo de leitura: 6 minutos
    10/07/2026
    Ilustração de uma cobra com o logo do Python centralizado
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Build a Snake Game in Python with Turtle

    Build a complete Snake game in Python with Turtle. Add movement, food, collisions, body growth, scoring, and keyboard controls.

    Ler mais

    Tempo de leitura: 6 minutos
    10/07/2026