Cyberlium

Python › Module 3 › Lesson 2

BeginnerModule 3Lesson 2/5

Password Generator Script

Generate strong random passwords with Python’s secrets module—not guessable random

15 min+21 XP3 quiz
Module progress2 of 5
Character chain · Passphrase · Vault lock unlocked

Opening

random.random is a dice for games. secrets.choice is how you pick password characters — and you still never fire them at a login.

Last lesson you listed password generation as toil you own: you need a long, unique secret for YOUR password manager, not a clever phrase that already lives in a breach corpus. Python’s random module is fine for shuffling a lab deck or picking a practice hostname from a list you wrote. It is the wrong module for passwords, session tokens, API keys, and reset codes. The secrets module is documented for that job: it draws from the operating system’s cryptographically strong source (on Linux, typically getrandom / /dev/urandom). secrets.choice(alphabet) picks one character the way a defender wants — unpredictable to an attacker who can see your source code. This lesson is generation for YOU, then paste into a password manager. You will learn why random.random and random.choice are the wrong primitive, how alphabet and length set strength, and why the script prints once instead of logging, emailing, or committing the result. You will not crack hashes, you will not build Hydra, you will not test the output against SSH, a website login, or a neighbor’s Wi-Fi. A generator that becomes a spray is Module 1’s ethics failure with extra steps. Next lesson parses a fictional sample_auth.log you create — still not a production scrape.

1. secrets.choice versus random.random: the PRNG you would not bet a vault on

Python’s random module is a Mersenne Twister (by default) seeded in a way that is convenient for simulations. If an attacker can observe enough outputs, or guess the seed (clock, PID, a small space), they can predict future outputs. That is acceptable for a dice roll in a game. It is not acceptable for a password. secrets is the standard library’s answer: secrets.choice(seq) returns a uniformly chosen element using a cryptographically strong RNG. secrets.token_hex, secrets.token_urlsafe, and secrets.token_bytes exist for tokens. For a password you can type or paste, the usual pattern is: build an alphabet, call secrets.choice once per character, join. You do not import random for this file. You do not “seed with time.time() to make it extra random” — that is how people shrink the search space. You do not use random.random() < 0.5 to decide whether to include a digit. That is a toy coin.

A common confusion: “but my random.choice passwords look messy.” Looking messy is not entropy. A password of eight lowercase letters chosen with a predictable RNG can look messy and still be in a space a GPU eats. Another confusion: “I will add secrets later; random is fine for the prototype.” Prototypes get copied. The module name in the import line is the control. If the file generates secrets for accounts, the import is secrets. If the file shuffles a list of lab hostnames you invented, random is fine. Do not mix them in gen_password.py. Do not pull in a third-party “advanced RNG” from a random PyPI package to look serious — extra dependencies are extra supply chain, and the standard library already solved this.

2. Alphabet and length: the search space is math, not a clever word

Strength is roughly alphabet_size ** length, assuming independent uniform picks. string.ascii_letters gives 52 characters, digits add 10, a small symbol set like !@#$%^&* adds a handful more. Twenty characters from that mix is a different universe from eight characters from lowercase-only, and a different universe from Password1!. Clever phrases that hit song lyrics, pet names, and keyboard walks fail because attackers do not brute-force first — they try corpora. Your generator’s job is to skip clever. Length 20 is a teaching default, not a religion: some systems cap length; some reject certain symbols. If a site forbids ^, shrink the symbol set, do not secretly fall back to iloveyou. Unique per account: one generated string for mail, a different one for the lab VPN. Reuse is how one breach opens three services.

Do not “ensure complexity” by forcing the first character to be uppercase and the last to be a bang after a predictable template. That shrinks the space again. If a policy requires at least one digit and one symbol, draw the full string with secrets.choice, then reject-and-retry until the policy holds — still using secrets — rather than slotting known characters into known positions. Do not print entropy estimates from a blog formula as if they were a guarantee. Do not generate 10,000 candidates and pick the “most random looking” by eye. The first output of a correct generator is the password. You paste it into the manager and you close the terminal scrollback when you can. You do not screenshot it into a group chat.

3. Print once, never git, never a ticket, never a live-login test

The script’s output is a secret the moment it exists. print(password) to your own terminal is the intended path: you copy it into a password manager, then you do not save the script’s stdout. Do not open a log file and write the password. Do not email it to a ticket system “for the record.” Do not commit gen_password.py after you edited it to contain password = "the one I just generated". Do not name the file my_bank_password.py. Do not leave the output in a world-readable lab notes file. If you must keep a recovery copy for a throwaway lab account, that copy lives in a chmod 600 file that is in .gitignore — and even then, a manager is better. Module 4 will repeat chmod 600 and .gitignore for API keys. Same instinct: secrets are not source.

The generator is not a cracker. It does not take a hash. It does not take a login URL. It does not loop until a site returns 200 OK. Those extra loops are how people turn a teaching script into Hydra. You will not add requests to this file to “verify the password works.” You will not point it at localhost SSH either, unless you are testing YOUR lab account with a password you just set through the normal passwd path — and even that is unnecessary for this lesson. Success is: a 20-character string appears, you store it in the manager for an account you own, the script contains no leftover secret, git status is clean of passwords. Failure is a gist titled passwords.txt.

