Step 49. ★ Project: Password Tool — Building Your First Security Tool

Step 49. ★ Project: Password Tool — Building Your First Security Tool

Level 1 — Programming and the Computer’s Insides | Difficulty ★★★☆☆ | Estimated time: 4 hours

Prerequisites: Steps 41–48 complete. You can use functions, loops, strings, exception handling, and file saving.

  • What you need: a computer with Python installed, a text editor. Nothing new to install.
  • ⚠️ Safety notice: the tool you build today is for your own files and your own accounts only. It’s a tool for re-measuring the strength of your own passwords and generating passwords you will use — not a tool for testing or discovering other people’s passwords. All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime.

What’s the difference between the passwords "123456" and "X9#mQ2$vL8&z"? The answer is "the time it takes an attacker to figure it out." The former ranks #1 among leaked passwords worldwide, so it falls in 0.1 seconds; the latter has so many combinations that it’s effectively near-infinite. Today you’ll verify the principle behind this difference with your own hands, and build a machine that makes strong passwords and a machine that measures strength — your first security tool.


1. Learning Objectives

By the end of this chapter, you will be able to:

  • Explain with numbers that password strength is determined by the combination count (multiplication)
  • Explain the difference between randomness for security (secrets) and randomness for games (random)
  • Complete a password generator using the character sets of the string module
  • Complete a strength checker (score + common-password warning) using length and diversity conditions
  • Complete a tool that even comes with a user manual (README)

2. Background Knowledge — Today’s Tools and Concepts

Today’s Tools at a Glance

Category Details
Language/environment Python 3 (standard library only, nothing to install)
Today’s modules secrets (randomness for security), string (character sets)
Review syntax Functions and default parameters, for loops, any(), while + try input loops
Concepts needed Combination counts (the principle of strength), entropy, the danger of predictable randomness
Today’s artifacts pwtool.py (a generate + check menu tool) and a README.md

2-1. The Multiplication of Combinations — The Principle of Strength

Picture a padlock. With one dial (10 digits) there are 10 candidates; with four dials, 10×10×10×10 = 10,000. The more dials (positions) and the more ticks (kinds of characters), the more the candidates explode multiplicatively.

Passwords are the same. The combination counts of three passwords, measured hands-on (2026-09-09):

123456           -> 1000000               (digits only, 6 places: 10 to the 6th power)
qwerasdf         -> 208827064576          (lowercase only, 8 places: 26 to the 8th power ≈ 200 billion)
kX#7mQ2$vL8&zP4a -> 37157429083410091685945089785856

The third one is 16 places of upper+lowercase+digits+special characters (94 kinds), with about 3.7×10²⁸ candidates. Even if an attacker tries a billion times per second, it takes longer than the age of the universe. This "total number of candidates" is the true nature of strength, and its logarithm (exponent) is called entropy in technical terms. For now, just remember "the combination count is the strength."

2-2. random and secrets — Two Dice

The random module you learned in Step 47 is not actually true randomness. It produces numbers that look random through "a fixed calculation starting from a seed." That’s fine for games, but if you know the seed, you can predict the next number — make a password with this die and an attacker who knows the rules will break it.

That’s why for security you use the secrets module. It uses unpredictable randomness provided by the operating system, and its usage is nearly identical to random, so switching is easy.

  • Games, shuffling, test data: random
  • Passwords, keys, tokens: secrets

If you can explain this distinction, half of today is already a success.

2-3. Character Sets — The string Module

The ingredient list for passwords (which characters to use) is prepared in the string module.

import string
print(string.ascii_lowercase)   # abcdefghijklmnopqrstuvwxyz
print(string.ascii_letters)     # lowercase + uppercase
print(string.digits)            # 0123456789
print(string.punctuation)       # !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~

Adding these lists together gives you an ingredient basket. The more ingredients, the more ticks on the padlock, and the combination count explodes.


3. Follow Along

3-1. First Experience with secrets

Input (sec1.py)

import secrets
import string

pool = string.ascii_letters + string.digits
print(secrets.choice(pool))
print(secrets.choice(pool))
print(secrets.choice(pool))

Run it. Multiple times.

How to read it: a different character comes out every time. secrets.choice(basket) picks one at random from the basket — its usage is the same as random.choice. Only the module differs.

3-2. The Generator — Repeat for the Number of Places

Input (pwgen.py)

import secrets
import string

def generate_password(length=16):
    pool = string.ascii_letters + string.digits + "!@#$%^&*"
    password = ""
    for _ in range(length):
        password = password + secrets.choice(pool)
    return password

print(generate_password())
print(generate_password(12))

