Python Password Generator

In the last lesson, Calculator Application, you built your first complete program. So you know how to wire functions, input, and loops together. Now let’s build something you might actually use: a password generator. Weak passwords like password123 are easy to guess, and reusing the same one everywhere is risky. A good generator makes a long, random password in a second. By building it, you will learn the random, string, and secrets modules, which are useful far beyond this project.

πŸ€” Why a password generator?

Strong passwords are long and random, with a mix of letters, numbers, and symbols. The problem is, humans are bad at making them. We pick names, birthdays, and easy words, because random strings are hard to invent and hard to remember.

So this is a job for a program, not a person. A program is perfect here:

  • It can pick truly mixed-up characters with no human habits or patterns.
  • It can make the password any length you want, instantly.
  • It can include the right mix of uppercase, lowercase, numbers, and symbols every single time.

So we hand the boring, important job to Python. Along the way you will meet a few handy tools: the string module for ready-made sets of characters, the random module for picking randomly, and later the secrets module for when real security matters.

🧰 The tools: string and random

Python comes with these modules built in, so there is nothing to install. Let’s see what each one gives us before we build anything.

The string module has ready-made groups of characters, so you do not type them all out by hand. Here we print the three groups we care about:

import string
print(string.ascii_letters)
print(string.digits)
print(string.punctuation)

Output

abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
0123456789
!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~

Let’s read what each one is:

  • string.ascii_letters is every letter, both small and big. So it is a to z followed by A to Z, all in one string.
  • string.digits is the ten number characters, 0 through 9.
  • string.punctuation is the symbol characters, like !, #, $, %, and the rest.

See, these are just plain strings. We will glue them together to make the full pool of characters a password can use.

Now the random module. It picks things at random. The key function for us is random.choice, which picks one random item from a group:

import random
letters = "abcde"
print(random.choice(letters)) # one random letter

Output

c

Your result will differ, because it is random. That is the whole idea. random.choice grabs one random character each time. So if we call it many times and join the results, we get a random password.

Note

The exact characters you get will be different every run, because they are random. That is the whole point of a password generator. So do not expect your output to match these examples character for character.

🧩 Step 1: Building the character pool

First we build the pool. The pool is just all the characters a password is allowed to use, sitting in one string. We combine the letter, digit, and symbol groups into one.

This line makes the full pool of possible characters:

import string
pool = string.ascii_letters + string.digits + string.punctuation
print(pool)

Output

abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~

So what happened here:

  • The + joins strings together, the same way "ab" + "cd" gives "abcd".
  • We joined letters, then digits, then symbols, so pool now holds every character we might use, all in one place.
  • The more variety in the pool, the harder the password is to guess. A pool of only lowercase letters is weak. This wide pool is strong.

πŸ” Step 2: Picking random characters in a loop

To build a password of a chosen length, we pick one random character from the pool, that many times, and join them. A loop does the repeating for us.

This function builds and returns a password of whatever length you ask for:

import random
import string
def generate_password(length):
pool = string.ascii_letters + string.digits + string.punctuation
password = ""
for _ in range(length):
password += random.choice(pool)
return password
print(generate_password(12))

Output

K7#mP2!vQ9xL

Let’s read it line by line:

  • pool is our full set of characters, built the same way as Step 1.
  • password = "" starts with an empty string. We will add to it one character at a time.
  • for _ in range(length): runs the loop length times. We use _ as the loop variable because we do not need the count, we just need the repeats. The _ is a normal variable name that says β€œI’m ignoring this”.
  • random.choice(pool) picks one random character from the pool, and password += ... glues it onto the end.
  • After the loop finishes, return password hands back the finished password.

So a length of 12 gives twelve random characters joined together. Change the number, and you change the length. Simple, right?

Tip

There is a shorter way to do the same loop. random.choices(pool, k=length) picks length characters in one call, and "".join(...) glues them into a string: "".join(random.choices(pool, k=length)). Note the s on choices, that is the version that picks many at once. It does exactly what our loop does, just in one line.

πŸ”’ A big warning: random is not safe for real passwords

Here is the part most beginner tutorials skip, and it really matters.

The random module is not built for security. Here is the thing:

  • The numbers it produces are not truly unpredictable. They come from a starting value called a seed, and the sequence can be worked out by someone who knows what they are doing.
  • That is totally fine for games, shuffling a list, or picking a random color. It is not fine for anything that protects real money or real accounts.
  • For actual passwords, tokens, or secret keys, an attacker guessing your β€œrandom” output is a real danger.

