Skip to content

About

Privacy-first Python CLI to analyse password strength, check breaches via Have I Been Pwned (k-anonymity) and generate secure passwords & passphrases.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

ย 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿ” Password Security Toolkit

A privacy-first command-line toolkit to analyse password strength, check passwords against known data breaches, and generate cryptographically secure passwords and passphrases.

Python License: MIT Tests Coverage Ruff mypy HIBP

pwtool check - password strength report

๐Ÿ“– Overview

pwtool answers three questions every developer, sysadmin and security-minded user asks:

Question Command
How strong is this password, really? pwtool check
Has this password already leaked in a data breach? pwtool breach
Can you give me a strong one instead? pwtool generate

It is designed with security as a first-class requirement: passwords are read from a hidden prompt (never from command-line arguments, so they don't land in your shell history), they are never logged or written to disk, the reports never echo the password or any fragment of it, and breach lookups use the k-anonymity model so your password never leaves your machine.

โœจ Features

check โ€” strength analysis

  • Score 0โ€“4 (Very weak โ†’ Very strong) with a colour-coded report.
  • Entropy estimate that goes beyond naรฏve length ร— log2(charset): predictable parts of the password are charged only the few bits an attacker actually needs (inspired by Dropbox's zxcvbn).
  • Character-class analysis (lower, UPPER, digits, symbols, Unicode).
  • Common-password check against a bundled list of the 10,000 most common passwords, including leetspeak variants (P@ssw0rd โ†’ password).
  • Pattern detection: dictionary words (EFF wordlist), keyboard walks (qwerty, 1qaz2wsx, numpad), sequences (abc, 9876), repeats (aaaa, abcabc), years and dates.
  • Crack-time estimates for four attack scenarios, from rate-limited online attacks to GPU-powered offline attacks on fast hashes.
  • Actionable warnings and suggestions.
  • --breach to run a breach check in the same go, --min-score N for scripting / CI gates.

breach โ€” Have I Been Pwned lookup

  • Uses the official Pwned Passwords range API with k-anonymity: only the first 5 hex characters of the SHA-1 hash are sent.
  • Requests response padding (Add-Padding: true) so even the response size leaks nothing.
  • Graceful handling of timeouts, DNS failures and HTTP errors, with automatic retries and exponential backoff.
  • Meaningful exit codes: 0 not found, 1 pwned, 2 error.

generate โ€” secure generation

  • Passwords from Python's secrets module (the OS CSPRNG) โ€” never random.
  • Guarantees at least one character from every enabled class; options for length, count, --no-symbols, --no-digits, --no-upper, --exclude-ambiguous.
  • Diceware-style passphrases from the bundled EFF large wordlist (7,772 words โ‰ˆ 12.9 bits/word) with custom separator, capitalisation and an optional digit.
  • Entropy shown for every batch.

Everywhere

  • Beautiful terminal output powered by Rich.
  • --json on every command for scripting and automation; --no-color (and NO_COLOR) support.
  • --stdin to pipe a password from a password manager, e.g. pass show github | pwtool check --stdin.
  • Fully typed (mypy --strict), documented, src/ layout, zero network access except the explicit breach check.

๐Ÿ›ก๏ธ Security notes

How k-anonymity protects you

 your machine                                             api.pwnedpasswords.com
 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€                                             โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 password โ”€โ”€SHA-1โ”€โ”€โ–บ 5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8
                     โ””โ”€โ”ฌโ”€โ”˜โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                    prefix          suffix (kept local)
                       โ”‚
                       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ GET /range/5BAA6 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ
                                                                  returns ~800-1,000 suffixes
                       โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 0018A45C4D1DEF81644B54AB7F969B88D65:10 โ”€  sharing that prefix
                                 00D4F6E8FA6EECAD2A3AA415EEC418D38EC:2      (+ padding decoys)
                                 1E4C9B93F3F0682250B6CF8331B7EE68FD8:52372427
                                 ...
 suffix compared locally โ”€โ”€โ–บ match found โ†’ "PWNED 52,372,427 times"
  1. The password is hashed locally with SHA-1 (the format of the HIBP dataset).
  2. Only the first 5 characters of the 40-character hash are sent. Roughly a million different hashes share any given prefix, so the API cannot know which one you hold.
  3. The API returns every suffix with that prefix; the comparison happens on your machine.
  4. With Add-Padding: true the response is padded with fake entries, so its size does not reveal anything either.

The code enforces this: fetch_range() refuses to send anything that isn't exactly five upper-case hex characters, and the test-suite asserts that neither the password nor the hash suffix is ever passed to the network layer.

Your password is never logged or stored

  • No CLI arguments: passwords are read with getpass (hidden input) or from --stdin, so they never appear in shell history or in ps output.
  • No persistence: nothing is written to disk; there is no config file, cache or log.
  • No echo: reports describe where patterns are (chars 1-6), never what they are โ€” so output is safe to paste into a ticket or a CI log. (Generated passwords are, of course, printed โ€” that's the point of generate.)
  • No telemetry: the only network request the tool can make is the explicit breach check.

โš ๏ธ Disclaimer: strength estimates are heuristics. A high score is not a guarantee โ€” always use a unique password per site, a password manager, and multi-factor authentication.

๐Ÿ“ฆ Installation

Requires Python 3.10+.

# Install straight from GitHub
pip install "git+https://github.com/Josephobaje/password-security-toolkit.git"

# โ€ฆor as an isolated CLI app
pipx install "git+https://github.com/Josephobaje/password-security-toolkit.git"

From source (for development):

git clone https://github.com/Josephobaje/password-security-toolkit.git
cd password-security-toolkit
python -m venv .venv && source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
pwtool --version

python -m pwtool โ€ฆ works as well as the pwtool entry point.

๐Ÿš€ Usage

$ pwtool --help
usage: pwtool [-h] [-V] COMMAND ...

Password Security Toolkit - analyse strength, check breaches with HIBP
k-anonymity, and generate secure passwords.

positional arguments:
  COMMAND
    check           analyse password strength (score 0-4)
    breach          check a password against Have I Been Pwned (k-anonymity)
    generate (gen)  generate secure passwords or passphrases

Passwords are never accepted as command-line arguments, logged, or stored.

Check password strength

$ pwtool check
Password (input hidden):
โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Password Strength Report โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚                                                                              โ”‚
โ”‚  Score  โ–ˆโ–ˆโ–ˆโ–ˆ  4/4  Very strong                                               โ”‚
โ”‚                                                                              โ”‚
โ”‚  Length               16 characters                                          โ”‚
โ”‚  Character classes    โœ” lower   โœ” UPPER   โœ” digits   โœ” symbols               โ”‚
โ”‚  Entropy (estimated)  105.1 bits  (charset max 105.1 bits)                   โ”‚
โ”‚  Common password      no                                                     โ”‚
โ”‚  Patterns found       none                                                   โ”‚
โ”‚                                                                              โ”‚
โ”‚  Attack scenario                                Time to crack (avg)          โ”‚
โ”‚  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€          โ”‚
โ”‚  Online, rate-limited (100/hour)            longer than 100,000 years        โ”‚
โ”‚  Online, unthrottled (10/s)                 longer than 100,000 years        โ”‚
โ”‚  Offline, slow hash - bcrypt (10k/s)        longer than 100,000 years        โ”‚
โ”‚  Offline, fast hash - MD5 on GPUs (10B/s)   longer than 100,000 years        โ”‚
โ”‚                                                                              โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

A weak password such as Summer2024! gets a detailed breakdown (see the screenshot at the top): score 0/4, a common password and a year detected, ~22 bits of real entropy instead of the 72 bits its length and character mix suggest, and concrete suggestions.

Useful options:

pwtool check --breach            # strength analysis + HIBP breach check
pwtool check --json              # machine-readable report
pwtool check --min-score 3       # exit 1 if the score is below 3 (great for scripts)
pass show email | pwtool check --stdin
Sample --json output
{
  "score": 0,
  "max_score": 4,
  "label": "Very weak",
  "length": 10,
  "character_classes": {"lowercase": true, "uppercase": false, "digits": true, "symbols": false, "other": false},
  "charset_size": 36,
  "charset_entropy_bits": 51.7,
  "estimated_entropy_bits": 10.97,
  "is_common_password": false,
  "common_password_rank": null,
  "patterns": [
    {"kind": "dictionary", "start": 0, "end": 6, "length": 6, "bits": 2.32, "description": "Common password"},
    {"kind": "date", "start": 6, "end": 10, "length": 4, "bits": 7.64, "description": "Looks like a year"}
  ],
  "crack_time_seconds": {"online_throttled": 36000.0, "online_unthrottled": 100.0, "offline_slow_hash": 0.1, "offline_fast_hash": 1e-07},
  "crack_time_display": {"online_throttled": "10 hours", "online_unthrottled": "2 minutes", "offline_slow_hash": "instant", "offline_fast_hash": "instant"},
  "warnings": ["Contains common words or passwords that appear in attacker wordlists.", "Contains a year or date."],
  "suggestions": ["Use at least 12-16 characters; length beats complexity.", "..."]
}

Check for breaches

pwtool breach and generate

$ pwtool breach
Password (input hidden):
โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Have I Been Pwned - Breach Check โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚                                                                              โ”‚
โ”‚  โœ”  Good news!                                                               โ”‚
โ”‚  This password was not found in the Pwned Passwords corpus.                  โ”‚
โ”‚  Not being breached does not make it strong - run `pwtool check` too.        โ”‚
โ”‚                                                                              โ”‚
โ”‚  k-anonymity: only the SHA-1 prefix '948AF' was sent; 1,990 candidate        โ”‚
โ”‚  suffixes were compared locally.                                             โ”‚
โ”‚                                                                              โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
pwtool breach --json             # {"pwned": true, "count": 52372427, "hash_prefix_sent": "5BAA6", ...}
pwtool breach --timeout 5        # custom network timeout (seconds)

Generate passwords and passphrases

$ pwtool generate --length 24 --count 3
      Generated passwords
โ•ญโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ # โ”‚ Password                 โ”‚
โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 1 โ”‚ inb};)>~04}Au/{rGrDx?vtr โ”‚
โ”‚ 2 โ”‚ z@6N>H}X]X6Gc6g@5-oU[0.F โ”‚
โ”‚ 3 โ”‚ YBgYWwO$i^T2@$RS18lI>Ar6 โ”‚
โ•ฐโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
Entropy: 155.8 bits each ยท generated with Python's `secrets` (OS CSPRNG)

$ pwtool generate --passphrase --words 5 --separator " "
            Generated passphrase
โ•ญโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ # โ”‚ Passphrase                                 โ”‚
โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 1 โ”‚ spearfish distance drilling caregiver folk โ”‚
โ•ฐโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
Entropy: 64.6 bits each ยท generated with Python's `secrets` (OS CSPRNG)
Option Description
-l, --length N password length (default 20)
-n, --count N number of passwords/passphrases (default 1)
--no-symbols / --no-digits / --no-upper restrict the alphabet
--exclude-ambiguous drop look-alikes such as l 1 I O 0
-p, --passphrase Diceware-style passphrase mode
-w, --words N words per passphrase (default 6 โ‰ˆ 77.5 bits)
-s, --separator S word separator (default -)
--capitalize, --add-number passphrase variations
--json JSON output

Exit codes

Code Meaning
0 success / password not found in breaches
1 password pwned (breach), or score below --min-score (check)
2 error (bad input, network failure)
130 interrupted with Ctrl+C

Use it as a library

from pwtool import analyze, check_pwned, generate_passphrase

report = analyze("correct horse battery staple")
print(report.score, report.label, round(report.entropy, 1))  # 3 Strong 67.2

result = check_pwned("hunter2")  # k-anonymity under the hood
print(result.pwned, result.count)

print(generate_passphrase(6, capitalize=True))

๐Ÿ—‚๏ธ Project structure

password-security-toolkit/
โ”œโ”€โ”€ src/pwtool/
โ”‚   โ”œโ”€โ”€ __init__.py          # public API & version
โ”‚   โ”œโ”€โ”€ __main__.py          # `python -m pwtool`
โ”‚   โ”œโ”€โ”€ cli.py               # argparse CLI, secure input, exit codes
โ”‚   โ”œโ”€โ”€ strength.py          # scoring, entropy, crack times, feedback
โ”‚   โ”œโ”€โ”€ patterns.py          # dictionary/leet, keyboard, sequence, repeat, date detectors
โ”‚   โ”œโ”€โ”€ breach.py            # HIBP Pwned Passwords client (k-anonymity)
โ”‚   โ”œโ”€โ”€ generator.py         # CSPRNG password & passphrase generation
โ”‚   โ”œโ”€โ”€ output.py            # Rich rendering + JSON output
โ”‚   โ”œโ”€โ”€ wordlists.py         # loading of bundled data
โ”‚   โ”œโ”€โ”€ py.typed
โ”‚   โ””โ”€โ”€ data/
โ”‚       โ”œโ”€โ”€ common_passwords.txt     # 10k most common passwords (SecLists, MIT)
โ”‚       โ”œโ”€โ”€ eff_large_wordlist.txt   # EFF passphrase wordlist (CC BY 3.0 US)
โ”‚       โ””โ”€โ”€ NOTICE.md
โ”œโ”€โ”€ tests/                   # pytest suite (network fully mocked)
โ”œโ”€โ”€ docs/images/             # screenshots
โ”œโ”€โ”€ pyproject.toml           # packaging, ruff, mypy, pytest & coverage config
โ”œโ”€โ”€ CHANGELOG.md
โ””โ”€โ”€ LICENSE

๐Ÿงช Running tests

pip install -e ".[dev]"

pytest --cov=pwtool          # 143 tests, 99% branch coverage
ruff check . && ruff format --check .
mypy                         # strict mode

The Have I Been Pwned API is always mocked in tests โ€” an autouse fixture makes any real network call fail the test โ€” so the suite is fast, deterministic and works offline.

Name                      Stmts   Miss Branch BrPart  Cover
-----------------------------------------------------------
src/pwtool/breach.py         71      0     14      0   100%
src/pwtool/cli.py           134      0     28      0   100%
src/pwtool/generator.py      79      0     22      1    99%
src/pwtool/output.py         76      0     16      0   100%
src/pwtool/patterns.py      152      0     58      0   100%
src/pwtool/strength.py      134      0     34      0   100%
...
TOTAL                       672      0    174      1    99%
143 passed

๐Ÿ—บ๏ธ Roadmap

  • Batch audit mode: analyse a file of password hashes / a password-manager CSV export locally
  • Optional offline breach checks against a downloaded HIBP hash dataset
  • Localised wordlists and keyboard layouts (AZERTY, QWERTZ, Dvorak)
  • Better scoring for name/date/personal-info patterns (with user-supplied context)
  • TOTP / MFA secret generator
  • Publish to PyPI and add shell completions
  • GitHub Actions CI matrix (3.10 โ€“ 3.13) with coverage badge

Ideas and pull requests are welcome โ€” open an issue to discuss.

๐Ÿ“„ License

Released under the MIT License ยฉ 2026 Joseph Obaje.

Bundled data: the common-password list comes from SecLists (MIT) and the passphrase wordlist from the EFF (CC BY 3.0 US). Breach data is provided by Have I Been Pwned by Troy Hunt; this project is not affiliated with HIBP.

๐Ÿ‘ค Author

Joseph Obaje โ€” Full-stack, Flutter & Python developer with a focus on cybersecurity.

If you find this project useful, please consider giving it a โญ!

About

Privacy-first Python CLI to analyse password strength, check breaches via Have I Been Pwned (k-anonymity) and generate secure passwords & passphrases.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages