HashHunter indexes a storage medium (file name, path, size, timestamps, MD5/SHA-1/SHA-256), matches every file against reference hash lists, and turns the run into a self-contained case: a SQLite database of every file it saw, a tamper-evident audit log, a signable integrity manifest, and a report as PDF and CSV. It ships as a desktop application and a command-line tool built on the same engine.
Important
HashHunter compares exact hashes. It finds bit-identical copies of known files — nothing more, nothing less. It is not a similarity, carving, or content-analysis tool.
Warning
Please note that this script is currently under development, and I cannot provide a 100% guarantee that it operates in a forensically sound manner. It is tailored to meet specific needs at this stage. Use it with caution, especially in environments where forensic integrity is critical.
Hash matching is everyday casework: import a list of known hashes, run it against an evidence copy, document the hits. The annoying part has never been the matching — it is everything around it. Which files were skipped, and why? Which list version was actually loaded? Can I still prove, months later, that the report belongs to this exact database? Most setups answer these questions with a shrug, a heavyweight suite, or a folder full of loose scripts.
HashHunter grew out of that frustration. It does one job, does it fast, and treats the documentation as part of the job instead of an afterthought: every file ends in a defined state, every action lands in a hash-chained log, and the reference lists used by a scan are frozen with the case. The GUI exists so that colleagues who steer clear of the command line can run a clean, fully audited scan too.
- Why HashHunter
- Features
- What ends up on record
- Requirements
- Building
- Usage
- Step-by-step workflow
- Reference lists
- Case folder structure
- Project structure
- Forensic notes
- Third-party libraries
- Contributing
- License
- Disclaimer
- Conclusion
- Single-pass hashing — every file is read once and fed through MD5, SHA-1, and SHA-256 in a single pass; files are hashed in parallel and streamed in chunks: memory stays flat, progress is committed as the scan runs, and a cancelled scan preserves everything completed up to that point. Live progress with throughput and ETA.
- Reference database on LMDB — binary keys in separate namespaces per hash type, bulk import in a single transaction, and a per-list index that makes deleting or replacing a list proportional to the list's own size. NSRL-scale sets are the intended workload.
- Alert and exclude lists — flag known-bad files, account for known-good ones (e.g. NSRL). Both outcomes are recorded; a file in both kinds of list is still flagged.
- Triage filters — restrict a scan by file type (images/videos/audio/documents — by extension, or by magic bytes to catch renamed files), by size, and by modification date. An active filter is always disclosed in the audit log and the report, and malformed filter input aborts the scan instead of quietly matching nothing.
- Case database on SQLite — WAL mode, batched writes, one self-contained folder per case.
- Tamper-evident audit trail — every action is appended to a SHA-256 hash chain; a single altered byte breaks the chain verifiably. The integrity manifest covering the case artifacts can be signed with Ed25519 and verified later. The reference database keeps its own chained log of imports and deletions.
- Reports — a paginated PDF plus spreadsheet-friendly CSVs (UTF-8 BOM, semicolon-separated, formula injection neutralized). With the embedded typst renderer the PDF is properly typeset — and the tool still ships as a true single executable; the typst source is archived with the case.
- Windows specifics — live volumes can be scanned through a read-only VSS shadow copy (administrator rights required), and paths longer than 260 characters are handled.
- GUI and CLI — a Slint desktop interface for everyday use, a command-line binary for scripted or headless runs. Both are thin shells over the same engine, so behavior cannot drift between them.
The core guarantee: every enumerated file ends in exactly one defined state — there are no silent failures. A file only counts as "no match" if it was provably read, hashed, and checked.
| Status | Meaning |
|---|---|
match_confirmed |
Matched at least one alert list — a reportable finding |
excluded_known |
Matched exclude lists only — known and accounted for, not reported |
no_match |
Hashed and checked against the reference database, no match |
hashed_successfully |
Hashed in an index-only run (no reference database present) |
access_denied / read_error |
Could not be read — recorded with the exact error |
changed_during_processing |
Size or mtime changed while being read — flagged, not matched |
not_verifiable |
Scan was cancelled before this file was examined |
Paths that cannot even be enumerated (an unreadable directory hides its whole subtree) are recorded as enumeration_error entries, audited, and disclosed in the report summary — a gap never masquerades as a complete scan.
- Rust (stable, via rustup); the embedded typst report requires a recent stable toolchain.
- Windows: the Visual Studio Build Tools with the "Desktop development with C++" workload. The included Windows SDK also embeds the executable icon. VSS scanning is Windows-only and needs administrator rights.
- A C compiler for the bundled LMDB and SQLite. No cmake, no fontconfig, no external services, no network access at runtime.
Linux and macOS build and run the CLI and the core the same way; VSS is simply unavailable there.
-
Install the MSVC Build Tools and rustup (see above).
-
Clone the repository somewhere outside OneDrive-synced folders — sync clients and Controlled Folder Access like to hold locks on build artifacts:
git clone https://github.com/ot2i7ba/HashHunter.git C:\dev\hashhunter
-
Build the GUI (its own workspace; typst is embedded by default, so the result is a true single EXE):
cd C:\dev\hashhunter\crates\hh-gui cargo build --releaseResult:
crates\hh-gui\target\release\hh-gui.exe -
Build the CLI from the repository root:
cargo build --release -p hh-cli --features typst-embed
Result:
target\release\hashhunter.exe— leave out--features typst-embedfor a lighter build that uses the built-in PDF fallback.
Note
The first release build takes a while: every dependency is compiled once, and the final link runs thin LTO for a smaller, faster executable. Subsequent builds reuse the cached dependencies and are much quicker.
Tip
For a portable executable that runs without the VC++ redistributable, set RUSTFLAGS=-C target-feature=+crt-static before building.
The working directory (tool root) defaults to the folder the executable sits in. Hashes\ (the installation-wide reference database) and Cases\ (one folder per case) are created next to it on first use; --root overrides this.
Start hh-gui.exe. Everything is reachable through seven tabs:
| Tab | Purpose |
|---|---|
| Scan | Pick source and case name, set triage filters, run and cancel with live progress |
| Manual | Paste individual hashes and check them against the reference lists |
| Matches | Review the current case's hits, open the report or the case folder |
| DB | Browse the indexed files of a case — search, sort, filter by status |
| Export | Copy confirmed matches out of the case, explicitly confirmed and audited |
| Import | Import reference lists, review and delete them |
| Settings | Workers, I/O profile, symlink handling, signing key, manifest verification |
| Command | Purpose |
|---|---|
import-hashes |
Import a reference list (--kind alert or --kind exclude) |
refdb-info |
Show the reference catalog |
delete-list |
Remove a reference list completely |
scan |
Index and match a source into a new case |
query-hash |
Check individual hashes manually |
report |
Regenerate the case reports |
export |
Copy confirmed matches out of a case (requires --confirm-export) |
keygen |
Generate the Ed25519 signing key pair |
verify-manifest |
Verify the signed integrity manifest of a case |
verify-audit |
Verify the audit log's hash chain |
:: Import a reference list (alert = flag matches, exclude = known-good)
hashhunter import-hashes --file known.txt --kind alert
:: Scan a folder or an evidence copy into a new case
hashhunter scan --source D:\Evidence --name "Case 4711"
:: Triage variant: images and videos only, verified by content, 2026 only
hashhunter scan --source D:\Evidence --name "Case 4711" ^
--type images --type videos --modified-from 2026-01-01
:: Live Windows volume via a read-only VSS shadow copy (run as administrator)
hashhunter scan --source C:\Data --name "Case 4711" --vss
:: Reports, integrity, signing
hashhunter report --case Cases\Case_4711_2026-07-14_120000
hashhunter keygen
hashhunter verify-manifest --case Cases\Case_4711_2026-07-14_120000
hashhunter verify-audit --case Cases\Case_4711_2026-07-14_120000
:: Export confirmed matches (copies evidence material -> explicit flag required)
hashhunter export --case Cases\Case_4711_2026-07-14_120000 --confirm-export--help lists every option.
From an empty installation to a sealed case report. Steps name the GUI route; the CLI equivalent follows in parentheses.
- Place
hh-gui.exein a folder of its own (e.g.D:\Tools\HashHunter).Hashes\andCases\are created next to it on first use — the installation is portable. - Generate the signing key once: Settings tab → key section (CLI:
keygen). From then on every scan signs its integrity manifest automatically. Protect the private key; hand the public key to whoever needs to verify your cases later.
- Anything with one hash per line works: your own alert lists,
md5sum/sha256sumor certutil output, NSRL-style known-good sets. MD5, SHA-1, and SHA-256 may be mixed freely. - Import tab → Browse… (multiple files at once are fine) → tick "Exclude list instead of alert list" for known-good sets → import. (CLI:
hashhunter import-hashes --file bad_hashes.txt --kind alert/--kind exclude.) - Check the list overview afterwards: ID, kind, and counts per hash type. Re-importing a file with the same name replaces that list cleanly; every import and deletion lands in
Hashes\refdb-audit.jsonl.
Pick the route that matches your material:
- Forensic image (E01, raw/dd, …): mount it read-only, for example with Arsenal Image Mounter (Mount disk image, read-only or write-temporary mode — both leave the image untouched). Note the drive letter; that letter, or any folder below it, is your scan source.
- Physical drive: attach it through a hardware write blocker and scan the mounted letter.
- Folder of extracted or copied data: scan the folder directly.
- Live Windows volume: enable the VSS checkbox on the Scan tab (administrator rights required) — HashHunter creates a read-only shadow copy and scans that snapshot instead of the moving target.
- Scan tab: pick the source, name the case.
- Triage filters (type/size/date) are optional; an active filter is always disclosed in the audit log and the report. Leave everything off for a complete scan.
- About the read-only gate: HashHunter checks the source's read-only attribute. Volume-level protection — a write blocker or a read-only mount — is often invisible to that attribute, so the gate may still object. In that case tick "Source is not read-only — continue anyway (audited)" (CLI:
--force): your mount or blocker provides the actual protection, and the override itself is documented in the audit log. - Start the scan. Live progress shows files/s, MB/s, and ETA. Cancelling is clean: everything processed so far is kept, the remainder is recorded as
not_verifiable.
- Matches tab: every alert hit with its list and hash type; open the report or the case folder from there.
- DB tab: everything the scan saw — search, sort, filter by status. Look at
read_error,access_denied, and enumeration errors so you know exactly what could not be checked. - The report (PDF + CSVs) is written automatically at scan end. (CLI:
report --case …regenerates it — on a signed case only where the signing key is available.)
- Export tab → confirm the prompt (CLI:
export --case … --confirm-export). - Only alert matches are copied. Every copy is re-hashed and compared against the recorded values, listed in
export-manifest.csv, and audited file by file.
verify-audit --case …checks the audit hash chain;verify-manifest --case …checks the Ed25519 signature — at any later point, on any machine that has the public key.- Archive the whole case folder: it is self-contained (case database, audit chain, signed manifest, reports, the frozen reference-list snapshot, and logs).
Plain text, one hash per line. The type is detected from the token length (32 hex characters = MD5, 40 = SHA-1, 64 = SHA-256) and a single list may mix types freely. Real-world formats are tolerated: md5sum/sha256sum output ("hash filename"), certutil output (grouped hex), UTF-8 BOM, CRLF line endings, uppercase hex, even stray non-UTF-8 bytes in file-name columns. Invalid lines are counted and reported, never silently dropped. Each import records the source file's SHA-256 for chain of custody.
Each scan creates one self-contained folder under Cases\:
Cases\Case_4711_2026-07-14_120000\
├── case.sqlite every enumerated file: hashes, timestamps, status
├── audit\
│ ├── audit.jsonl tamper-evident audit log (SHA-256 hash chain)
│ ├── integrity.json SHA-256 manifest of the case artifacts
│ └── integrity.json.sig Ed25519 signature (when a signing key exists)
├── reports\
│ ├── report.pdf the case report
│ ├── report.typ its typst source (archival, manual re-rendering)
│ ├── report.csv one row per match, spreadsheet-friendly
│ └── technical-annex.csv status counts, errors, lists, key figures
├── exports\ exported matches + export-manifest.csv
├── config\
│ ├── effective-config.json the configuration this scan actually ran with
│ └── reference-lists.json the reference lists as frozen for this case
└── logs\technical.log the technical log of the run
Next to the reference database, Hashes\refdb-audit.jsonl records every list import, replacement, and deletion, and Hashes\query-audit.jsonl every manual lookup — both as hash chains.
| Crate | Role |
|---|---|
hh-core |
The engine: hashing, enumeration, LMDB/SQLite, audit chain, signing, filters |
hh-app |
Orchestration shared by CLI and GUI — scans, imports, reports, exports |
hh-report |
PDF/CSV generation, decoupled from the engine |
hh-cli |
Command-line binary (hashhunter.exe) |
hh-gui |
Slint desktop interface (its own workspace, hh-gui.exe) |
Warning
The read-only check on the source is a heuristic. It is no substitute for a hardware write blocker or a read-only mounted image, and neither the UI nor the report claims otherwise.
- MD5 and SHA-1 serve recognition; a SHA-256 match is the defensible result. The report keeps that distinction visible.
- Exporting matches copies evidence material. That is legally sensitive, which is why it demands explicit confirmation and is audited file by file.
- Regenerating reports re-seals the integrity manifest. On a signed case this requires the signing key — otherwise the old signature would be orphaned and the case would wrongly look tampered with.
- Protect the private signing key (
Hashes\keys\hashhunter-signing.key). On Windows its ACL is restricted to the current user at creation.
HashHunter stands on a stack of excellent open-source work. Copyright remains with the respective authors; their licenses apply.
Core: heed (LMDB) · rusqlite (SQLite) · rayon · walkdir · sha2 · sha1 · md-5 · hex · ed25519-dalek · rand · serde · serde_json · anyhow · thiserror · tracing · fs2 · infer · time Interfaces: slint · rfd · open · clap · tracing-subscriber · tracing-appender Reports: printpdf · typst (embedded via typst-as-lib, optional) Build: slint-build · winresource
This project is licensed under the MIT license, providing users with flexibility and freedom to use and modify the software according to their needs.
Contributions are welcome! Please fork the repository and submit a pull request for review.
This software is provided "as is", without warranty of any kind. It supports forensic triage; it does not replace the examiner's judgment or responsibility. Verify results independently before relying on them in proceedings, seek legal review before using the export function in production casework, and use the tool only on data you are authorized to examine.
This tool has been tailored to fit my specific professional needs, and while it may seem like a small tool, it has a significant impact on my workflow. HashHunter will not replace a full forensic suite, and it does not try to. It answers the daily "is any of this already known?" question quickly, completely, and with documentation you can hand over as-is. If it saves you from re-running a scan just because the paperwork was missing — or spares a console-averse colleague their first encounter with a command line — it has done its job. Greetings to my dear colleagues [^4] who avoid scripts like the plague and think that consoles and Bash are some sort of dark magic – the compiled version will spare you the console kung-fu and hopefully be a helpful tool for you as well. 😉