So Python gives us a separate module just for this: secrets. It works almost exactly like random, but it pulls its randomness from a source that is built to be unpredictable. You use secrets.choice in place of random.choice:

import secrets
import string
def generate_password(length):
pool = string.ascii_letters + string.digits + string.punctuation
password = ""
for _ in range(length):
password += secrets.choice(pool)
return password
print(generate_password(12))

Output

v8$Lp2@Kq5Wm

See, almost nothing changed. We swapped random.choice for secrets.choice, and the code reads the same. But now the output is safe to actually use as a password.

Caution

Use random while you are learning and for fun, non-secret stuff. But for any password, login token, or secret key that protects something real, use secrets. Same idea, much safer. From here on we will use secrets.

πŸ’ͺ Step 3: Making the password genuinely strong

There is still a small weakness. When we pick every character at random from the whole pool, we might get unlucky and end up with, say, no digits at all. A password with only letters is weaker than one with a real mix.

So a strong generator does not leave the mix to luck. It guarantees it. The plan is:

  • Pick at least one lowercase letter, one uppercase letter, one digit, and one symbol up front. So all four types are definitely present.
  • Fill the rest of the length with random characters from the full pool.
  • Shuffle everything, so those guaranteed characters are not always stuck at the front.

Here is the stronger version. Read the code first, then we will walk through it:

import secrets
import string
def generate_password(length):
lowercase = string.ascii_lowercase
uppercase = string.ascii_uppercase
digits = string.digits
symbols = string.punctuation
pool = lowercase + uppercase + digits + symbols
# guarantee one of each type
password_chars = [
secrets.choice(lowercase),
secrets.choice(uppercase),
secrets.choice(digits),
secrets.choice(symbols),
]
# fill the rest from the full pool
for _ in range(length - 4):
password_chars.append(secrets.choice(pool))
# shuffle so the guaranteed ones aren't always first
secrets.SystemRandom().shuffle(password_chars)
return "".join(password_chars)
print(generate_password(12))

Output

m4K@xQ9p!Lv2

Let’s read it step by step:

  • We split the pool into named pieces: lowercase, uppercase, digits, symbols. string.ascii_lowercase is just the small letters, and string.ascii_uppercase is just the big ones.
  • password_chars starts as a list with one of each type already inside. So even before we add anything else, we know the password has a lowercase, an uppercase, a digit, and a symbol.
  • The loop runs length - 4 times, because we already placed 4 characters. It fills the rest from the full pool.
  • secrets.SystemRandom().shuffle(...) mixes the list, so the four guaranteed characters are not always sitting at the start. This is the secure way to shuffle.
  • "".join(password_chars) turns the list of single characters into one string, which we return.

So now every password is the length you asked for, and it always has all four character types. No more relying on luck.

Note

Notice we used a list this time, not a string. That is on purpose. You cannot shuffle a string in place, but you can shuffle a list. So we collect the characters in a list, shuffle, then join into a string at the very end.

🧠 What actually makes a password strong?

Quick plain-English version, because people get this wrong all the time.

  • Length matters the most. A long password is far harder to crack than a short one, even a short one full of symbols. Going from 8 to 16 characters helps more than adding one more symbol.
  • Variety matters next. Using lowercase, uppercase, digits, and symbols makes the pool bigger, so there are far more possible passwords to guess through.
  • Randomness ties it together. Summer2024! ticks the length and variety boxes but is still weak, because it is a common pattern. A truly random m4K@xQ9p!Lv2 is not.

So aim for long and random with a mix of types. That is what our generator gives you.

⌨️ Step 4: The complete program

Now we add a friendly front. We ask the user how long they want the password, check that the input makes sense, and show the result. Save this as password.py and run it.

import secrets
import string
def generate_password(length):
lowercase = string.ascii_lowercase
uppercase = string.ascii_uppercase
digits = string.digits
symbols = string.punctuation
pool = lowercase + uppercase + digits + symbols
password_chars = [
secrets.choice(lowercase),
secrets.choice(uppercase),
secrets.choice(digits),
secrets.choice(symbols),
]
for _ in range(length - 4):
password_chars.append(secrets.choice(pool))
secrets.SystemRandom().shuffle(password_chars)
return "".join(password_chars)
def main():
print("Password Generator")
while True:
choice = input("\nEnter password length (or 'q' to quit): ")
if choice == "q":
print("Goodbye!")
break
try:
length = int(choice)
except ValueError:
print("Please enter a whole number.")
continue
if length < 8:
print("Too short. Use at least 8 characters.")
continue
password = generate_password(length)
print("Your password:", password)
main()

