Build a Password Generator in Python
Why Password Security Matters
Think of a password like a lock on a door. A weak lock can be picked in seconds. The same goes for passwords — if your password is easy to guess, a computer can crack it almost instantly.
When a website asks you to create a password, it is trying to protect something valuable. A hacker might use a program that tries millions of password combinations per second. If your password is “123456” or “password”, those programs will find it in less than a second. But a truly random 16-character password with mixed character types would take longer than the age of the universe to guess.
The randomness — the unpredictability — is what makes a password hard to crack.
The Problem with Python’s random Module
You might already know that Python has a random module. It can pick random numbers and shuffle things around. Many beginners reach for random.choice() when they want to generate a password.
Here is the problem: the random module is designed for games, simulations, and statistical work. It uses an algorithm that produces numbers that look random but are actually predictable if you know where it started. The starting point is called a “seed.”
An attacker who figures out the seed can reproduce all the “random” values your program ever generated. That means they can recreate your passwords. This is not a theoretical risk — it is a real security flaw.
Python’s random module should never be used for anything security-related, including passwords, session tokens, or API keys.
Generating Secure Randomness with secrets
Python 3.6 introduced a module called secrets. This module taps into the operating system’s source of cryptographically secure randomness. Think of it as getting random data from a hardware device inside your computer that is designed to be truly unpredictable.
The secrets module gives you functions that are safe to use for passwords and security tokens:
secrets.choice(sequence)— picks one item at random from a list or stringsecrets.randbelow(n)— picks a random integer from 0 up to (but not including) nsecrets.randbits(k)— generates a number with k random bitssecrets.token_urlsafe(n)— creates a URL-safe random string
You also get secrets.compare_digest(a, b), which compares two strings in a way that prevents timing attacks — a technique where attackers measure how long a comparison takes to guess parts of a password.
For building character pools, the string module provides convenient constants:
string.ascii_letters— all letters, uppercase and lowercasestring.digits— the numbers 0 through 9string.punctuation— symbols like!,@,#, and&
Generate a Basic Password
Here is a simple example. We want an 8-character password using letters and digits.
First, we need to decide which characters are allowed. We combine the letters and digits into one long string, then pick characters from it one at a time.
import string
import secrets
def generate_password(length=8):
# Build a pool of all allowed characters
alphabet = string.ascii_letters + string.digits
# Pick 'length' random characters from the pool
# secrets.choice() returns one character each iteration
password = ''.join(secrets.choice(alphabet) for _ in range(length))
return password
pw = generate_password(8)
print(pw)
# example output: bK3mQa7p
Here is what happens step by step. First, alphabet becomes a string containing “abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789”. Then secrets.choice(alphabet) picks one character from that long string. We do that 8 times and join them together.
Each call to secrets.choice() uses the operating system’s secure randomness source, so each character is genuinely unpredictable.
Ensure Minimum Character Type Requirements
The basic generator above works, but it has a downside. Sometimes it produces a password with no uppercase letters, or no digits at all. For a user who expects at least one of each, this is frustrating.
We need a way to guarantee that our password meets certain rules. The technique is called rejection sampling. We generate a candidate password, check if it meets our rules, and if not, we throw it away and try again.
import string
import secrets
def generate_password_with_requirements(length=10):
# Our character pool: all letters and digits
alphabet = string.ascii_letters + string.digits
while True:
# Generate a candidate password
password = ''.join(secrets.choice(alphabet) for _ in range(length))
# Count how many of each type this password has
has_lower = any(c.islower() for c in password) # at least one lowercase?
has_upper = any(c.isupper() for c in password) # at least one uppercase?
digit_count = sum(c.isdigit() for c in password) # how many digits?
# Check all our requirements
if has_lower and has_upper and digit_count >= 3:
return password
pw = generate_password_with_requirements()
print(pw)
# example output: qR7tU3vB2w
This function keeps generating passwords until it finds one with at least one lowercase letter, at least one uppercase letter, and at least three digits. The any() function returns True if any element in the collection satisfies the condition. The sum() function counts how many characters are digits.
One thing to watch out for: if your length is too small for your requirements, this loop could run for a very long time. For example, asking for 3 digits in a 4-character password is extremely restrictive. Make sure your length is comfortably larger than your constraints.
Build a Memorable Passphrase
Short passwords with random characters are hard to type and hard to remember. An alternative approach is passphrases — a sequence of random words like “brave clock horse lemon”.
These are much easier to remember than “xK9#mP2q”. They are also often stronger because each word adds many bits of randomness. Four common words give you far more possible combinations than an 8-character mixed password.
Here is how to build one:
import secrets
def generate_passphrase(word_count=4):
# Try to load words from the system's dictionary (Linux)
# Each line in this file is one word
try:
with open('/usr/share/dict/words') as f:
# Read every line, strip the newline, keep words with 3+ characters
words = [line.strip() for line in f if len(line.strip()) >= 3]
except FileNotFoundError:
# If the dictionary file does not exist, use this backup list
words = ['apple', 'brave', 'clock', 'dance', 'eagle', 'flame',
'grape', 'horse', 'ivory', 'joker', 'kayak', 'lemon']
# Pick 'word_count' random words and join them with spaces
return ' '.join(secrets.choice(words) for _ in range(word_count))
phrase = generate_passphrase(4)
print(phrase)
# example output: brave clock horse lemon
On a Linux system, /usr/share/dict/words is a file containing thousands of common English words. We read it in, filter for words at least 3 letters long, and then pick random words from the list. If the file does not exist (for example on Windows or macOS), we fall back to a small built-in list.
Common Mistakes to Avoid
Using random instead of secrets. This is the biggest mistake. If you use random.choice() for passwords, an attacker can reproduce them. Always use secrets.choice().
Accidentally including whitespace. The string.printable constant looks useful because it contains every printable character. But it also includes spaces and tabs, which will sneak into your password and may cause issues when you paste the password somewhere. Stick with string.ascii_letters + string.digits + string.punctuation for explicit control.
No guaranteed character diversity. A naive approach may produce passwords with no uppercase letters or no numbers. If you need guaranteed minimums, use the rejection sampling pattern from the example above.
Base64 padding in tokens. secrets.token_urlsafe() produces URL-safe strings, but when the number of bytes is not evenly divisible by 3, the output gets padded with = characters. Strip them if you need clean output: token.rstrip('=').
Empty character pools. secrets.choice() raises an error if you give it an empty sequence. Always make sure your alphabet has at least one character in it.
Summary
Generating secure passwords in Python is straightforward once you know which module to use. Always reach for secrets when you need randomness for security purposes. The string module makes it easy to build a character pool from letters, digits, and punctuation.
For simple cases, secrets.choice() with a character alphabet does the job. When you need guaranteed minimums for character types, use a rejection sampling loop. And if you prefer something more human-readable, passphrases built from random words are a strong and memorable alternative.
The secrets module has been part of Python since version 3.6, so it is available in any modern Python installation.
See Also
- The
stringmodule — character constants for building alphabet pools - The
randommodule — whyrandomshould never be used for passwords - The
hashlibmodule — hashing passwords after generation