Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions pbkdf2/src/mcf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ impl CustomizedPasswordHasher<PasswordHash> for Pbkdf2 {
return Err(Error::Version);
}

let mut buffer = [0u8; Params::MAX_LENGTH];
let mut buffer = [0u8; Params::MAX_OUTPUT_LENGTH];
let out = buffer
.get_mut(..params.output_len())
.ok_or(Error::OutputSize)?;
Expand Down Expand Up @@ -110,7 +110,7 @@ impl PasswordVerifier<PasswordHashRef> for Pbkdf2 {
return Err(Error::EncodingInvalid);
}

let mut buffer = [0u8; Params::MAX_LENGTH];
let mut buffer = [0u8; Params::MAX_OUTPUT_LENGTH];
let out = buffer.get_mut(..expected.len()).ok_or(Error::OutputSize)?;

let f = match algorithm {
Expand Down Expand Up @@ -168,7 +168,7 @@ mod tests {
"$pbkdf2-sha256$8000$XAuBMIYQQogxRg$tRRlz8hYn63B9LYiCd6PRo6FMiunY9ozmMMI3srxeRE";

let salt = base64_decode(EXAMPLE_SALT).unwrap();
let params = Params::new(EXAMPLE_ROUNDS);
let params = Params::new(EXAMPLE_ROUNDS).unwrap();

let actual_hash: PasswordHash = Pbkdf2::SHA256
.hash_password_with_params(EXAMPLE_PASSWORD, salt.as_slice(), params)
Expand Down Expand Up @@ -198,7 +198,7 @@ mod tests {
const EXAMPLE_HASH: &str = "$pbkdf2-sha512$25000$O4fwPmdMyRmDUIrx/h9jTA$Xlp267ZwEbG4aOpN3Bve/ATo3rFA7WH8iMdS16Xbe9rc6P5welk1yiXEMPy7.BFp0qsncipHumaW1trCWVvq/A";

let salt = base64_decode(EXAMPLE_SALT).unwrap();
let params = Params::new_with_output_len(EXAMPLE_ROUNDS, 64);
let params = Params::new_with_output_len(EXAMPLE_ROUNDS, 64).unwrap();

let actual_hash: PasswordHash = Pbkdf2::SHA512
.hash_password_with_params(EXAMPLE_PASSWORD, salt.as_slice(), params)
Expand Down
69 changes: 45 additions & 24 deletions pbkdf2/src/params.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use core::{
fmt::{self, Display},
num::ParseIntError,
str::FromStr,
};

#[cfg(feature = "phc")]
use password_hash::{
Error,
Error, Result,
phc::{self, Decimal, ParamsString},
};

Expand All @@ -21,11 +20,18 @@ pub struct Params {
}

impl Params {
/// Minimum supported output length.
// Uses the same recommendation as the PHC spec.
pub const MIN_OUTPUT_LENGTH: usize = 10;

/// Maximum supported output length.
pub const MAX_LENGTH: usize = 64;
pub const MAX_OUTPUT_LENGTH: usize = 64;

/// Minimum supported number of rounds, adapted from NIST's suggestions.
pub const MIN_ROUNDS: u32 = 1000;

/// Recommended output length.
pub const RECOMMENDED_LENGTH: usize = 32;
pub const RECOMMENDED_OUTPUT_LENGTH: usize = 32;

/// Recommended number of PBKDF2 rounds (used by default).
///
Expand All @@ -41,22 +47,24 @@ impl Params {
/// [OWASP cheat sheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
pub const RECOMMENDED: Self = Params {
rounds: Self::RECOMMENDED_ROUNDS,
output_len: Self::RECOMMENDED_LENGTH,
output_len: Self::RECOMMENDED_OUTPUT_LENGTH,
};

/// Create new params with the given number of rounds.
pub const fn new(rounds: u32) -> Self {
let mut ret = Self::RECOMMENDED;
ret.rounds = rounds;
ret
pub const fn new(rounds: u32) -> Result<Self> {
Self::new_with_output_len(rounds, Self::RECOMMENDED_OUTPUT_LENGTH)
}

/// Create new params with a customized output length.
pub const fn new_with_output_len(rounds: u32, output_length: usize) -> Self {
Self {
rounds,
output_len: output_length,
pub const fn new_with_output_len(rounds: u32, output_len: usize) -> Result<Self> {
if rounds < Self::MIN_ROUNDS
|| output_len < Self::MIN_OUTPUT_LENGTH
|| output_len > Self::MAX_OUTPUT_LENGTH
{
return Err(Error::ParamsInvalid);
}

Ok(Self { rounds, output_len })
}

/// Get the number of rounds.
Expand All @@ -83,10 +91,20 @@ impl Display for Params {
}

impl FromStr for Params {
type Err = ParseIntError;
type Err = Error;

fn from_str(s: &str) -> Result<Self> {
u32::from_str(s)
.map_err(|_| Error::EncodingInvalid)
.and_then(Params::new)
}
}

fn from_str(s: &str) -> Result<Self, ParseIntError> {
u32::from_str(s).map(Params::new)
impl TryFrom<u32> for Params {
type Error = Error;

fn try_from(value: u32) -> Result<Self> {
Self::new(value)
}
}

Expand All @@ -95,33 +113,36 @@ impl TryFrom<&ParamsString> for Params {
type Error = Error;

fn try_from(params_string: &ParamsString) -> password_hash::Result<Self> {
let mut params = Params::default();
let mut rounds = Params::RECOMMENDED_ROUNDS;
let mut output_len = Params::RECOMMENDED_OUTPUT_LENGTH;

for (ident, value) in params_string.iter() {
match ident.as_str() {
"i" => {
params.rounds = value
rounds = value
.decimal()
.map_err(|_| Error::ParamInvalid { name: "i" })?
.map_err(|_| Error::ParamInvalid { name: "i" })?;

if rounds < Self::MIN_ROUNDS {
return Err(Error::ParamInvalid { name: "i" });
}
}
"l" => {
let len = value
output_len = value
.decimal()
.ok()
.and_then(|dec| dec.try_into().ok())
.ok_or(Error::ParamInvalid { name: "l" })?;

if len > Self::MAX_LENGTH {
if output_len > Self::MAX_OUTPUT_LENGTH {
return Err(Error::ParamInvalid { name: "l" });
}

params.output_len = len;
}
_ => return Err(Error::ParamsInvalid),
}
}

Ok(params)
Params::new_with_output_len(rounds, output_len)
}
}

Expand Down
4 changes: 2 additions & 2 deletions pbkdf2/src/phc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl CustomizedPasswordHasher<PasswordHash> for Pbkdf2 {

let salt = Salt::new(salt)?;

let mut buffer = [0u8; Params::MAX_LENGTH];
let mut buffer = [0u8; Params::MAX_OUTPUT_LENGTH];
let out = buffer
.get_mut(..params.output_len())
.ok_or(Error::OutputSize)?;
Expand Down Expand Up @@ -88,7 +88,7 @@ mod tests {
/// dkLen = 40
#[test]
fn hash_with_default_algorithm() {
let params = Params::new_with_output_len(4096, 40);
let params = Params::new_with_output_len(4096, 40).unwrap();

let pwhash: PasswordHash = Pbkdf2::default()
.hash_password_customized(PASSWORD, SALT, None, None, params)
Expand Down