Python Password Generator
Table of Contents + β
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
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~Letβs read what each one is:
string.ascii_lettersis every letter, both small and big. So it isatozfollowed byAtoZ, all in one string.string.digitsis the ten number characters,0through9.string.punctuationis 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 letterOutput
cYour 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.punctuationprint(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
poolnow 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 randomimport 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!vQ9xLLetβs read it line by line:
poolis 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 looplengthtimes. 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, andpassword += ...glues it onto the end.- After the loop finishes,
return passwordhands 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 secretsimport 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@Kq5WmSee, 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 secretsimport 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!Lv2Letβs read it step by step:
- We split the pool into named pieces:
lowercase,uppercase,digits,symbols.string.ascii_lowercaseis just the small letters, andstring.ascii_uppercaseis just the big ones. password_charsstarts 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 - 4times, because we already placed 4 characters. It fills the rest from the fullpool. 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 randomm4K@xQ9p!Lv2is 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 secretsimport 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): 12Your password: m4K@xQ9p!Lv2
Enter password length (or 'q' to quit): 3Too short. Use at least 8 characters.
Enter password length (or 'q' to quit): abcPlease enter a whole number.
Enter password length (or 'q' to quit): qGoodbye!Look at the main function, because that is where the user-handling lives:
- The
while True:loop keeps asking until the user typesqto 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, likeabc, it raises aValueError, which we catch and handle with a friendly message. Thencontinuejumps 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, andrange("12")would raise an error. So convert it first, and catch the error if the text is not a number. - Using
randomfor real passwords. It is predictable enough to be unsafe for security. Usesecretsfor 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 - 4in the loop. If you already placed 4 characters and then looplengthmore times, your password comes out 4 characters too long.
β Best Practices
Habits this project teaches:
- Reach for
secretsinstead ofrandomwhenever the output is a password, token, or key. Same usage, much safer. - Use the built-in
stringgroups 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
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
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
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
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.