From 94ce3f73926bfb7beaabd223ca0c5232e58c6649 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 14 Dec 2025 08:39:06 -0700 Subject: [PATCH] password-hash: expose salt generating helper functions Right now there's not a way to generate a random salt in the context of MCF password hashes when not using the high-level `PasswordHash::hash_password` API, e.g. for KDF usage, which is to say that for PHC password hashes `phc::Salt::generate` is fine for this purpose since it now provides raw bytes, but password hashes which use MCF don't have access to that type. This is often still documented as using some static value with a comment saying it needs to be unique. Instead we should provide a reusable solution in the rustdoc. These can potentially be replaced with the `crypto_common::Generate` trait and something like a `RecommendedLengthSalt` type after there's a new `getrandom` crate prerelease, but in the meantime these helper functions seem like a reasonable enough stopgap, and can potentially stick around and use the `Generate` trait for you. --- password-hash/src/error.rs | 8 ++++++++ password-hash/src/lib.rs | 23 ++++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/password-hash/src/error.rs b/password-hash/src/error.rs index 765188305..6a8dd8949 100644 --- a/password-hash/src/error.rs +++ b/password-hash/src/error.rs @@ -66,6 +66,14 @@ impl fmt::Display for Error { impl core::error::Error for Error {} +#[cfg(feature = "getrandom")] +impl From for Error { + fn from(_: getrandom::Error) -> Self { + // TODO(tarcieri): should we have a specific variant for RNGs errors? + Error::Crypto + } +} + #[cfg(feature = "phc")] impl From for Error { fn from(err: phc::Error) -> Self { diff --git a/password-hash/src/lib.rs b/password-hash/src/lib.rs index 09813c2b1..4f956ff99 100644 --- a/password-hash/src/lib.rs +++ b/password-hash/src/lib.rs @@ -37,7 +37,7 @@ pub use crate::error::{Error, Result}; pub use phc; #[cfg(feature = "rand_core")] -pub use rand_core::{self, TryCryptoRng}; +pub use rand_core; /// DEPRECATED: import this as `password_hash::phc::PasswordHash`. #[cfg(feature = "phc")] @@ -61,6 +61,9 @@ use core::{ str::FromStr, }; +#[cfg(feature = "rand_core")] +use rand_core::TryCryptoRng; + /// Numeric version identifier for password hashing algorithms. pub type Version = u32; @@ -94,8 +97,7 @@ pub trait PasswordHasher { /// A large random salt will be generated automatically. #[cfg(feature = "getrandom")] fn hash_password(&self, password: &[u8]) -> Result { - let mut salt = [0u8; RECOMMENDED_SALT_LEN]; - getrandom::fill(&mut salt).map_err(|_| Error::Crypto)?; + let salt = try_generate_salt()?; self.hash_password_with_salt(password, &salt) } @@ -211,3 +213,18 @@ pub trait McfHasher { /// algorithm-specific rules so hashers must parse a raw string themselves. fn upgrade_mcf_hash(&self, hash: &str) -> Result; } + +/// Generate a random salt value of the recommended length using the system's secure RNG. +#[cfg(feature = "getrandom")] +pub fn generate_salt() -> [u8; RECOMMENDED_SALT_LEN] { + try_generate_salt().expect("RNG failure") +} + +/// Try generating a random salt value of the recommended length using the system's secure RNG, +/// returning errors if they occur. +#[cfg(feature = "getrandom")] +pub fn try_generate_salt() -> core::result::Result<[u8; RECOMMENDED_SALT_LEN], getrandom::Error> { + let mut salt = [0u8; RECOMMENDED_SALT_LEN]; + getrandom::fill(&mut salt)?; + Ok(salt) +}