From 722f7d40eb7068a575ed599acd7b888365d740ee Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 14 Dec 2025 12:13:20 -0700 Subject: [PATCH] Add `PasswordVerifier` impls For algorithms where there is only one canonical encoding (PHC or MCF), adds `PasswordVerifier` impls which mean the user doesn't have to first parse the hash. It would be good if there were a better conversion from `mcf::Error` to `password_hash::Error`, perhaps the latter could pull in the `mcf` crate optionally like it does with `phc` and have an error conversion impl. --- argon2/src/lib.rs | 11 +++++++++-- balloon-hash/src/lib.rs | 15 +++++++++++++-- pbkdf2/src/phc.rs | 4 ++-- scrypt/src/phc.rs | 4 ++-- sha-crypt/src/mcf.rs | 11 +++++++++++ yescrypt/src/mcf.rs | 8 ++++++++ 6 files changed, 45 insertions(+), 8 deletions(-) diff --git a/argon2/src/lib.rs b/argon2/src/lib.rs index 674e463d..07945614 100644 --- a/argon2/src/lib.rs +++ b/argon2/src/lib.rs @@ -649,7 +649,7 @@ impl PasswordHasher for Argon2<'_> { password: &[u8], salt: &[u8], ) -> password_hash::Result { - let salt = Salt::new(salt).map_err(|_| password_hash::Error::SaltInvalid)?; + let salt = Salt::new(salt)?; let output_len = self .params @@ -662,7 +662,7 @@ impl PasswordHasher for Argon2<'_> { .ok_or(password_hash::Error::OutputSize)?; self.hash_password_into(password, &salt, out)?; - let output = Output::new(out).map_err(|_| password_hash::Error::OutputSize)?; + let output = Output::new(out)?; Ok(PasswordHash { algorithm: self.algorithm.ident(), @@ -674,6 +674,13 @@ impl PasswordHasher for Argon2<'_> { } } +#[cfg(all(feature = "alloc", feature = "password-hash"))] +impl PasswordVerifier for Argon2<'_> { + fn verify_password(&self, password: &[u8], hash: &str) -> password_hash::Result<()> { + self.verify_password(password, &PasswordHash::new(hash)?) + } +} + impl From for Argon2<'_> { fn from(params: Params) -> Self { Self::new(Algorithm::default(), Version::default(), params) diff --git a/balloon-hash/src/lib.rs b/balloon-hash/src/lib.rs index 9be4a46e..02deb1ae 100644 --- a/balloon-hash/src/lib.rs +++ b/balloon-hash/src/lib.rs @@ -234,9 +234,9 @@ where password: &[u8], salt: &[u8], ) -> password_hash::Result { - let salt = Salt::new(salt).map_err(|_| password_hash::Error::SaltInvalid)?; + let salt = Salt::new(salt)?; let hash = self.hash(password, &salt)?; - let output = Output::new(&hash).map_err(|_| password_hash::Error::OutputSize)?; + let output = Output::new(&hash)?; Ok(PasswordHash { algorithm: self.algorithm.ident(), @@ -248,6 +248,17 @@ where } } +#[cfg(all(feature = "alloc", feature = "password-hash"))] +impl PasswordVerifier for Balloon<'_, D> +where + D: Digest + FixedOutputReset, + Array: ArrayDecoding, +{ + fn verify_password(&self, password: &[u8], hash: &str) -> password_hash::Result<()> { + self.verify_password(password, &PasswordHash::new(hash)?) + } +} + impl From for Balloon<'_, D> where Array: ArrayDecoding, diff --git a/pbkdf2/src/phc.rs b/pbkdf2/src/phc.rs index 65c13890..524f336e 100644 --- a/pbkdf2/src/phc.rs +++ b/pbkdf2/src/phc.rs @@ -39,7 +39,7 @@ impl CustomizedPasswordHasher for Pbkdf2 { return Err(Error::Version); } - let salt = Salt::new(salt).map_err(|_| Error::SaltInvalid)?; + let salt = Salt::new(salt)?; let mut buffer = [0u8; Output::MAX_LENGTH]; let out = buffer @@ -54,7 +54,7 @@ impl CustomizedPasswordHasher for Pbkdf2 { }; f(password, &salt, params.rounds, out); - let output = Output::new(out).map_err(|_| Error::OutputSize)?; + let output = Output::new(out)?; Ok(PasswordHash { algorithm: *algorithm.ident(), diff --git a/scrypt/src/phc.rs b/scrypt/src/phc.rs index f2b759a1..d0fc5b63 100644 --- a/scrypt/src/phc.rs +++ b/scrypt/src/phc.rs @@ -39,13 +39,13 @@ impl CustomizedPasswordHasher for Scrypt { return Err(Error::Version); } - let salt = Salt::new(salt).map_err(|_| Error::SaltInvalid)?; + let salt = Salt::new(salt)?; let len = params.len.unwrap_or(Params::RECOMMENDED_LEN); let mut buffer = [0u8; Output::MAX_LENGTH]; let out = buffer.get_mut(..len).ok_or(Error::OutputSize)?; scrypt(password, &salt, ¶ms, out).map_err(|_| Error::OutputSize)?; - let output = Output::new(out).map_err(|_| Error::OutputSize)?; + let output = Output::new(out)?; Ok(PasswordHash { algorithm: ALG_ID, diff --git a/sha-crypt/src/mcf.rs b/sha-crypt/src/mcf.rs index 07297859..8d848b92 100644 --- a/sha-crypt/src/mcf.rs +++ b/sha-crypt/src/mcf.rs @@ -144,6 +144,17 @@ where } } +impl PasswordVerifier for ShaCrypt +where + Self: ShaCryptCore, +{ + fn verify_password(&self, password: &[u8], hash: &str) -> password_hash::Result<()> { + // TODO(tarcieri): better mapping from `mcf::Error` and `password_hash::Error`? + let hash = PasswordHashRef::new(hash).map_err(|_| Error::EncodingInvalid)?; + self.verify_password(password, hash) + } +} + impl ShaCryptCore for ShaCrypt { const MCF_ID: &'static str = "5"; type Output = [u8; BLOCK_SIZE_SHA256]; diff --git a/yescrypt/src/mcf.rs b/yescrypt/src/mcf.rs index b1ba9e5a..5e4ffcb9 100644 --- a/yescrypt/src/mcf.rs +++ b/yescrypt/src/mcf.rs @@ -127,3 +127,11 @@ impl PasswordVerifier for Yescrypt { Ok(()) } } + +impl PasswordVerifier for Yescrypt { + fn verify_password(&self, password: &[u8], hash: &str) -> Result<()> { + // TODO(tarcieri): better mapping from `mcf::Error` and `password_hash::Error`? + let hash = PasswordHashRef::new(hash).map_err(|_| Error::EncodingInvalid)?; + self.verify_password(password, hash) + } +}