From 20c296a39c38d1cd3b048a17c9f7fbc7d6ac7dae Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 11 Jan 2026 11:47:57 -0700 Subject: [PATCH] sha-crypt: customized `Params` for `ShaCrypt` Adds the ability to customize the default params used by `ShaCrypt`, the Modular Crypt Format password hasher. --- sha-crypt/src/lib.rs | 4 ++-- sha-crypt/src/mcf.rs | 40 +++++++++++++++++++++++++++++++--------- sha-crypt/src/params.rs | 3 ++- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/sha-crypt/src/lib.rs b/sha-crypt/src/lib.rs index 28d3748c..3120900c 100644 --- a/sha-crypt/src/lib.rs +++ b/sha-crypt/src/lib.rs @@ -71,7 +71,7 @@ pub const BLOCK_SIZE_SHA512: usize = 64; /// - `params`: the parameters to use /// /// **WARNING: Make sure to compare this value in constant time!** -pub fn sha256_crypt(password: &[u8], salt: &[u8], params: &Params) -> [u8; BLOCK_SIZE_SHA256] { +pub fn sha256_crypt(password: &[u8], salt: &[u8], params: Params) -> [u8; BLOCK_SIZE_SHA256] { let pw_len = password.len(); let salt_len = salt.len(); @@ -160,7 +160,7 @@ pub fn sha256_crypt(password: &[u8], salt: &[u8], params: &Params) -> [u8; BLOCK /// - `params` - The parameters to use /// /// **WARNING: Make sure to compare this value in constant time!** -pub fn sha512_crypt(password: &[u8], salt: &[u8], params: &Params) -> [u8; BLOCK_SIZE_SHA512] { +pub fn sha512_crypt(password: &[u8], salt: &[u8], params: Params) -> [u8; BLOCK_SIZE_SHA512] { let pw_len = password.len(); let salt_len = salt.len(); diff --git a/sha-crypt/src/mcf.rs b/sha-crypt/src/mcf.rs index 7263968f..12d5220b 100644 --- a/sha-crypt/src/mcf.rs +++ b/sha-crypt/src/mcf.rs @@ -17,6 +17,16 @@ use subtle::ConstantTimeEq; pub struct ShaCrypt { /// Default algorithm to use when generating password hashes. algorithm: Algorithm, + + /// Default params to use when generating password hashes. + params: Params, +} + +impl ShaCrypt { + /// Create a new password hasher with customized algorithm and params. + pub fn new(algorithm: Algorithm, params: Params) -> Self { + Self { algorithm, params } + } } impl CustomizedPasswordHasher for ShaCrypt { @@ -44,7 +54,7 @@ impl CustomizedPasswordHasher for ShaCrypt { let mut mcf_hash = PasswordHash::from_id(alg.ident()).expect("should have valid ID"); mcf_hash - .push_displayable(¶ms) + .push_displayable(params) .expect("should be valid field"); mcf_hash @@ -53,11 +63,11 @@ impl CustomizedPasswordHasher for ShaCrypt { match alg { Algorithm::Sha256Crypt => { - let out = sha256_crypt_core(password, salt.as_bytes(), ¶ms); + let out = sha256_crypt_core(password, salt.as_bytes(), params); mcf_hash.push_base64(&out, Base64::Crypt); } Algorithm::Sha512Crypt => { - let out = sha512_crypt_core(password, salt.as_bytes(), ¶ms); + let out = sha512_crypt_core(password, salt.as_bytes(), params); mcf_hash.push_base64(&out, Base64::Crypt); } } @@ -68,7 +78,7 @@ impl CustomizedPasswordHasher for ShaCrypt { impl PasswordHasher for ShaCrypt { 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) } } @@ -108,10 +118,10 @@ impl PasswordVerifier for ShaCrypt { } let is_valid = match alg { - Algorithm::Sha256Crypt => sha256_crypt_core(password, salt, ¶ms) + Algorithm::Sha256Crypt => sha256_crypt_core(password, salt, params) .as_ref() .ct_eq(&expected), - Algorithm::Sha512Crypt => sha512_crypt_core(password, salt, ¶ms) + Algorithm::Sha512Crypt => sha512_crypt_core(password, salt, params) .as_ref() .ct_eq(&expected), }; @@ -134,12 +144,24 @@ impl PasswordVerifier for ShaCrypt { impl From for ShaCrypt { fn from(algorithm: Algorithm) -> Self { - Self { algorithm } + Self { + algorithm, + params: Params::default(), + } + } +} + +impl From for ShaCrypt { + fn from(params: Params) -> Self { + Self { + algorithm: Algorithm::default(), + params, + } } } /// SHA-256-crypt core function: uses an algorithm-specific transposition table. -fn sha256_crypt_core(password: &[u8], salt: &[u8], params: &Params) -> [u8; BLOCK_SIZE_SHA256] { +fn sha256_crypt_core(password: &[u8], salt: &[u8], params: Params) -> [u8; BLOCK_SIZE_SHA256] { let output = super::sha256_crypt(password, salt, params); let transposition_table = [ 20, 10, 0, 11, 1, 21, 2, 22, 12, 23, 13, 3, 14, 4, 24, 5, 25, 15, 26, 16, 6, 17, 7, 27, 8, @@ -155,7 +177,7 @@ fn sha256_crypt_core(password: &[u8], salt: &[u8], params: &Params) -> [u8; BLOC } /// SHA-512-crypt core function: uses an algorithm-specific transposition table. -fn sha512_crypt_core(password: &[u8], salt: &[u8], params: &Params) -> [u8; BLOCK_SIZE_SHA512] { +fn sha512_crypt_core(password: &[u8], salt: &[u8], params: Params) -> [u8; BLOCK_SIZE_SHA512] { let output = super::sha512_crypt(password, salt, params); let transposition_table = [ 42, 21, 0, 1, 43, 22, 23, 2, 44, 45, 24, 3, 4, 46, 25, 26, 5, 47, 48, 27, 6, 7, 49, 28, 29, diff --git a/sha-crypt/src/params.rs b/sha-crypt/src/params.rs index 2d342186..5aab6483 100644 --- a/sha-crypt/src/params.rs +++ b/sha-crypt/src/params.rs @@ -7,10 +7,11 @@ use core::{ str::FromStr, }; +/// Name of the parameter when serialized in an MCF string. const ROUNDS_PARAM: &str = "rounds="; /// Algorithm parameters. -#[derive(Debug, Clone)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Params { /// Number of times to apply the digest function pub(crate) rounds: u32,