Screen example (differs every run):

kX#7mQ2$vL8&zP4a
WWo7JS08ZAWL

(The second line, obtained from a 12-place generation in the 2026-09-09 hands-on run, is the actual output. You’ll get different values — that’s normal.)

How to read it: the underscore _ is an idiom for "a loop that doesn’t need the number." for _ in range(16): means "just loop 16 times." Each round picks one ingredient and appends it. With the default parameter (Step 44), 16 places is the default, but 12 places works if you want.

Predict: if you run the same code again right away, will the same password come out? Run it. And also try swapping secrets for random.choice. The "different every time" of the results looks the same, but the difference between the two lies in predictability (section 2-2).

3-3. Measuring Strength — A Combination Calculator

Input (strength.py)

def estimate_pool_size(pw):
    pool = 0
    if any(c.islower() for c in pw):
        pool = pool + 26
    if any(c.isupper() for c in pw):
        pool = pool + 26
    if any(c.isdigit() for c in pw):
        pool = pool + 10
    if any(not c.isalnum() for c in pw):
        pool = pool + 32
    return pool

def combinations(pw):
    pool = estimate_pool_size(pw)
    return pool ** len(pw)

print(combinations("123456"))
print(combinations("qwerasdf"))
print(combinations("kX#7mQ2$vL8&zP4a"))

Output (measured 2026-09-09):

1000000
208827064576
37157429083410091685945089785856

How to read it: the new function any(condition for c in pw) asks "is there at least one character matching the condition?" .islower() asks whether it’s lowercase, .isupper() uppercase, .isdigit() a digit, and .isalnum() alphanumeric — string checkers. You add to the ingredient count (pool) for each kind of character present, and raise it to the power of the length (**).

Why: compare the three numbers. The numbers themselves tell you how length and ingredients multiply strength.

3-4. Making a Scorecard — The Strength Checker

Combination counts are too big, so let’s convert them into a human-readable score.

Input (checker.py)

def score_password(pw):
    score = 0
    if len(pw) >= 8:
        score = score + 1
    if len(pw) >= 12:
        score = score + 1
    if any(c.islower() for c in pw) and any(c.isupper() for c in pw):
        score = score + 1
    if any(c.isdigit() for c in pw):
        score = score + 1
    if any(not c.isalnum() for c in pw):
        score = score + 1
    return score

def grade(score):
    if score <= 2:
        return "Weak — change it immediately"
    if score == 3:
        return "Fair — try making it longer"
    if score == 4:
        return "Strong"
    return "Very strong"

pw = input("Password to check: ")
s = score_password(pw)
print(f"Score: {s}/5 — {grade(s)}")

Measured run with 123456, Qwer1234, and kX#7mQ2$vL8&zP4a (2026-09-09):

123456           -> Score: 1/5 — Weak — change it immediately
Qwer1234         -> Score: 3/5 — Fair — try making it longer
kX#7mQ2$vL8&zP4a -> Score: 5/5 — Very strong

How to read it: we set the scoring criteria ourselves. Five conditions of length (8/12 places) and diversity (upper/lower, digits, special). Deciding criteria like this and leaving the rationale in comments is also part of design.

3-5. The Common-Password Warning

Separate from the strength calculation, anything in the dictionary must be filtered immediately.

Input (add to checker.py)

COMMON = ["123456", "password", "qwerty", "111111", "abc123", "12345678"]

if pw.lower() in COMMON:
    print("Warning: this is one of the most common passwords in the world. Regardless of the combination count, it falls in one second!")

Measured check of 123456 (2026-09-09) — the warning appears below the score:

Score: 1/5 — Weak — change it immediately
Warning: this is one of the most common passwords in the world. Regardless of the combination count, it falls in one second!

How to read it: attackers try the list of leaked passwords before random combinations. 12345678 looks decent at 8 places, but it’s at the top of the dictionary, so it’s meaningless. .lower() is the technique of unifying case for comparison (treating PASSWORD and password as the same).

Why: strength is math and psychology at the same time. What’s easy for a person to remember is also easy for an attacker to try.

3-6. Combining the Two Tools — Completing the Menu Program

Input (pwtool.py)

import secrets
import string

COMMON = ["123456", "password", "qwerty", "111111", "abc123", "12345678"]

def generate_password(length=16):
    pool = string.ascii_letters + string.digits + "!@#$%^&*"
    password = ""
    for _ in range(length):
        password = password + secrets.choice(pool)
    return password