Here is a sample run:

Output

Password Generator
Enter password length (or 'q' to quit): 12
Your password: m4K@xQ9p!Lv2
Enter password length (or 'q' to quit): 3
Too short. Use at least 8 characters.
Enter password length (or 'q' to quit): abc
Please enter a whole number.
Enter password length (or 'q' to quit): q
Goodbye!

Look at the main function, because that is where the user-handling lives:

  • The while True: loop keeps asking until the user types q to quit. So you can make many passwords in one run.
  • int(choice) tries to turn the typed text into a whole number. If the text is not a number, like abc, it raises a ValueError, which we catch and handle with a friendly message. Then continue jumps back to ask again.
  • if length < 8: refuses anything too short. We set the minimum to 8 here, because the strong version places 4 fixed characters and you want room for more. A short password is easy to crack, so this keeps the output safe.
  • Otherwise we call generate_password(length) and print a fresh, strong password.

So it reads a length, refuses anything that is not a number, refuses a length that is too short, and otherwise prints a strong random password. All from the small pieces you already learned.

⚠️ Common Mistakes

A few traps in this project:

  • Forgetting to convert the length with int(). input() gives you text, and range("12") would raise an error. So convert it first, and catch the error if the text is not a number.
  • Using random for real passwords. It is predictable enough to be unsafe for security. Use secrets for anything that protects something real.
  • Using a pool that is too small, like only lowercase letters. A small pool makes weak passwords. Mix letters, digits, and symbols.
  • Leaving the character mix to luck. Picking everything at random can give you a password with no digits. Guarantee one of each type, then shuffle.
  • Forgetting the length - 4 in the loop. If you already placed 4 characters and then loop length more times, your password comes out 4 characters too long.

βœ… Best Practices

Habits this project teaches:

  • Reach for secrets instead of random whenever the output is a password, token, or key. Same usage, much safer.
  • Use the built-in string groups instead of typing out every character. Less typing and no typos.
  • Guarantee the character mix, then shuffle, so every password is strong and not just usually strong.
  • Let the user choose the length, but enforce a sensible minimum so the result stays strong.
  • Validate the input right where you read it, so bad data never reaches the generator.
  • Keep the generating logic in its own function, so you could reuse it in a bigger program later.

πŸ› οΈ Practice Challenge

Try these two before peeking. They build right on top of the code above.

1. Skip the confusing characters. Some characters look alike, like the digit 0 and the letter O, or the digit 1 and the lowercase letter l. Change the generator so those are never used, so the password is easier to read and type. How would you do it?

2. Generate several at once. Instead of one password, print five for the same length, so the user can choose the one they like. How would you change main?

🧩 What You’ve Learned

βœ… You built a password generator that makes strong, random passwords of any length.

βœ… The string module gives ready-made character groups: ascii_letters, ascii_lowercase, ascii_uppercase, digits, and punctuation.

βœ… random.choice(pool) picks one random character, and a loop repeats it to build the full password.

βœ… random is not safe for real secrets, so you used secrets.choice instead for actual passwords.

βœ… You made the password genuinely strong by guaranteeing one of each character type, then shuffling.

βœ… Length matters most for a strong password, with variety of character types close behind.

βœ… Checking the input for a real number and a safe minimum length keeps the program friendly and the passwords strong.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What does random.choice(pool) do?

    Why: random.choice picks a single random item from a group. Calling it repeatedly and joining the results builds a random password.

  2. 2

    Why should you use the secrets module instead of random for real passwords?

    Why: random is fine for games and shuffling, but its output can be predicted. For passwords, tokens, or keys, use secrets, which is built to be unpredictable.

  3. 3

    Why guarantee one lowercase, one uppercase, one digit, and one symbol, then shuffle?

    Why: Picking everything at random can leave out a type by chance. Placing one of each up front guarantees the mix, and shuffling hides where those fixed characters sit.

  4. 4

    Why convert the length input with int() before using it?

    Why: input() always returns text. range() needs an integer, so you must convert with int() first, and catch the error if the text is not a number.

πŸš€ What’s Next?

You built a tool you can really use, and learned the random, string, and secrets modules along the way. Next we build something with memory: an expense tracker that records what you spend and saves it to a file.

Expense Tracker

Share & Connect