From bbd389836684725316137e8b1e323ca89552ac1c Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 4 Jan 2026 11:06:54 -0700 Subject: [PATCH] pbkdf2: customizable `Params` for `Pbkdf2` type Following the general pattern of `Argon2` and `Scrypt`, allows the `Params` used with the `Pbkdf2` type to be customized. See also: #797 --- README.md | 6 +++++- password-auth/src/lib.rs | 4 ++-- pbkdf2/src/lib.rs | 5 +++-- pbkdf2/src/phc.rs | 45 +++++++++++++++++++++++++++++++++++----- pbkdf2/tests/phc.rs | 2 +- 5 files changed, 51 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e499d719..212bea57 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,11 @@ let input_password = "password"; let password_hash = phc::PasswordHash::new(&hash_string)?; // Trait objects for algorithms to support -let algs: &[&dyn PasswordVerifier] = &[&Argon2::default(), &Pbkdf2, &Scrypt::default()]; +let algs: &[&dyn PasswordVerifier] = &[ + &Argon2::default(), + &Pbkdf2::default(), + &Scrypt::default() +]; for alg in algs { if alg.verify_password(input_password.as_ref(), &password_hash).is_ok() { diff --git a/password-auth/src/lib.rs b/password-auth/src/lib.rs index c16aa529..f987eb89 100644 --- a/password-auth/src/lib.rs +++ b/password-auth/src/lib.rs @@ -67,7 +67,7 @@ fn generate_phc_hash(password: &[u8]) -> password_hash::Result { return Scrypt::default().hash_password(password); #[cfg(feature = "pbkdf2")] - return Pbkdf2.hash_password(password); + return Pbkdf2::default().hash_password(password); } /// Verify the provided password against the provided password hash. @@ -84,7 +84,7 @@ pub fn verify_password(password: impl AsRef<[u8]>, hash: &str) -> Result<(), Ver #[cfg(feature = "argon2")] &Argon2::default(), #[cfg(feature = "pbkdf2")] - &Pbkdf2, + &Pbkdf2::default(), #[cfg(feature = "scrypt")] &Scrypt::default(), ]; diff --git a/pbkdf2/src/lib.rs b/pbkdf2/src/lib.rs index dec0b3b1..8c3388e0 100644 --- a/pbkdf2/src/lib.rs +++ b/pbkdf2/src/lib.rs @@ -69,14 +69,15 @@ //! Pbkdf2 //! }; //! +//! let pbkdf2 = Pbkdf2::new(); // Uses `Params::RECOMMENDED` //! let password = b"hunter42"; // Bad password; don't actually use! //! //! // Hash password to PHC string ($pbkdf2-sha256$...) -//! let password_hash = Pbkdf2.hash_password(password)?.to_string(); +//! let password_hash = pbkdf2.hash_password(password)?.to_string(); //! //! // Verify password against PHC string //! let parsed_hash = PasswordHash::new(&password_hash)?; -//! assert!(Pbkdf2.verify_password(password, &parsed_hash).is_ok()); +//! assert!(pbkdf2.verify_password(password, &parsed_hash).is_ok()); //! # Ok(()) //! # } //! ``` diff --git a/pbkdf2/src/phc.rs b/pbkdf2/src/phc.rs index 524f336e..b68da274 100644 --- a/pbkdf2/src/phc.rs +++ b/pbkdf2/src/phc.rs @@ -15,8 +15,35 @@ use sha2::{Sha256, Sha512}; use sha1::Sha1; /// PBKDF2 type for use with [`PasswordHasher`]. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Pbkdf2; +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub struct Pbkdf2 { + /// Algorithm to use + algorithm: Algorithm, + + /// Default parameters to use. + params: Params, +} + +impl Pbkdf2 { + /// Initialize [`Pbkdf2`] with default parameters. + pub const fn new() -> Self { + Self::new_with_params(Params::RECOMMENDED) + } + + /// Initialize [`Pbkdf2`] with the provided parameters. + pub const fn new_with_params(params: Params) -> Self { + Self { + algorithm: Algorithm::RECOMMENDED, + params, + } + } +} + +impl From for Pbkdf2 { + fn from(params: Params) -> Self { + Self::new_with_params(params) + } +} impl CustomizedPasswordHasher for Pbkdf2 { type Params = Params; @@ -32,7 +59,7 @@ impl CustomizedPasswordHasher for Pbkdf2 { let algorithm = alg_id .map(Algorithm::try_from) .transpose()? - .unwrap_or_default(); + .unwrap_or(self.algorithm); // Versions unsupported if version.is_some() { @@ -68,7 +95,7 @@ impl CustomizedPasswordHasher for Pbkdf2 { impl PasswordHasher for Pbkdf2 { fn hash_password_with_salt(&self, password: &[u8], salt: &[u8]) -> Result { - self.hash_password_customized(password, salt, None, None, Params::default()) + self.hash_password_customized(password, salt, None, None, self.params) } } @@ -97,7 +124,7 @@ impl Default for Algorithm { /// /// [OWASP cheat sheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html fn default() -> Self { - Self::Pbkdf2Sha256 + Self::RECOMMENDED } } @@ -112,6 +139,14 @@ impl Algorithm { /// PBKDF2 (SHA-512) algorithm identifier pub const PBKDF2_SHA512_IDENT: Ident = Ident::new_unwrap("pbkdf2-sha512"); + /// Default algorithm suggested by the [OWASP cheat sheet]: + /// + /// > Use PBKDF2 with a work factor of 600,000 or more and set with an + /// > internal hash function of HMAC-SHA-256. + /// + /// [OWASP cheat sheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html + const RECOMMENDED: Self = Self::Pbkdf2Sha256; + /// Parse an [`Algorithm`] from the provided string. pub fn new(id: impl AsRef) -> Result { id.as_ref().parse() diff --git a/pbkdf2/tests/phc.rs b/pbkdf2/tests/phc.rs index d8b3a486..65d16f79 100644 --- a/pbkdf2/tests/phc.rs +++ b/pbkdf2/tests/phc.rs @@ -28,7 +28,7 @@ fn hash_with_default_algorithm() { output_length: 40, }; - let hash = Pbkdf2 + let hash = Pbkdf2::new() .hash_password_customized(PASSWORD, SALT, None, None, params) .unwrap();