def score_password(pw):
    score = 0
    if len(pw) >= 8:
        score = score + 1
    if len(pw) >= 12:
        score = score + 1
    if any(c.islower() for c in pw) and any(c.isupper() for c in pw):
        score = score + 1
    if any(c.isdigit() for c in pw):
        score = score + 1
    if any(not c.isalnum() for c in pw):
        score = score + 1
    return score

def grade(score):
    if score <= 2:
        return "Weak — change it immediately"
    if score == 3:
        return "Fair — try making it longer"
    if score == 4:
        return "Strong"
    return "Very strong"

while True:
    print("=== Password Tool ===")
    print("1. Generate  2. Check strength  0. Quit")
    menu = input("Choice: ").strip()
    if menu == "1":
        while True:
            try:
                n = int(input("Length (default 16, Enter is fine): ") or "16")
                break
            except ValueError:
                print("Please enter a number.")
    elif menu == "2":
        pw = input("Password to check: ")
        s = score_password(pw)
        print(f"Score: {s}/5 — {grade(s)}")
        if pw.lower() in COMMON:
            print("Warning: this is one of the most common passwords in the world. Regardless of the combination count, it falls in one second!")
    elif menu == "0":
        print("Goodbye.")
        break
    else:
        print("Please choose 0, 1, or 2.")

An actual run (measured 2026-09-09 — we deliberately typed "twelve" for the length and also entered 9, which isn’t on the menu):

=== Password Tool ===
1. Generate  2. Check strength  0. Quit
Choice: Length (default 16, Enter is fine): Please enter a number.
Length (default 16, Enter is fine): Generated: WWo7JS08ZAWL
=== Password Tool ===
1. Generate  2. Check strength  0. Quit
Choice: Password to check: Score: 1/5 — Weak — change it immediately
Warning: this is one of the most common passwords in the world. Regardless of the combination count, it falls in one second!
=== Password Tool ===
1. Generate  2. Check strength  0. Quit
Choice: Please choose 0, 1, or 2.
=== Password Tool ===
1. Generate  2. Check strength  0. Quit
Choice: Goodbye.

How to read it: it’s Step 44’s menu structure with Step 46’s input loop (while + try) layered on. A word input like "twelve" is handled by asking again, a 9 not on the menu is handled with a guidance message, and the program doesn’t die until quit (0). or "16" is the technique of using the default 16 when the input is empty (just Enter).


4. Missions & Exercises

Mission — Complete the Tool + README

Attach a user manual to your finished pwtool.py:

  1. Verify that the finished version from 3-6 works without gaps (generate, check, warning, bad-input handling)
  2. Create a README.md file and write: the tool’s name, who made it, how to use it (how to run + menu explanation), the date made
  3. Include a safety notice in the README too: "This tool is for managing my own account passwords only"

README example:

# Password Tool (pwtool)

- Made by: me
- Run: python pwtool.py
- Features: menu 1 generates strong passwords, menu 2 checks the strength of my passwords
- Caution: this tool is for my own account management only. Store generated passwords in a password manager

If you’ve made a tool, writing its manual is part of the work. It’s only a tool if someone else can use it.

Exercises

Q1. Explain the difference between secrets and random, and say which purpose each should be used for.

Q2. Roughly what are the combination counts of "lowercase only, 8 places" and "upper+lowercase + digits, 8 places," and what does this difference mean?

Q3. 12345678 is 8 digits, so its combination count is 100 million. Why is it still a password that "falls in one second"?

Q4. What does the condition any(not c.isalnum() for c in pw) check, and what misjudgment occurs if you remove the not?


5. Model Answers & Completion Criteria

Mission Model Answer

A model README:

# Password Tool (pwtool)

- Made by: me
- Date made: 2026-09-09
- Run: python pwtool.py
- Menu:
  - 1. Generate — set the length and it makes an unpredictable password with secrets
  - 2. Check strength — 5-point scale for length and diversity + common-password warning
  - 0. Quit
- Caution: this tool is for managing my own account passwords only.
  Don't send generated passwords through a messenger — transfer them directly into a password manager.

How to verify: ① On menu 1, does entering a word ("twelve") or a blank either ask again or fall through to the default? ② On menu 2, does 123456 show the warning and a 16-place mix show "Very strong"? ③ Does entering 9 or a letter on the menu show guidance? ④ Can someone who has only read the README go from running it to choosing a menu? If all four are "yes," it’s complete.

Exercise Solutions

Q1 solution. random imitates randomness with a fixed calculation starting from a seed, so it’s predictable if you know the seed. secrets uses unpredictable randomness provided by the operating system. Use random for games, shuffling, and test data; use secrets for passwords, keys, and tokens.

