| .githooks | ||
| include | ||
| src | ||
| tests | ||
| .gitignore | ||
| .markdownlint-cli2.jsonc | ||
| CHANGELOG.md | ||
| cliff.toml | ||
| CONTRIBUTING.md | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
| TESTING.md | ||
RexCrypt
RexCrypt is a small, educational command-line tool that demonstrates password hashing with the Unix crypt(3) API (via libxcrypt/glibc). It's designed as a teaching aid: clear CLI, heavily commented source, and practical notes about algorithms, salts, and parsing pitfalls.
Why this exists
- Show how to pick a hashing algorithm via salt prefix.
- Show how to generate salts with
crypt_gensalt(3)and hash withcrypt_r(3). - Teach robust CLI parsing (e.g. handling
errno,ERANGE/EINVAL, and junk suffixes). - Illustrate practical tradeoffs (defaults vs explicit parameters, portability, and debug tracing).
⚠️ This is a teaching tool. Don’t copy-paste it into production without a proper security review.
Disclaimer
This project is an educational example, not a production-ready password system. It demonstrates how the historical crypt(3) interface works and how to reason about salts, algorithms, and CLI parsing. The presence of legacy algorithms (MD5, SHA-1, NT, SunMD5, BSDi) is for completeness and interoperability only -- not an endorsement.
No warranty is provided. Use at your own risk. If you adapt any of this code, perform a full security review and prefer modern, memory-hard KDFs and vetted libraries appropriate to your platform and threat model.
Features
- Multiple algorithms (availability depends on your libxcrypt build):
0→ library default/best1MD5 ($1$)2NT ($3$, legacy)3BSDi (_)4SunMD5 ($md5, legacy)5SHA-1 ($sha1, legacy)6SHA-256 ($5$)7SHA-512 ($6$)8bcrypt ($2b$)9scrypt ($7$)aGOST-yescrypt ($gy$)byescrypt ($y$)
- Thread-safe hashing using
crypt_r(3). - Clear CLI with
getopt(3)and defensive parsing. - Built-in help (-?) providing concise
usage()with options, algorithm table, and examples (prints to stderr for easy scripting). - TTY secret entry via
getpass(3)when-kis omitted. - Non-TTY handling: if stdin is not a TTY and no
-kis provided, RexCrypt exits with an error. - Passphrase length cap: input is bound by
CRYPT_MAX_PASSPHRASE_SIZE(commonly 512 bytes) to match the library'sstruct crypt_datalimit. - Hash verification provided by
-Voption flag, allowing verification that a given key (via-k) produces a known hash; outputs "OK"/"FAIL" with appropriate exit code, accordingly. - List algorithms via
-L, outputting the name of the algorithm & its availability - Secure memory wipe: demonstrates memory security through
explicit_bzero()or volatile memset, depending on availability. - Readable DEBUG traces (compile-time flag) for step-through demos.
- Companion tool:
rexcrack-- multithreaded dictionary-attack demo usingcrypt_r(3). Validates hashes, supports legacy DES/bigcrypt for teaching, prints the winning password on stdout; verbose mode logs to stderr. Exit codes0(match),2(not found),EX_*for errors. - Makefile workflow with common targets (
make,make test,make clean,make distclean) and simple knobs (CC,CFLAGS). - TAP v14 tests via
make test: quick checks for prefixes, stdin path (-k -), and non-TTY handling.
Install & Build
Dependencies
- A C compiler (clang or gcc)
- libxcrypt /
libcryptheaders and library
On Debian/Ubuntu:
sudo apt install clang libcrypt-dev
Build
Using the provided Makefile:
git clone https://repo.feedthetrolls.net/fosster/rexcrypt.git
cd rexcrypt
make
Outputs ./bin/rexcrypt and ./bin/rexcrack.
Housekeeping:
make clean # removes compiled binaries
make distclean # removes all generated files/directories
Tip: If you prefer a manual compile, a common line is:
# rexcrypt clang -Wall -Wextra -W -O2 -Iinclude -o bin/rexcrypt src/rexcrypt.c -lcrypt # rexcrack (pthreads) clang -Wall -Wextra -W -O2 -Iinclude -o bin/rexcrack src/rexcrack.c -lcrypt -pthread
Usage
rexcrypt [-c <count>] [-r <resalt>] [-a <alg>] [-k <key>|-] [-s <salt>] [-V <hash>]
Options
-c <cpu_time>: Set the library-defined cost/rounds/time knob.-r <resalt>: Regenerate the salt N extra times after first generation (default0).-a <algorithm>: Select algorithm (see table above). Accepts0..9and lettersa/b.-k <key>: Provide the secret inline. If omitted, RexCrypt prompts viagetpass().-k -: Read the secret from stdin (for scripting; echo is not disabled).-s <salt>: Provide a deterministic salt to generate a consistent hash.-V <hash>: Specify a known hash to verify a given key (via-k).
Examples
# Best available default algorithm; prompt for password
./bin/rexcrypt
# List algorithms supported & their availability
./bin/rexcrypt -L
# SHA-512 with a time/rounds knob of 5000 (value semantics depend on algorithm; check `man 5 crypt`)
./bin/rexcrypt -a 7 -c 5000 -k "correct horse battery staple"
# yescrypt (letter form)
./bin/rexcrypt -a b -k "hunter2"
# scrypt, resalt twice -- 3 total
./bin/rexcrypt -a 9 -r 2 -k "super secret"
# read secret from stdin (newline stripped)
printf 'supers3cret\n' | ./bin/rexcrypt -k -
# verify a specified hash matches a given key
./bin/rexcrypt -k "Joshua" -V '$5$jbfeceBV2R3cStO9$tt.VJazWc0bKk3x7fNqeCXfmy43FT3/DZUbdIjYuHE0'
Exit codes
0on success (hash printed to stdout)- Non-zero on invalid arguments or library failures
Notes on algorithm support
- Not all prefixes are supported on all systems. If an unsupported prefix is requested, RexCrypt will fall back to the library default/best (detected at runtime).
-L(no argument) can be used to walk through the table of algorithms, probing each algorithm for support on the system, and listing its availability status.
RexCrack -- dictionary-attack demo (educational)
Try candidates from a wordlist against a specified hash using the same "hash-as-setting' verification shown in RexCrypt. Miltithreaded; one struct crypt_data per thread.
RexCrack Usage
rexcrack -h <hash> -w <wordlist> [-t <threads>] [-v]
-h <hash>: specify hash (exact string as produced byrexcrypt)-w <wordlist>: newline-separated candidates-t <threads>: number of threads (default: number of CPUs; min 1; soft cap 64)-v: verbose (prints attempts/progress to stderr; stdout stays clean for the winner)
RexCrack Exit codes
0-> match found (prints the password on stdout)2-> not found- errors ->
EX_*from<sysexits.h>(e.g.EX_NOINPUTfor bad file,EX_DATAERRfor invalid hash)
RexCrack Notes
- Hash validation uses anchored regexes (and
crypt_checksalt()when available). Legacy DES/bigcrypt are intentionally supported for teaching. - Wordlists with overlong and blank lines are handled correctly (overlong lines are drained).
Debug & Testing
make debug # build with debug symbols & DEBUG flag enabled
make test # build & run TAP v14 test suite
Example Testing Output:
>>> Running tests
TAP version 14
1..4
ok 1 - sha512 (-a 7) prefix correct
ok 2 - yescrypt (-a b) prefix correct
ok 3 - stdin (-k -) path works
ok 4 - non-tty without -k fails as expected
ok 5 - algorithm defaults correctly
ok 6 - invalid algorithm fails correctly
ok 7 - negative cpu_time fails correctly
ok 8 - bcrypt hash matches regex pattern
ok 9 - scrypt hash length correct
ok 10 - md5crypt hash match
ok 11 - verify OK -- match against specified hash
ok 12 - verify OK -- mismatch fails as expected
Tests treat rexcrypt as a black-box binary and cover:
- prefix checks for representative algorithms
-k -(stdin) path- non-TTY without
-k(expected error path) - algorithm defaults
- improper argument failures
- hash matching
The TAP suite also exercises rexcrack:
- match ->
0, not found ->2 - bad wordlist file ->
EX_NOINPUT - invalid hash ->
EX_DATAERR - stdout/stderr discipline (winnder only on stdout; verbose logs on stderr)
- overlong line handling
- legacy DES acceptance (educational)
See TESTING for more details on testing RexCrypt, including designing new tests.
Security & Pedagogy Notes
getpass(3)is legacy and may be unavailable on some platforms; it's fine for teaching. See alsoreadpassphrase(3)from BSD(libbsd-dev on Debian).- The hashed output is sensitive -- treat it like a credential.
- The code includes commentary about parsing pitfalls (
strtol,errno, and input validation) and pointers about memory hygiene. - Method-specific limits apply; RexCrypt caps input to
CRYPT_MAX_PASSPHRASE_SIZEto match the library. - Though steps are taken for memory security, the methods are known not to be fool-proof, though their practice is recommended. See also
explicit_bzero(3)for additional notes on the security implications & caveats of its use.
Development
This repository uses Conventional Comits, local Git hooks, and auto-generated changelogs (via git-cliff). If you'd like to contribute or run the tooling locally, see CONTRIBUTING.
License
This project is licensed under the PolyForm Noncommercial License 1.0.0. See the LICENSE file for more details.
Contributing
Issues and PRs that improve clarity, portability, or pedagogy are welcome -- especially notes that help students understand why choices were made (not just what they are).