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
6 changes: 6 additions & 0 deletions scrypt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ pub use crate::simple::{ALG_ID, Scrypt};
/// `Ok(())` if calculation is successful and `Err(InvalidOutputLen)` if
/// `output` does not satisfy the following condition:
/// `output.len() > 0 && output.len() <= (2^32 - 1) * 32`.
///
/// # Note about output lengths
/// The output size is determined entirely by size of the `output` parameter.
///
/// If the length of the [`Params`] have been customized using the [`Params::new_with_output_len`]
/// constructor, that length is ignored and the length of `output` is used instead.
pub fn scrypt(
password: &[u8],
salt: &[u8],
Expand Down
49 changes: 39 additions & 10 deletions scrypt/src/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ use crate::errors::InvalidParams;
#[cfg(feature = "simple")]
use password_hash::{Error, ParamsString, PasswordHash, errors::InvalidValue};

#[cfg(doc)]
use password_hash::PasswordHasher;

/// The Scrypt parameter values.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Params {
pub(crate) log_n: u8,
pub(crate) r: u32,
pub(crate) p: u32,
#[allow(dead_code)] // this field is used only with the `PasswordHasher` impl
pub(crate) len: usize,
#[cfg(feature = "password-hash")]
pub(crate) len: Option<usize>,
}

impl Params {
Expand All @@ -34,19 +37,16 @@ impl Params {
/// - `log_n` - The log₂ of the Scrypt parameter `N`
/// - `r` - The Scrypt parameter `r`
/// - `p` - The Scrypt parameter `p`
/// - `len` - The Scrypt parameter `Key length`
///
/// # Conditions
/// - `log_n` must be less than `64`
/// - `r` must be greater than `0` and less than or equal to `4294967295`
/// - `p` must be greater than `0` and less than `4294967295`
/// - `len` must be greater than `9` and less than or equal to `64`
pub fn new(log_n: u8, r: u32, p: u32, len: usize) -> Result<Params, InvalidParams> {
pub fn new(log_n: u8, r: u32, p: u32) -> Result<Params, InvalidParams> {
let cond1 = (log_n as usize) < usize::BITS as usize;
let cond2 = size_of::<usize>() >= size_of::<u32>();
let cond3 = r <= usize::MAX as u32 && p < usize::MAX as u32;
let cond4 = (10..=64).contains(&len);
if !(r > 0 && p > 0 && cond1 && (cond2 || cond3) && cond4) {
if !(r > 0 && p > 0 && cond1 && (cond2 || cond3)) {
return Err(InvalidParams);
}

Expand Down Expand Up @@ -83,10 +83,36 @@ impl Params {
log_n,
r: r as u32,
p: p as u32,
len,
#[cfg(feature = "password-hash")]
len: None,
})
}

/// Create a new instance of [`Params`], overriding the output length.
///
/// Note that this length is only intended for use with the [`PasswordHasher`] API, and not with
/// the low-level [`scrypt::scrypt`][`crate::scrypt`] API, which determines the output length
/// using the size of the `output` slice.
///
/// The allowed values for `len` are between 10 bytes (80 bits) and 64 bytes inclusive.
/// These lengths come from the [PHC string format specification](https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md)
/// because they are intended for use with password hash strings.
#[cfg(feature = "password-hash")]
pub fn new_with_output_len(
log_n: u8,
r: u32,
p: u32,
len: usize,
) -> Result<Params, InvalidParams> {
if !(password_hash::Output::MIN_LENGTH..=password_hash::Output::MAX_LENGTH).contains(&len) {
return Err(InvalidParams);
}

let mut ret = Self::new(log_n, r, p)?;
ret.len = Some(len);
Ok(ret)
}

/// Recommended values according to the [OWASP cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#scrypt)
/// - `log_n = 17` (`n = 131072`)
/// - `r = 8`
Expand All @@ -96,7 +122,8 @@ impl Params {
log_n: Self::RECOMMENDED_LOG_N,
r: Self::RECOMMENDED_R,
p: Self::RECOMMENDED_P,
len: Self::RECOMMENDED_LEN,
#[cfg(feature = "password-hash")]
len: None,
}
}

Expand Down Expand Up @@ -167,7 +194,9 @@ impl<'a> TryFrom<&'a PasswordHash<'a>> for Params {
.hash
.map(|out| out.len())
.unwrap_or(Self::RECOMMENDED_LEN);
Params::new(log_n, r, p, len).map_err(|_| InvalidValue::Malformed.param_error())

Params::new_with_output_len(log_n, r, p, len)
.map_err(|_| InvalidValue::Malformed.param_error())
}
}

Expand Down
3 changes: 2 additions & 1 deletion scrypt/src/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ impl PasswordHasher for Scrypt {
let salt = salt.into();
let mut salt_arr = [0u8; 64];
let salt_bytes = salt.decode_b64(&mut salt_arr)?;
let len = params.len.unwrap_or(Params::RECOMMENDED_LEN);

let output = Output::init_with(params.len, |out| {
let output = Output::init_with(len, |out| {
scrypt(password, salt_bytes, &params, out).map_err(|_| {
let provided = if out.is_empty() {
Ordering::Less
Expand Down
2 changes: 1 addition & 1 deletion scrypt/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ fn test_scrypt() {
let tests = tests();
for t in tests.iter() {
let mut result = vec![0u8; t.expected.len()];
let params = Params::new(t.log_n, t.r, t.p, t.expected.len()).unwrap();
let params = Params::new(t.log_n, t.r, t.p).unwrap();
scrypt(
t.password.as_bytes(),
t.salt.as_bytes(),
Expand Down