Q2 solution. Lowercase only, 8 places is 26 to the 8th power ≈ 208.8 billion; upper+lowercase + digits, 8 places is 62 to the 8th power ≈ 218 trillion (26 to the 8th power measured 2026-09-09: 208,827,064,576). When the ingredient kinds grow from 26 to 62, the candidates grow about a thousandfold at the same length — not just length, ingredient diversity also multiplies strength.

Q3 solution. Because attackers try the leaked-password dictionary before random combinations (brute force). 12345678 is at the very top of the dictionary, so it’s in the first batch of attempts regardless of the combination calculation. Strength is math and psychology at the same time — what’s easy for a person to remember is easy for an attacker to try.

Q4 solution. It checks whether there’s at least one special character. Since isalnum() is "True if alphanumeric," special characters are caught as "things that are not isalnum." If you remove the not, the condition flips to "if there’s an alphanumeric character," causing the misjudgment of awarding the special-character point even to passwords with no special characters.

Completion Criteria Checklist

  • [ ] I can explain the difference between secrets and random
  • [ ] I can explain the multiplication principle of combination counts with measured numbers
  • [ ] I completed the password generator (length specification + exception handling)
  • [ ] I completed the strength checker (score + common-password warning)
  • [ ] The tool doesn’t die on off-menu input or word input
  • [ ] Mission: I completed a tool that even has a README.md
  • [ ] I understand that this tool is for my own files / my own accounts only

6. Common Pitfalls & Fixes

Wall 1. The same password comes out every time

Symptom: the result is the same every run.
Cause: in most cases the secrets.choice(pool) call is outside the for loop, so it picks once and copies. Or you’re using random with a fixed seed.
Fix: check that the choice call is inside the for loop. You must pick fresh for every position.

Wall 2. The score is oddly low or high

Symptom: a decent password comes out as "Weak."
Cause: a mistake in the order of conditions or the operators. Especially mixing up and and or ("must have both lowercase and uppercase" is and).
Fix: check each condition one by one with print, like print(any(c.isdigit() for c in pw)). Finding where the score fails to rise is fastest.

Wall 3. Special characters get skipped in the check

Symptom: there are special characters but the score doesn’t rise.
Cause: the not c.isalnum() condition is often written wrong. Since isalnum() is "True if alphanumeric," special characters must be caught as "things that are not isalnum."
Fix: test that condition on its own. print([not c.isalnum() for c in pw]) shows the per-character verdict, and you’ll see immediately what’s wrong.

Wall 4. Pressing Enter at the length prompt kills it

Symptom: you pressed just Enter to use the default, but it asks again with a ValueError.
Cause: an empty input is the empty string "", and int("") is a ValueError.
Fix: use the input(...) or "16" technique — the empty string is treated as false, so "16" is used instead (included in the 3-6 finished version, verified hands-on 2026-09-09).

Wall 5. Sending the generated password over chat

Symptom: a leak incident while moving the password you made through a messenger.
Cause: the tool is innocent; operations are the problem. Passwords must not be left in plain text in messengers or email.
Fix: make a habit of transferring generated passwords directly into a password manager. How you handle a tool’s output is an extension of security practice.


7. Summary

Today’s Concepts

Concept One-line description
Combination count (kinds of characters) to the power of (length) — the true nature of strength
Entropy Strength expressed as the logarithm (exponent) of the combination count
secrets Randomness for security — unpredictable (for passwords and keys)
random Randomness for games — predictable if you know the seed
Common-password dictionary A trap that falls in one second regardless of combination count

Today’s Syntax & Parts

Part What it does
secrets.choice(basket) Picks one unpredictably from the basket
string.ascii_letters / digits / punctuation Ingredient character sets
any(condition for c in pw) Whether any character matches
islower / isupper / isdigit / isalnum Character-kind checkers
pool ** len(pw) Combination count calculation (exponentiation)
input(...) or "16" Apply a default to empty input

The Instinct That Matters More Than Commands

Someone who knows the attacker’s multiplication also knows the defender’s multiplication. Today you verified "the principle of strength" with your hands, and built a tool in which that principle works.

Remember two more things. First, "a strong password, different for every site" is impossible with human memory, so in practice it’s delegated to a password manager program — today’s tool was a miniature experience of that manager’s heart. Second, recent guidelines emphasize length over complexity. A long, easy-to-memorize sentence style like correct horse battery staple is both stronger and easier to remember than something short and complex — think of the multiplication where each added place grows the exponent, and the reason becomes clear.

Congratulations. Today you built your first "security tool." Unlike a game, this tool can be used in real life right away — and it can even change your family’s password habits.


Once every box is checked, Step 49 is complete. Click the checkbox in the sidebar to save your progress.