#![cfg(all(feature = "password-hash", kdf_scrypt))]
use password_hash::phc::{Ident, Output, ParamsString, PasswordHash, Salt};
use password_hash::{CustomizedPasswordHasher, Error, Result, Version};
use crate::kdf;
const SCRYPT_IDENT: Ident = Ident::new_unwrap("scrypt");
pub const RECOMMENDED_LOG_N: u8 = 17;
pub const RECOMMENDED_R: u32 = 8;
pub const RECOMMENDED_P: u32 = 1;
pub const DEFAULT_OUTPUT_LEN: usize = 32;
#[derive(Clone, Debug)]
pub struct Params {
pub log_n: u8,
pub r: u32,
pub p: u32,
pub output_len: usize,
}
impl Default for Params {
fn default() -> Self {
Params {
log_n: RECOMMENDED_LOG_N,
r: RECOMMENDED_R,
p: RECOMMENDED_P,
output_len: DEFAULT_OUTPUT_LEN,
}
}
}
impl Params {
fn validate(&self) -> Result<()> {
if self.r == 0 || self.r > 8 {
return Err(Error::ParamInvalid { name: "r" });
}
if self.p == 0 {
return Err(Error::ParamInvalid { name: "p" });
}
let log_n_cutoff = (128u32 * self.r) / 8;
if self.log_n == 0 || u32::from(self.log_n) >= log_n_cutoff {
return Err(Error::ParamInvalid { name: "ln" });
}
if self.output_len == 0 || self.output_len > Output::MAX_LENGTH {
return Err(Error::ParamInvalid { name: "l" });
}
let max_n = u32::MAX / (128 * self.r);
let n = 1u32
.checked_shl(self.log_n as u32)
.ok_or(Error::ParamInvalid { name: "ln" })?;
if n > max_n {
return Err(Error::ParamInvalid { name: "ln" });
}
let max_p1 = (u32::MAX / 4) / self.r;
let max_p2 = u32::MAX / (128 * self.r);
let max_p = max_p1.min(max_p2);
if self.p > max_p {
return Err(Error::ParamInvalid { name: "p" });
}
Ok(())
}
}
impl TryFrom<&PasswordHash> for Params {
type Error = Error;
fn try_from(hash: &PasswordHash) -> Result<Self> {
let log_n = hash
.params
.get_decimal("ln")
.ok_or(Error::ParamInvalid { name: "ln" })?;
let log_n = u8::try_from(log_n)
.map_err(|_| Error::ParamInvalid { name: "ln" })?;
let r = hash
.params
.get_decimal("r")
.ok_or(Error::ParamInvalid { name: "r" })?;
let p = hash
.params
.get_decimal("p")
.ok_or(Error::ParamInvalid { name: "p" })?;
let output_len = if let Some(ref h) = hash.hash {
h.len()
} else if let Some(l) = hash.params.get_decimal("l") &&
0 < l && (l as usize) <= Output::MAX_LENGTH {
l as usize
} else {
return Err(Error::ParamInvalid { name: "l" });
};
let params = Params { log_n, r, p, output_len };
params.validate()?;
Ok(params)
}
}
#[derive(Clone, Debug, Default)]
pub struct Scrypt {
pub params: Params,
}
impl password_hash::PasswordHasher<PasswordHash> for Scrypt {
fn hash_password_with_salt(&self, password: &[u8], salt: &[u8]) -> Result<PasswordHash> {
self.hash_password_customized(password, salt, None, None, self.params.clone())
}
}
impl CustomizedPasswordHasher<PasswordHash> for Scrypt {
type Params = Params;
fn hash_password_customized(
&self,
password: &[u8],
salt: &[u8],
algorithm: Option<&str>,
version: Option<Version>,
params: Params,
) -> Result<PasswordHash> {
if version.is_some() {
return Err(Error::Version);
}
if let Some(s) = algorithm {
let ident = Ident::new(s).map_err(|_| Error::Algorithm)?;
if ident != SCRYPT_IDENT {
return Err(Error::Algorithm);
}
}
params.validate()?;
let block_size = i32::try_from(params.r)
.map_err(|_| Error::ParamInvalid { name: "r" })?;
let parallel = i32::try_from(params.p)
.map_err(|_| Error::ParamInvalid { name: "p" })?;
let salt = Salt::new(salt)?;
let mut out_buf = [0u8; Output::MAX_LENGTH];
let out_slice = &mut out_buf[..params.output_len];
kdf::scrypt(password, salt.as_ref(), i32::from(params.log_n),
block_size, parallel, out_slice)
.map_err(|_| Error::Crypto)?;
let output = Output::new(out_slice)?;
let mut phc_params = ParamsString::new();
phc_params.add_decimal("ln", u32::from(params.log_n))?;
phc_params.add_decimal("r", params.r)?;
phc_params.add_decimal("p", params.p)?;
Ok(PasswordHash {
algorithm: SCRYPT_IDENT,
version: None,
params: phc_params,
salt: Some(salt),
hash: Some(output),
})
}
}