pub use password_hash::phc::{Ident, Output, PasswordHash, Salt};
use crate::{Params, Scrypt, scrypt};
use password_hash::{CustomizedPasswordHasher, Error, PasswordHasher, Result, Version};
const ALG_NAME: &str = "scrypt";
pub const ALG_ID: Ident = Ident::new_unwrap(ALG_NAME);
impl CustomizedPasswordHasher<PasswordHash> for Scrypt {
type Params = Params;
fn hash_password_customized(
&self,
password: &[u8],
salt: &[u8],
alg_id: Option<&str>,
version: Option<Version>,
params: Params,
) -> Result<PasswordHash> {
match alg_id {
Some(ALG_NAME) | None => (),
Some(_) => return Err(Error::Algorithm),
}
if version.is_some() {
return Err(Error::Version);
}
let salt = Salt::new(salt)?;
let len = params.len.unwrap_or(Params::RECOMMENDED_LEN);
let mut buffer = [0u8; Output::MAX_LENGTH];
let out = buffer.get_mut(..len).ok_or(Error::OutputSize)?;
scrypt(password, &salt, ¶ms, out).map_err(|_| Error::OutputSize)?;
let output = Output::new(out)?;
Ok(PasswordHash {
algorithm: ALG_ID,
version: None,
params: params.try_into()?,
salt: Some(salt),
hash: Some(output),
})
}
}
impl 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)
}
}
#[cfg(test)]
mod tests {
use super::{PasswordHash, Scrypt};
use password_hash::PasswordVerifier;
#[cfg(feature = "password-hash")]
const EXAMPLE_PASSWORD_HASH: &str =
"$scrypt$ln=16,r=8,p=1$aM15713r3Xsvxbi31lqr1Q$nFNh2CVHVjNldFVKDHDlm4CbdRSCdEBsjjJxD+iCs5E";
#[cfg(feature = "password-hash")]
#[test]
fn password_hash_verify_password() {
let password = "password";
let hash = PasswordHash::new(EXAMPLE_PASSWORD_HASH).unwrap();
assert_eq!(
Scrypt::new().verify_password(password.as_bytes(), &hash),
Ok(())
);
}
#[cfg(feature = "password-hash")]
#[test]
fn password_hash_reject_incorrect_password() {
let hash = PasswordHash::new(EXAMPLE_PASSWORD_HASH).unwrap();
assert!(Scrypt::new().verify_password(b"invalid", &hash).is_err());
}
}