Screen passwords the way current guidance says to
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
- Build
/tmp/breached.txt: at least forty common or predictable passwords, one per line, includingPassword1!,Summer2026!,Welcome123andQwerty123!. - 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 usinghashlib.sha1does it. - Write
/tmp/pwcheck.py. It reads one candidate password from standard input and prints a single line beginningACCEPTorREJECT, 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 wordlabcorp, your invented company name, in any letter case. Impose NO rule about capitals, digits or symbols. - 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.mdwhy that means the service never learns the password, or even its full hash. - Test it by hand with a long lowercase passphrase, with
Password1!, withLabcorp-Summer-2026and with a short random string, and record the four results. - Finish
/tmp/policy.mdwith 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.