From 1f0eca0c0cf4f1fb4992eb31feae332d710d74c5 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Thu, 4 Dec 2025 17:18:45 -0700 Subject: [PATCH] password-hash: extract `CustomizedPasswordHasher` trait Splits out `PasswordHasher::hash_password_customized` into its own separate `CustomizedPasswordHasher` trait. This trait retains the associated `Params` type since that's used as a method argument. This also adds a type alias `Version` for `u32` to use in place of `Decimal` (also a `u32` alias) for the `Version` parameter to `hash_password_customized`, and changes the `salt` parameter to be `&'a str`, which removes any PHC-related types as input arguments to the method. However, there is still a dependency on `ParamsString` and `PasswordHash`, which still needs to be addressed to fully decouple the trait from PHC types. --- password-hash/src/lib.rs | 38 +++++++++++++++++----------------- password-hash/src/phc.rs | 2 +- password-hash/tests/hashing.rs | 21 ++++++++++++------- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/password-hash/src/lib.rs b/password-hash/src/lib.rs index 694354329..074467fb0 100644 --- a/password-hash/src/lib.rs +++ b/password-hash/src/lib.rs @@ -40,11 +40,23 @@ pub use phc::PasswordHash; #[cfg(feature = "alloc")] pub use phc::PasswordHashString; -use crate::phc::{Decimal, Ident, ParamsString, Salt}; +use crate::phc::ParamsString; use core::fmt::Debug; +/// Numeric version identifier for password hashing algorithms. +pub type Version = u32; + /// Trait for password hashing functions. pub trait PasswordHasher { + /// Simple API for computing a [`PasswordHash`] from a password and + /// salt value. + /// + /// Uses the default recommended parameters for a given algorithm. + fn hash_password<'a>(&self, password: &[u8], salt: &'a str) -> Result>; +} + +/// Trait for password hashing functions which support customization. +pub trait CustomizedPasswordHasher { /// Algorithm-specific parameters. type Params: Clone + Debug @@ -60,23 +72,11 @@ pub trait PasswordHasher { fn hash_password_customized<'a>( &self, password: &[u8], - algorithm: Option>, - version: Option, + algorithm: Option<&'a str>, + version: Option, params: Self::Params, - salt: impl Into>, + salt: &'a str, ) -> Result>; - - /// Simple API for computing a [`PasswordHash`] from a password and - /// salt value. - /// - /// Uses the default recommended parameters for a given algorithm. - fn hash_password<'a>( - &self, - password: &[u8], - salt: impl Into>, - ) -> Result> { - self.hash_password_customized(password, None, None, Self::Params::default(), salt) - } } /// Trait for password verification. @@ -93,15 +93,15 @@ pub trait PasswordVerifier { fn verify_password(&self, password: &[u8], hash: &PasswordHash<'_>) -> Result<()>; } -impl PasswordVerifier for T { +impl PasswordVerifier for T { fn verify_password(&self, password: &[u8], hash: &PasswordHash<'_>) -> Result<()> { if let (Some(salt), Some(expected_output)) = (&hash.salt, &hash.hash) { let computed_hash = self.hash_password_customized( password, - Some(hash.algorithm), + Some(hash.algorithm.as_str()), hash.version, T::Params::try_from(hash)?, - *salt, + salt.as_str(), )?; if let Some(computed_output) = &computed_hash.hash { diff --git a/password-hash/src/phc.rs b/password-hash/src/phc.rs index 048989b74..83e513959 100644 --- a/password-hash/src/phc.rs +++ b/password-hash/src/phc.rs @@ -161,7 +161,7 @@ impl<'a> PasswordHash<'a> { pub fn generate( phf: impl PasswordHasher, password: impl AsRef<[u8]>, - salt: impl Into>, + salt: &'a str, ) -> crate::Result { phf.hash_password(password.as_ref(), salt) } diff --git a/password-hash/tests/hashing.rs b/password-hash/tests/hashing.rs index 07c595101..4acc63043 100644 --- a/password-hash/tests/hashing.rs +++ b/password-hash/tests/hashing.rs @@ -1,7 +1,8 @@ //! Password hashing tests +use password_hash::PasswordHasher; pub use password_hash::{ - PasswordHasher, + CustomizedPasswordHasher, errors::{Error, Result}, phc::{Decimal, Ident, Output, ParamsString, PasswordHash, Salt}, }; @@ -12,21 +13,27 @@ const ALG: Ident = Ident::new_unwrap("example"); pub struct StubPasswordHasher; impl PasswordHasher for StubPasswordHasher { + fn hash_password<'a>(&self, password: &[u8], salt: &'a str) -> Result> { + self.hash_password_customized(password, None, None, StubParams, salt) + } +} + +impl CustomizedPasswordHasher for StubPasswordHasher { type Params = StubParams; fn hash_password_customized<'a>( &self, password: &[u8], - algorithm: Option>, + algorithm: Option<&'a str>, version: Option, params: StubParams, - salt: impl Into>, + salt: &'a str, ) -> Result> { - let salt = salt.into(); + let salt = Salt::from_b64(salt)?; let mut output = Vec::new(); if let Some(alg) = algorithm { - if alg != ALG { + if Ident::new(alg)? != ALG { return Err(Error::Algorithm); } } @@ -70,12 +77,12 @@ impl TryFrom for ParamsString { #[test] fn verify_password_hash() { let valid_password = "test password"; - let salt = Salt::from_b64("test-salt").unwrap(); + let salt = "test-salt"; let hash = PasswordHash::generate(StubPasswordHasher, valid_password, salt).unwrap(); // Sanity tests for StubFunction impl above assert_eq!(hash.algorithm, ALG); - assert_eq!(hash.salt.unwrap(), salt); + assert_eq!(hash.salt.unwrap().as_str(), salt); // Tests for generic password verification logic assert_eq!(