4. Wrong vs right: random toys and sprays vs secrets.choice for YOUR vault

Worked failure — same 20 characters, opposite module and opposite target. Right never includes Hydra, rainbow tables, or live logins.

  • Wrong

    import random; password = str(random.random()) or an eight-character random.choice loop “for now.” Seed with the clock. Write passwords to passwords.log and git add. Slack the output. Test candidates against a public SSH, a work VPN, or a website login. Build a loop that is Hydra in slow motion. Download rainbow tables. Crack a classmate’s hash. Use the generator as a spray wordlist. This course forbids all of that. secrets exists so you do not “upgrade later.”

  • Right

    import secrets and string. Alphabet = letters + digits + a small symbol set. length = 20. "".join(secrets.choice(alphabet) for _ in range(length)). Print once. Paste into YOUR password manager. Do not log, commit, or email the value. Do not test it against any live authentication. Save a short note that you used secrets (not the password itself) to $HOME/cyberlium-lab/password-gen-notes.txt and chmod 600. Next lesson parses logs you create — still not production.

5. Practical: gen_password.py in cyberlium-lab — print once, notes without the secret

Write the generator under $HOME/cyberlium-lab. Run it. Copy the output into a manager (or a throwaway lab login you own). Then write notes that record the module, alphabet idea, and length — not the password. chmod 600 the notes. If you accidentally printed into a file, delete that file and rotate the password as if it leaked. Do not add argparse that takes a target host. Do not import requests. The stub below is the whole program plus a notes template. Fill the notes in your own words after you run it.

Command guide

gen_password.py — secrets.choice for YOUR passwords, never a spray

DEFENSIVE generator for YOUR passwords. NEVER crack, Hydra, spray, or test output against live logins. NEVER log the password to git, Slack, or a world-readable file.

Command — copy this

mkdir -p "$HOME/cyberlium-lab"
cd "$HOME/cyberlium-lab"

Command — copy this

cat > gen_password.py << 'PY'
import secrets
import string

# Cryptographically strong picks — not random.random / random.choice.
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
length = 20
password = "".join(secrets.choice(alphabet) for _ in range(length))

# Print ONCE to the terminal. Do not write this to a log.
print(password)
PY

Command — copy this

python3 gen_password.py

Optional command

Copy the line into YOUR password manager, then continue.

Command — copy this

NOTES="$HOME/cyberlium-lab/password-gen-notes.txt"
{
  echo "=== PASSWORD GENERATOR NOTES (no secrets in this file) ==="
  echo "module: secrets  (NOT random.random / random.choice)"
  echo "primitive: secrets.choice(alphabet) once per character"
  echo "alphabet: ascii_letters + digits + !@#$%^&*"
  echo "length: 20"
  echo "output_handling: printed once, pasted into my manager, not committed"
  echo "explicit_non_use: no Hydra, no live SSH/web login tests, no rainbow tables"
  echo "git: gen_password.py has no leftover password literals"
} > "$NOTES"

Command — copy this

chmod 600 "$NOTES" gen_password.py

Windows without chmod: WSL/Git Bash, or restrict the files in your profile.

NEVER: import random for this job NEVER: open("passwords.log","a") and write the password NEVER: requests.post a login URL in a loop NEVER: hydra -l root -P wordlist.txt ssh://anyone

Mission: run gen_password.py and lock notes without the password

1) Explain in your notes why secrets.choice is the primitive and random.random is not. 2) Run a generator with a mixed alphabet and length 20; paste the result into YOUR password manager only. 3) Record module, alphabet, length, and “print once / no git / no live-login test” in $HOME/cyberlium-lab/password-gen-notes.txt and chmod 600. Do not store the password in that file. Never Hydra. Never crack. Never test the output against a live login.

Stuck? Ask Cyberlium AI Mentor

If “random.choice looks the same so it should be fine” still feels true, ask for a hint — not a cracker. Try: "Hint only: why is secrets.choice required for password characters, why is length-20 from a mixed alphabet stronger than a clever phrase, and why must I not loop the output against SSH?" You still run the generator. No Hydra, no rainbow tables, no live logins.

You now generate passwords with secrets, not with a game PRNG, and you treat the output as a secret: print once, manager, no git. Alphabet and length are the search space; clever phrases are not. The generator does not grow a login loop. Next — Log Parser Script — line-by-line reads of a fictional sample_auth.log YOU create, counting failures without scraping production or deanonymizing people.

Knowledge Check

1

APPLY: A classmate’s gen_password.py uses random.choice and writes every result to passwords.log, then they want to “try the list against SSH to see which work.” What is wrong, and what is right?

Multiple choice

Knowledge Check

2

APPLY: You need a unique password for YOUR mail. You used letters+digits+symbols, length 20, secrets.choice, printed once. Where does the string go, and what stays out of git?

Multiple choice

Knowledge Check

3

APPLY: True or False: After generating with secrets, you should hash the password and look it up in a rainbow table, or POST it to a live login in a loop, to prove it is strong.

True or False

← Previous

Answer all 3 knowledge checks to continue. (0/3 answered)