pub use mcf::PasswordHashRef;
#[cfg(feature = "alloc")]
pub use mcf::PasswordHash;
use crate::{Algorithm, Params, Pbkdf2, pbkdf2_hmac_with_params};
use mcf::Base64;
use password_hash::{Error, PasswordVerifier, Result};
#[cfg(feature = "alloc")]
use password_hash::{CustomizedPasswordHasher, PasswordHasher, Version};
const MAX_SALT_LEN: usize = 64;
#[cfg(feature = "alloc")]
impl CustomizedPasswordHasher<PasswordHash> for Pbkdf2 {
type Params = Params;
fn hash_password_customized(
&self,
password: &[u8],
salt: &[u8],
alg_id: Option<&str>,
version: Option<Version>,
params: Params,
) -> Result<PasswordHash> {
let algorithm = alg_id
.map(Algorithm::try_from)
.transpose()?
.unwrap_or(self.algorithm);
if version.is_some() {
return Err(Error::Version);
}
let mut buffer = [0u8; Params::MAX_OUTPUT_LENGTH];
let out = buffer
.get_mut(..params.output_len())
.ok_or(Error::OutputSize)?;
pbkdf2_hmac_with_params(password, salt, algorithm, params, out);
let mut mcf_hash = PasswordHash::from_id(algorithm.to_str()).expect("should have valid ID");
mcf_hash
.push_displayable(params)
.map_err(|_| Error::EncodingInvalid)?;
mcf_hash.push_base64(salt, Base64::Pbkdf2);
mcf_hash.push_base64(out, Base64::Pbkdf2);
Ok(mcf_hash)
}
}
#[cfg(feature = "alloc")]
impl PasswordHasher<PasswordHash> for Pbkdf2 {
fn hash_password_with_salt(&self, password: &[u8], salt: &[u8]) -> Result<PasswordHash> {
self.hash_password_customized(password, salt, None, None, self.params)
}
}
#[cfg(feature = "alloc")]
impl PasswordVerifier<PasswordHash> for Pbkdf2 {
fn verify_password(&self, password: &[u8], hash: &PasswordHash) -> Result<()> {
self.verify_password(password, hash.as_password_hash_ref())
}
}
impl PasswordVerifier<PasswordHashRef> for Pbkdf2 {
fn verify_password(&self, password: &[u8], hash: &PasswordHashRef) -> Result<()> {
let algorithm = hash.id().parse::<Algorithm>()?;
let mut fields = hash.fields();
let mut next = fields.next().ok_or(Error::EncodingInvalid)?;
let mut params = Params::recommended_for(algorithm);
if let Ok(p) = next.as_str().parse::<Params>() {
params = p;
next = fields.next().ok_or(Error::EncodingInvalid)?;
}
let mut salt_buf = [0u8; MAX_SALT_LEN];
let salt = next
.decode_base64_into(Base64::Pbkdf2, &mut salt_buf)
.map_err(|_| Error::EncodingInvalid)?;
let mut expected_buf = [0u8; Params::MAX_OUTPUT_LENGTH];
let expected = fields
.next()
.ok_or(Error::EncodingInvalid)?
.decode_base64_into(Base64::Pbkdf2, &mut expected_buf)
.map_err(|_| Error::EncodingInvalid)?;
if fields.next().is_some() {
return Err(Error::EncodingInvalid);
}
let mut out_buf = [0u8; Params::MAX_OUTPUT_LENGTH];
let out = out_buf.get_mut(..expected.len()).ok_or(Error::OutputSize)?;
pbkdf2_hmac_with_params(password, salt, algorithm, params, out);
if out
.iter()
.zip(expected.iter())
.fold(0, |acc, (a, b)| acc | (a ^ b))
== 0
{
Ok(())
} else {
Err(Error::PasswordInvalid)
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use crate::Pbkdf2;
use mcf::PasswordHashRef;
use password_hash::{Error, PasswordVerifier};
#[cfg(feature = "alloc")]
use {
crate::Params,
mcf::{Base64, PasswordHash},
password_hash::CustomizedPasswordHasher,
};
#[test]
#[cfg(feature = "alloc")]
fn hash_password_sha256() {
const EXAMPLE_PASSWORD: &[u8] = b"password";
const EXAMPLE_ROUNDS: u32 = 8000;
const EXAMPLE_SALT: &str = "XAuBMIYQQogxRg";
const EXAMPLE_HASH: &str =
"$pbkdf2-sha256$8000$XAuBMIYQQogxRg$tRRlz8hYn63B9LYiCd6PRo6FMiunY9ozmMMI3srxeRE";
let salt = Base64::Pbkdf2.decode_vec(EXAMPLE_SALT).unwrap();
let params = Params::new(EXAMPLE_ROUNDS).unwrap();
let actual_hash: PasswordHash = Pbkdf2::SHA256
.hash_password_with_params(EXAMPLE_PASSWORD, salt.as_slice(), params)
.unwrap();
let expected_hash = PasswordHash::new(EXAMPLE_HASH).unwrap();
assert_eq!(expected_hash, actual_hash);
assert_eq!(
Pbkdf2::SHA256.verify_password(EXAMPLE_PASSWORD, &actual_hash),
Ok(())
);
assert_eq!(
Pbkdf2::SHA256.verify_password(b"bogus", &actual_hash),
Err(Error::PasswordInvalid)
);
}
#[test]
#[cfg(feature = "alloc")]
fn hash_password_sha512() {
const EXAMPLE_PASSWORD: &[u8] = b"abcdefghijklmnop";
const EXAMPLE_ROUNDS: u32 = 25000;
const EXAMPLE_SALT: &str = "O4fwPmdMyRmDUIrx/h9jTA";
const EXAMPLE_HASH: &str = "$pbkdf2-sha512$25000$O4fwPmdMyRmDUIrx/h9jTA$Xlp267ZwEbG4aOpN3Bve/ATo3rFA7WH8iMdS16Xbe9rc6P5welk1yiXEMPy7.BFp0qsncipHumaW1trCWVvq/A";
let salt = Base64::Pbkdf2.decode_vec(EXAMPLE_SALT).unwrap();
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)
.unwrap();
let expected_hash = PasswordHash::new(EXAMPLE_HASH).unwrap();
assert_eq!(expected_hash, actual_hash);
assert_eq!(
Pbkdf2::SHA512.verify_password(EXAMPLE_PASSWORD, &actual_hash),
Ok(())
);
assert_eq!(
Pbkdf2::SHA512.verify_password(b"bogus", &actual_hash),
Err(Error::PasswordInvalid)
);
}
#[test]
fn verify_password_sha256() {
const EXAMPLE_PASSWORD: &[u8] = b"password";
const EXAMPLE_HASH: &str =
"$pbkdf2-sha256$8000$XAuBMIYQQogxRg$tRRlz8hYn63B9LYiCd6PRo6FMiunY9ozmMMI3srxeRE";
let pwhash = PasswordHashRef::new(EXAMPLE_HASH).unwrap();
assert_eq!(
Pbkdf2::SHA256.verify_password(EXAMPLE_PASSWORD, pwhash),
Ok(())
);
assert_eq!(
Pbkdf2::SHA256.verify_password(b"bogus", pwhash),
Err(Error::PasswordInvalid)
);
}
}