pwhash
A pure-Rust library for hashing and verifying passwords with bcrypt, SHA-crypt, MD5-crypt, and other Unix crypt(3) algorithms.
Repository Health
Technical Analysis
pwhash is a pure-Rust implementation of seven classic Unix password-hashing algorithms, covering everything from legacy DES crypt to modern bcrypt and SHA-512 crypt. Each algorithm lives in its own module and exposes the same three-function interface — verify, hash, and hash_with — so callers can drop in whichever scheme matches an existing hash format without learning a new API per algorithm.
The crate targets systems that need to interoperate with Unix-style modular crypt hashes (the $id$salt$hash format used by /etc/shadow and similar files) rather than greenfield password storage, where a memory-hard algorithm like Argon2 is usually preferable. A convenience unix module auto-detects the algorithm from a hash’s prefix and dispatches to the right implementation, making it straightforward to verify passwords against hashes produced by any of the seven supported schemes.
What You Get
- Seven Unix-compatible hashing algorithms — bcrypt, SHA-512/SHA-256/SHA1 crypt, MD5 crypt, BSDi crypt, and DES crypt — each in its own module.
- A consistent hash/verify/hash_with interface repeated across every algorithm module, so switching schemes doesn’t mean learning a new API.
- A unix module that auto-detects the algorithm from a hash string’s prefix and dispatches to the right verifier, useful for validating against /etc/shadow-style hashes of unknown origin.
- Customizable hashing parameters (salt, rounds/cost) via a shared HashSetup struct, plus bcrypt’s own BcryptSetup for explicit variant selection (2a/2b/2y).
- Constant-time hash comparison built into every verify function to avoid timing side-channels.
Common Use Cases
- Legacy system migration - a team migrating a Unix authentication database imports existing crypt(3) hashes and verifies passwords against them with pwhash::unix::verify without re-hashing every account up front.
- Interop with system password databases - a Rust daemon reading /etc/shadow-style entries validates logins against MD5, SHA-256, or SHA-512 crypt hashes without shelling out to libc crypt().
- Password migration tooling - an engineer writing a one-off script re-hashes bcrypt hashes carried over from an old PHP or Node app (which use $2y$ or $2a$ prefixes) directly in Rust with correct variant handling.
- Cross-language hash compatibility testing - a developer verifies that hashes generated by pwhash’s bcrypt module match output from OpenBSD or Openwall’s C implementations for a given salt and cost.
Under The Hood
Architecture Each algorithm gets its own module (bcrypt.rs, md5_crypt.rs, sha1_crypt.rs, sha2_crypt.rs as a shared base for sha256_crypt.rs/sha512_crypt.rs, bsdi_crypt.rs, des_crypt.rs, unix_crypt.rs), while lib.rs centralizes shared plumbing: a crate-wide Result alias, the HashSetup struct, an IntoHashSetup trait, a FindNul trait for truncating passwords at embedded NUL bytes, a constant-time consteq comparison helper, and two private submodules — random for salt generation and parse for a HashSlice/HashIterator abstraction that walks a hash string’s delimited fields. enc_dec.rs centralizes the different base64-like alphabets each format uses (bcrypt’s own ordering versus the standard crypt alphabet) so encoding logic isn’t duplicated per module. Every algorithm module repeats the same adapter pattern seen in bcrypt.rs’s IntoBcryptSetup trait, implemented for a raw hash &str (to reuse an existing hash’s parameters), a generic HashSetup, and the algorithm’s own typed setup struct — letting hash_with accept whichever form is convenient. A public unix module in lib.rs ties everything together, inspecting a hash’s prefix and dispatching to the matching algorithm’s hash_with. Because every module depends on the shared HashSetup/IntoHashSetup contract, changing that core abstraction would ripple through every algorithm’s public signature.
Tech Stack pwhash is a pure-Rust crate (2021 edition) with no async runtime, web framework, or FFI. Dependencies are the RustCrypto hash/cipher primitives — md-5, sha-1, sha2, and blowfish (with its bcrypt feature) — plus hmac for SHA1-crypt, byteorder for endian-aware handling of Blowfish’s encrypted output, and rand 0.8 (via OsRng) for salt generation. CI (.github/workflows/build.yml) runs cargo build and cargo test with RUSTFLAGS=“-D warnings” across a matrix of ubuntu-latest, windows-2019, and macOS-latest on both stable and nightly Rust, giving reasonably broad platform coverage for a pure-computation crate.
Code Quality The crate has 18 #[cfg(test)] test functions spread across its modules, including hash/verify round-trip checks against known-good constants (e.g. bcrypt.rs’s variant test) and dedicated parser tests for the HashSlice abstraction covering empty strings, drained iterators, and delimiter edge cases. Errors are represented by a custom Error enum in error.rs implementing Display and std::error::Error, propagated through the crate’s Result<T> alias rather than panicking on bad input; the handful of assert! calls inside the internal bcrypt() function enforce invariants (salt length, password length, output buffer size) already validated by their callers, not public-API panics. Naming is consistent snake_case throughout, and parsing logic is centralized in one HashIterator trait rather than duplicated per algorithm. CI enforces a warnings-as-errors build and test run on every push and PR across three operating systems, which is a meaningful automated quality gate even though the crate has no clippy configuration or measured coverage.
What Makes It Unique pwhash isn’t algorithmically novel — it reimplements well-documented legacy Unix crypt(3) formats rather than inventing new cryptography. Its actual value is completeness and interop: it’s one of the few pure-Rust crates covering the full spread of Unix modular crypt formats behind one uniform API, including older schemes (DES crypt, BSDi crypt) that most modern Rust password crates skip in favor of bcrypt or Argon2 alone, plus a self-detecting unix::verify dispatcher that picks the right algorithm from a hash’s prefix automatically — a specific fit for migration and interop scenarios where hashes originated outside Rust.