mod argon2;
#[cfg(feature = "balloon")]
mod balloon;
pub(crate) mod pbkdf2;
use crate::hash::HashFunction;
use crate::key::KeyBuilder;
use crate::pass::error::Error;
use crate::pass::length::password_length;
use crate::pass::phc::PHCData;
use crate::pass::{
Algorithm, DEFAULT_USER_VERSION, HashedDuo, HashingFunction, INTERNAL_VERSION,
LengthCalculationMethod, Normalization, XHMAC, std_default,
};
use hmac::{Hmac, Mac};
use sha1::Sha1;
use sha2::{Sha224, Sha256, Sha384, Sha512, Sha512_224, Sha512_256};
use sha3::{Keccak224, Keccak256, Keccak384, Keccak512, Sha3_224, Sha3_256, Sha3_384, Sha3_512};
use std::collections::HashMap;
use unicode_normalization::UnicodeNormalization;
macro_rules! get_hmac {
($hash_func: ty, $salt: ident, $pass: ident) => {{
let mut hasher = Hmac::<$hash_func>::new_from_slice(&$salt)?;
hasher.update($pass);
Ok(hasher.finalize().into_bytes().to_vec())
}};
}
pub struct Hasher {
pub(crate) normalization: Normalization,
pub(crate) min_len: usize,
pub(crate) max_len: usize,
pub(crate) algorithm: Algorithm,
pub(crate) parameters: HashMap<String, String>,
pub(crate) ref_salt: Option<Vec<u8>>,
pub(crate) ref_hash: Option<Vec<u8>>,
pub(crate) salt_len: usize,
pub(crate) length_calculation: LengthCalculationMethod,
pub(crate) version: usize,
pub(crate) xhmac: XHMAC,
pub(crate) xhmax_alg: HashFunction,
}
impl Hasher {
fn check_password(&self, password: &str) -> Result<(), Error> {
let pass_len = password_length(password, self.length_calculation);
if pass_len < self.min_len {
return Err(Error::PasswordTooShort {
min: self.min_len,
actual: pass_len,
});
}
if pass_len > self.max_len {
return Err(Error::PasswordTooLong {
max: self.max_len,
actual: pass_len,
});
}
Ok(())
}
fn normalize_password(&self, password: &str) -> String {
match self.normalization {
Normalization::Nfd => password.nfd().collect::<String>(),
Normalization::Nfkd => password.nfkd().collect::<String>(),
Normalization::Nfc => password.nfc().collect::<String>(),
Normalization::Nfkc => password.nfkc().collect::<String>(),
Normalization::None => password.to_string(),
}
}
fn get_hash_func(&self) -> Result<Box<dyn HashingFunction>, Error> {
let mut hash_func: Box<dyn HashingFunction> = match self.algorithm {
Algorithm::Argon2 => Box::new(argon2::Argon2Hash::new()),
#[cfg(feature = "balloon")]
Algorithm::Balloon => Box::new(balloon::BalloonHash::new()),
Algorithm::Pbkdf2 => Box::new(pbkdf2::Pbkdf2Hash::new()),
};
hash_func.set_normalization(self.normalization)?;
for (k, v) in &self.parameters {
hash_func.set_parameter(k, v)?;
}
match self.ref_salt {
Some(ref s) => {
hash_func.set_salt(s.to_vec())?;
}
None => {
hash_func.set_salt_len(self.salt_len)?;
}
};
Ok(hash_func)
}
fn apply_xhmac(&self, password: &[u8], salt: &[u8]) -> Result<Vec<u8>, Error> {
match self.xhmax_alg {
HashFunction::Sha1 => get_hmac!(Sha1, salt, password),
HashFunction::Sha224 => get_hmac!(Sha224, salt, password),
HashFunction::Sha256 => get_hmac!(Sha256, salt, password),
HashFunction::Sha384 => get_hmac!(Sha384, salt, password),
HashFunction::Sha512 => get_hmac!(Sha512, salt, password),
HashFunction::Sha512Trunc224 => get_hmac!(Sha512_224, salt, password),
HashFunction::Sha512Trunc256 => get_hmac!(Sha512_256, salt, password),
HashFunction::Sha3_224 => get_hmac!(Sha3_224, salt, password),
HashFunction::Sha3_256 => get_hmac!(Sha3_256, salt, password),
HashFunction::Sha3_384 => get_hmac!(Sha3_384, salt, password),
HashFunction::Sha3_512 => get_hmac!(Sha3_512, salt, password),
HashFunction::Keccak224 => get_hmac!(Keccak224, salt, password),
HashFunction::Keccak256 => get_hmac!(Keccak256, salt, password),
HashFunction::Keccak384 => get_hmac!(Keccak384, salt, password),
HashFunction::Keccak512 => get_hmac!(Keccak512, salt, password),
}
}
fn do_hash(&self, password: &str) -> Result<HashedDuo, Error> {
let norm_pass = self.normalize_password(password);
self.check_password(&norm_pass)?;
let norm_pass = match &self.xhmac {
XHMAC::Before(salt) => self.apply_xhmac(password.as_bytes(), salt)?,
_ => norm_pass.into_bytes(),
};
let hash_func = self.get_hash_func()?;
let hash = hash_func.hash(&norm_pass);
let hash = match &self.xhmac {
XHMAC::After(salt) => self.apply_xhmac(&hash, salt)?,
_ => hash,
};
let lc = match self.length_calculation {
LengthCalculationMethod::Bytes => "bytes",
LengthCalculationMethod::Characters => "chars",
LengthCalculationMethod::CodePoints => "codepoints",
LengthCalculationMethod::Graphemes => "graphemes",
};
let mut params = hash_func.get_parameters();
params.insert("len-calc".to_string(), lc.to_string());
params.insert("pmin".to_string(), format!("{}", self.min_len));
params.insert("pmax".to_string(), format!("{}", self.max_len));
params.insert("ver".to_string(), format!("{}", self.version));
params.insert("xhmac".to_string(), self.xhmac.to_string());
if self.xhmac.is_some() {
params.insert(
"xhmac-alg".to_string(),
self.xhmax_alg.to_string().to_lowercase(),
);
}
let phc = PHCData {
id: hash_func.get_id(),
parameters: params,
salt: hash_func.get_salt(),
hash: Some(hash.clone()),
};
match phc.to_string() {
Ok(fmtd) => Ok(HashedDuo {
raw: hash,
formated: fmtd,
}),
Err(_) => Err(Error::InvalidPasswordFormat),
}
}
pub fn hash(&self, password: &str) -> Result<String, Error> {
Ok(self.do_hash(password)?.formated)
}
pub fn is_valid(&self, password: &str) -> bool {
match self.ref_hash {
Some(ref rh) => match self.do_hash(password) {
Ok(hash_duo) => {
let salt = KeyBuilder::new()
.size(std_default::DEFAULT_SALT_LEN)
.as_vec();
let mut ref_hmac = match Hmac::<Sha512>::new_from_slice(&salt) {
Ok(h) => h,
Err(_) => {
return false;
}
};
ref_hmac.update(rh.as_slice());
let mut pass_hmac = match Hmac::<Sha512>::new_from_slice(&salt) {
Ok(h) => h,
Err(_) => {
return false;
}
};
pass_hmac.update(hash_duo.raw.as_slice());
ref_hmac.finalize().into_bytes() == pass_hmac.finalize().into_bytes()
}
Err(_) => false,
},
None => false,
}
}
pub fn needs_update(&self, current_version: Option<usize>) -> bool {
match current_version {
Some(nb) => self.version < nb + INTERNAL_VERSION,
None => self.version < DEFAULT_USER_VERSION + INTERNAL_VERSION,
}
}
}