Editor

Password Generator in Python

Beginner · Useful tools · about 35 minutes to build · 106 lines

Choose a length and the kinds of characters to use, and get random passwords with a note on how strong they are.

The program

Press Run and answer its questions in the box that appears under the code.

password_generator.py
"""A password generator that uses the secrets module for safe randomness."""
import math
import secrets
import string
MIN_LENGTH = 8
MAX_LENGTH = 64
DEFAULT_LENGTH = 16
MAX_COUNT = 10
SYMBOLS = "!#$%&*+-=?@^_"
# Asking questions
def ask_yes_no(prompt, default):
"""Return True or False; pressing Enter gives the default."""
hint = "Y/n" if default else "y/N"
while True:
answer = input(f"{prompt} ({hint}): ").strip().lower()
if answer == "":
return default
if answer in ("y", "yes"):
return True
if answer in ("n", "no"):
return False
print(" Please answer y or n.")
def ask_number(prompt, low, high, default):
"""Return a whole number from low to high; pressing Enter gives the default."""
while True:
text = input(f"{prompt} ({low}-{high}, Enter for {default}): ").strip()
if text == "":
return default
try:
number = int(text)
except ValueError:
print(" Please type a whole number.")
continue
if low <= number <= high:
return number
print(f" Please choose a number from {low} to {high}.")
def choose_character_sets():
"""Ask which kinds of characters to use; lowercase letters are always in."""
sets = {"lowercase": string.ascii_lowercase}
if ask_yes_no("Include uppercase letters?", True):
sets["uppercase"] = string.ascii_uppercase
if ask_yes_no("Include digits?", True):
sets["digits"] = string.digits
if ask_yes_no("Include symbols?", True):
sets["symbols"] = SYMBOLS
return sets
# Making and rating passwords
def make_password(length, sets):
"""Build a password with at least one character from every chosen set."""
# One character from each set first, so no set is missing by chance
password = [secrets.choice(group) for group in sets.values()]
pool = "".join(sets.values())
while len(password) < length:
password.append(secrets.choice(pool))
# Shuffle so those first characters are not always at the front
secrets.SystemRandom().shuffle(password)
return "".join(password)
def strength_note(length, pool_size):
"""Rate a password by how many bits of randomness it holds."""
bits = length * math.log2(pool_size)
if bits < 50:
verdict = "weak, a computer could crack it quickly"
elif bits < 80:
verdict = "good for most accounts"
else:
verdict = "strong"
return f"{verdict} (about {bits:.0f} bits of randomness)"
def main():
"""Ask for the options, print the passwords, and repeat if wanted."""
print("Password generator")
while True:
print()
length = ask_number("Password length", MIN_LENGTH, MAX_LENGTH, DEFAULT_LENGTH)
sets = choose_character_sets()
count = ask_number("How many passwords", 1, MAX_COUNT, 1)
pool_size = len("".join(sets.values()))
print()
print(f"Using {', '.join(sets)} ({pool_size} possible characters):")
for number in range(1, count + 1):
print(f" {number:>2}. {make_password(length, sets)}")
print(f"Strength: {strength_note(length, pool_size)}")
print()
if not ask_yes_no("Make more passwords?", False):
break
print("Goodbye. Keep your passwords in a password manager.")
if __name__ == "__main__":
main()

Open it in the full editor to save it, share it and keep building.

Sample run

What the program printed, and what was typed (in yellow).

Password generator

Password length (8-64, Enter for 16): abc
  Please type a whole number.
Password length (8-64, Enter for 16): 5
  Please choose a number from 8 to 64.
Password length (8-64, Enter for 16): 
Include uppercase letters? (Y/n): 
Include digits? (Y/n): y
Include symbols? (Y/n): 
How many passwords (1-10, Enter for 1): 3

Using lowercase, uppercase, digits, symbols (75 possible characters):
   1. 8Ahk#wM$Hnm6zj8S
   2. Jm8e5s=fIZx6aq7G
   3. pI9@pyc^f-&*_JjS
