#![cfg(all(feature = "password-hash", hmac, kdf_pbkdf2))]
use password_hash::phc::{Ident, Output, ParamsString, PasswordHash, Salt};
use password_hash::{CustomizedPasswordHasher, Error, Result, Version};
use crate::hmac::HMAC;
use crate::kdf;
const PBKDF2_SHA256_IDENT: Ident = Ident::new_unwrap("pbkdf2-sha256");
const PBKDF2_SHA384_IDENT: Ident = Ident::new_unwrap("pbkdf2-sha384");
const PBKDF2_SHA512_IDENT: Ident = Ident::new_unwrap("pbkdf2-sha512");
pub const MIN_ROUNDS: u32 = 1_000;
pub const DEFAULT_ROUNDS: u32 = 600_000;
pub const DEFAULT_OUTPUT_LEN: usize = 32;
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub enum Algorithm {
#[default]
Pbkdf2Sha256,
Pbkdf2Sha384,
Pbkdf2Sha512,
}
impl Algorithm {
pub fn ident(self) -> Ident {
match self {
Algorithm::Pbkdf2Sha256 => PBKDF2_SHA256_IDENT,
Algorithm::Pbkdf2Sha384 => PBKDF2_SHA384_IDENT,
Algorithm::Pbkdf2Sha512 => PBKDF2_SHA512_IDENT,
}
}
fn hmac_type(self) -> i32 {
match self {
Algorithm::Pbkdf2Sha256 => HMAC::TYPE_SHA256,
Algorithm::Pbkdf2Sha384 => HMAC::TYPE_SHA384,
Algorithm::Pbkdf2Sha512 => HMAC::TYPE_SHA512,
}
}
}
impl TryFrom<Ident> for Algorithm {
type Error = Error;
fn try_from(ident: Ident) -> Result<Self> {
if ident == PBKDF2_SHA256_IDENT {
Ok(Algorithm::Pbkdf2Sha256)
} else if ident == PBKDF2_SHA384_IDENT {
Ok(Algorithm::Pbkdf2Sha384)
} else if ident == PBKDF2_SHA512_IDENT {
Ok(Algorithm::Pbkdf2Sha512)
} else {
Err(Error::Algorithm)
}
}
}
#[derive(Clone, Debug)]
pub struct Params {
pub rounds: u32,
pub output_len: usize,
}
impl Default for Params {
fn default() -> Self {
Params {
rounds: DEFAULT_ROUNDS,
output_len: DEFAULT_OUTPUT_LEN,
}
}
}
impl TryFrom<&PasswordHash> for Params {
type Error = Error;
fn try_from(hash: &PasswordHash) -> Result<Self> {
let rounds = hash
.params
.get_decimal("i")
.ok_or(Error::ParamInvalid { name: "i" })?;
if rounds < MIN_ROUNDS {
return Err(Error::ParamInvalid { name: "i" });
}
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" });
};
Ok(Params { rounds, output_len })
}
}
#[derive(Clone, Debug, Default)]
pub struct Pbkdf2 {
pub algorithm: Algorithm,
pub params: Params,
}
impl password_hash::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.clone())
}
}
impl password_hash::CustomizedPasswordHasher<PasswordHash> for Pbkdf2 {
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);
}
let algorithm = match algorithm {
Some(s) => {
let ident = Ident::new(s).map_err(|_| Error::Algorithm)?;
Algorithm::try_from(ident)?
}
None => self.algorithm,
};
if params.rounds < MIN_ROUNDS {
return Err(Error::ParamInvalid { name: "i" });
}
if params.output_len > Output::MAX_LENGTH {
return Err(Error::ParamInvalid { name: "l" });
}
let iterations = i32::try_from(params.rounds)
.map_err(|_| Error::ParamInvalid { name: "i" })?;
let salt = Salt::new(salt)?;
let mut out_buf = [0u8; Output::MAX_LENGTH];
let out_slice = &mut out_buf[..params.output_len];
kdf::pbkdf2(password, salt.as_ref(), iterations, algorithm.hmac_type(), out_slice)
.map_err(|_| Error::Crypto)?;
let output = Output::new(out_slice)?;
let mut phc_params = ParamsString::new();
phc_params.add_decimal("i", params.rounds)?;
Ok(PasswordHash {
algorithm: algorithm.ident(),
version: None,
params: phc_params,
salt: Some(salt),
hash: Some(output),
})
}
}