Screen passwords the way current guidance says to

short · 40 min · Objective 4.5

Task

Write a password check that follows current guidance -- a long minimum, no composition rules, and screening against breached and context-specific values -- then prove it accepts a long lowercase passphrase that a complexity rule would refuse, and rejects a complex-looking password that is already on the list attackers try first.

Steps

  1. Build /tmp/breached.txt: at least forty common or predictable passwords, one per line, including Password1!, Summer2026!, Welcome123 and Qwerty123!.
  2. Convert it to the form breached-password services publish: the uppercase SHA-1 hash of each entry, one per line, in /tmp/breached-sha1.txt. A short Python loop using hashlib.sha1 does it.
  3. Write /tmp/pwcheck.py. It reads one candidate password from standard input and prints a single line beginning ACCEPT or REJECT, followed by the reason. Reject anything shorter than a minimum you choose and can defend (at least 12 characters); reject anything whose SHA-1 is in the hashed list; and reject anything containing the context word labcorp, your invented company name, in any letter case. Impose NO rule about capitals, digits or symbols.
  4. Do the breach lookup the way a privacy-preserving service does: take the first five characters of the candidate's hash, pull out every listed hash with that prefix, and compare the remainder locally. Note in /tmp/policy.md why that means the service never learns the password, or even its full hash.
  5. Test it by hand with a long lowercase passphrase, with Password1!, with Labcorp-Summer-2026 and with a short random string, and record the four results.
  6. Finish /tmp/policy.md with the rest of the policy: no routine expiry, a forced change on evidence of compromise, and where breached-credential monitoring would supply that evidence.

Verify

printf '%s' 'Password1!' | python3 /tmp/pwcheck.py
printf '%s' 'violet tractor under seventeen lamps' | python3 /tmp/pwcheck.py
python3 - <<'PY'
import subprocess
cases={
    'violet tractor under seventeen lamps':'ACCEPT',
    'Password1!':'REJECT',
    'Summer2026!':'REJECT',
    'Labcorp-Summer-2026':'REJECT',
    'xK9#mQ':'REJECT',
}
for pw,want in cases.items():
    out=subprocess.run(['python3','/tmp/pwcheck.py'],input=pw,capture_output=True,text=True).stdout.strip()
    print('%-38s -> %s' % (pw,out))
    assert out.upper().startswith(want), 'expected %s for %r' % (want,pw)
print('length and screening decide; composition rules do not')
PY
grep -ciE "evidence of compromise|expir" /tmp/policy.md

The first must print REJECT and the second ACCEPT. The assertion is the lesson in five cases: a lowercase passphrase with no digits or symbols passes, because its length is what makes it strong, while Password1! -- which satisfies every old complexity rule -- fails because it is exactly what spraying and stuffing try first. The context word catches the company-name password that no generic list would contain.

Notes

Composition rules feel rigorous and produce Password1!. The prefix lookup you wrote is how real breached-password services let an organisation screen without disclosing what it is screening, which is why the check can run on every password change rather than once a year during an audit.

This is an independent study companion for CompTIA Security+ SY0-801 and is not produced by or endorsed by CompTIA.