rust 129 lines · 3 tabs

Pluggable Password Hasher Trait in Rust with Argon2 and Bcrypt Backends

Shared by codesnips Aug 2026
3 tabs
use std::fmt;

#[derive(Debug)]
pub enum HashError {
    Backend(String),
    InvalidHash,
}

impl fmt::Display for HashError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            HashError::Backend(m) => write!(f, "hash backend error: {}", m),
            HashError::InvalidHash => write!(f, "stored hash is malformed"),
        }
    }
}

impl std::error::Error for HashError {}

pub trait PasswordHasher: Send + Sync {
    fn hash(&self, plaintext: &str) -> Result<String, HashError>;

    fn verify(&self, plaintext: &str, stored: &str) -> Result<bool, HashError>;

    fn needs_rehash(&self, _stored: &str) -> bool {
        false
    }
}
3 files · rust Explain with highlit

Password hashing algorithms change over time: bcrypt was fine for years, Argon2 is the current recommendation, and whatever replaces it will arrive eventually. Hard-coding a single algorithm into an authentication service makes migration painful, so this snippet defines a PasswordHasher trait as a stable seam between the auth logic and the concrete hashing backend.

In hasher.rs, the PasswordHasher trait declares two methods: hash produces a self-describing PHC-format string, and verify checks a plaintext candidate against a stored hash. The trait is object-safe, so it can be stored behind a Box<dyn PasswordHasher> and swapped at runtime or in tests. HashError wraps the failure modes into one type via From conversions, keeping call sites clean. Returning a bool from verify rather than a Result<()> distinguishes a genuine mismatch from an operational error like a corrupt hash string.

backends.rs provides two implementations. Argon2Hasher uses the argon2 crate with a random salt from OsRng and encodes parameters into the output, while BcryptHasher wraps the bcrypt crate with a configurable cost. Because each hash string embeds its own algorithm identifier and parameters, verify can validate hashes produced under older settings — critical for rolling password migrations where existing users keep their bcrypt hashes until their next login.

auth_service.rs shows the payoff. AuthService holds a boxed hasher and never names a concrete algorithm. authenticate looks up the stored hash and delegates to verify; on success it calls needs_rehash to detect hashes made by a weaker or outdated scheme and transparently upgrades them using the current hasher. This is the standard pattern for phasing in a stronger algorithm without forcing a password reset.

The trade-off is a small amount of dynamic dispatch and the discipline of a self-describing hash format, in exchange for testability and painless algorithm migration. A pitfall to avoid is comparing hashes with ==; verification must always go through the algorithm's constant-time check, which both backends here delegate to their crates.


Related snips

Share this code

Here's the card — post it anywhere.

Pluggable Password Hasher Trait in Rust with Argon2 and Bcrypt Backends — share card
Link copied