Strength: strong (about 100 bits of randomness)

Make more passwords? (y/N): y

Password length (8-64, Enter for 16): 10
Include uppercase letters? (Y/n): n
Include digits? (Y/n): n
Include symbols? (Y/n): n
How many passwords (1-10, Enter for 1): 

Using lowercase (26 possible characters):
   1. cxlhzpjdsq
Strength: weak, a computer could crack it quickly (about 47 bits of randomness)

Make more passwords? (y/N): n
Goodbye. Keep your passwords in a password manager.

How it is built

1. Questions with a default answer

Pressing Enter returns the default, which the prompt shows in capitals as Y/n or y/N. Any answer other than y, yes, n or no asks again.

def ask_yes_no(prompt, default):
    """Return True or False; pressing Enter gives the default."""
    hint = "Y/n" if default else "y/N"
    while True:
        answer = input(f"{prompt} ({hint}): ").strip().lower()
        if answer == "":
            return default
        if answer in ("y", "yes"):
            return True
        if answer in ("n", "no"):
            return False
        print("  Please answer y or n.")

2. Choosing the characters

Lowercase letters are always used; each other set joins the dictionary when the user says yes. string.ascii_uppercase and string.digits save typing out the alphabet and the digits.

def choose_character_sets():
    """Ask which kinds of characters to use; lowercase letters are always in."""
    sets = {"lowercase": string.ascii_lowercase}
    if ask_yes_no("Include uppercase letters?", True):
        sets["uppercase"] = string.ascii_uppercase
    if ask_yes_no("Include digits?", True):
        sets["digits"] = string.digits
    if ask_yes_no("Include symbols?", True):
        sets["symbols"] = SYMBOLS
    return sets

3. Building the password

One character comes from each chosen set first, so a password that should have digits always has one. secrets.choice picks the rest and a final shuffle mixes the positions; secrets uses the system's secure randomness, which random does not.

def make_password(length, sets):
    """Build a password with at least one character from every chosen set."""
    # One character from each set first, so no set is missing by chance
    password = [secrets.choice(group) for group in sets.values()]
    pool = "".join(sets.values())
    while len(password) < length:
        password.append(secrets.choice(pool))
    # Shuffle so those first characters are not always at the front
    secrets.SystemRandom().shuffle(password)
    return "".join(password)

4. How strong is it

Each character drawn from a pool of pool_size adds log2(pool_size) bits, so longer passwords and bigger pools both help. Under 50 bits is weak and 80 or more is strong.

def strength_note(length, pool_size):
    """Rate a password by how many bits of randomness it holds."""
    bits = length * math.log2(pool_size)
    if bits < 50:
        verdict = "weak, a computer could crack it quickly"
    elif bits < 80:
        verdict = "good for most accounts"
    else:
        verdict = "strong"
    return f"{verdict} (about {bits:.0f} bits of randomness)"

5. The main loop

Each round asks the questions, prints the passwords with their strength and asks whether to make more. Enter at that last question means no, so the program ends.

def main():
    """Ask for the options, print the passwords, and repeat if wanted."""
    print("Password generator")
    while True:
        print()
        length = ask_number("Password length", MIN_LENGTH, MAX_LENGTH, DEFAULT_LENGTH)
        sets = choose_character_sets()
        count = ask_number("How many passwords", 1, MAX_COUNT, 1)
        pool_size = len("".join(sets.values()))

        print()
        print(f"Using {', '.join(sets)} ({pool_size} possible characters):")
        for number in range(1, count + 1):
            print(f"  {number:>2}. {make_password(length, sets)}")
        print(f"Strength: {strength_note(length, pool_size)}")
        print()
        if not ask_yes_no("Make more passwords?", False):
            break
    print("Goodbye. Keep your passwords in a password manager.")

Make it bigger

  • Add an option to leave out look-alike characters such as 0, O, l and 1.
  • Make passphrases from random words, like river-candle-orbit-mango.
  • Rate a password the user types with the same strength_note function.
  • Let the user type their own set of symbols, for sites that allow only some.

What it practises