Python tool identifying hash types by pure format analysis (prefix, alphabet, length, context) with no cryptographic computation — returns a ranked candidate list with hashcat/john modes, exposed as a CLI, a Python API and a Flask web app deployed on Render.
A hash identification tool that determines the likely algorithm behind a hash string through format analysis only — prefix, alphabet, length and surrounding context. No cryptographic computation is ever performed: the tool is a pattern recognizer, not a cracker.
The result is always a ranked list, never a single answer. A 32-character hexadecimal string matches at least eight plausible algorithms (MD5, NTLM, MD4, RIPEMD-128, …), and claiming otherwise would simply be false. The tool ranks candidates by confidence instead of pretending to a certainty it cannot have.
| Step | Signal | Role |
|------|--------|------|
| 1 | Prefix | $2b$, {SSHA}, $argon2id$… unique signature, short-circuits everything else |
| 2 | Alphabet | hex, standard base64, crypt base64 (./A-Za-z0-9), decimal |
| 3 | Length | discriminates the family, rarely the algorithm |
| 4 | Context | separators, salt, pwdump line, MySQL * prefix |
Each candidate is returned with its hashcat mode and john format name, so the output feeds directly into the next step of a workflow.
Strict single-responsibility layering, with dependencies always pointing inward:
cli.py ┐
web/app.py ┼→ engine.py → normalize.py / charset.py → models.py
| Path | Role | Never does |
|------|------|-----------|
| models.py | Shared types: Rule, Candidate, Parsed | no logic |
| normalize.py | Dirty input → clean input | guesses no algorithm |
| charset.py | Alphabet predicates | knows no algorithm name |
| engine.py | Rules → candidates → scores → sort | no print() |
| cli.py | Arguments, I/O, display | no identification logic |
| data/rules.json | All domain knowledge | — |
| web/app.py | Flask API: serves page + /api/identify | no identification logic |
The CLI and the web API are two façades over the same engine.identify() — adding an interface means one new file and zero changes to the engine.
Adding a new algorithm is a single JSON entry, zero lines of Python:
{
"name": "SHA-224",
"regex": "[a-fA-F0-9]{56}",
"hashcat": "1300",
"john": "raw-sha224",
"base_score": 50,
"confidence": "ambigu",
"exclusive": false
}
base_score is uniform per length group (50 for raw hex): it measures pattern precision, not popularity. Ties between same-length algorithms are broken by a separate POPULARITE map in engine.py — keeping "how specific is this pattern" and "how common is this algorithm" as two independent axes.
hashid 5d41402abc4b2a76b9719d911017c592
cat hashes.txt | hashid --json
hashid -f hashes.txt --top 3
from hashid import identify
identify("5d41402abc4b2a76b9719d911017c592")
A Flask layer serves a terminal-styled front end and a JSON API on top of the same engine:
GET / — the pageGET /api/identify?hash=&top= — JSON {hash, count, candidates}src/ layout so tests run against the installed package, not the working directorypip install -e ".[dev]") with dev and web extrasrender.yaml Blueprint — every git push triggers a redeploy