use crate::key::KeyBuilder;
use crate::pass::error::Error;
use crate::pass::{HashingFunction, Normalization, std_default};
use std::collections::HashMap;
const MIN_SALT_LENGTH: usize = 8; const MAX_SALT_LENGTH: usize = 256; const DEFAULT_PASSES: u32 = 3;
const MIN_PASSES: u32 = 1;
const MAX_PASSES: u32 = 1024;
const DEFAULT_MEM_COST: u32 = 12; const MIN_MEM_COST: u32 = 7; const MAX_MEM_COST: u32 = 18; const DEFAULT_LANES: u32 = 4;
const MIN_LANES: u32 = 1;
const MAX_LANES: u32 = 128;
const DEFAULT_OUTPUT_LEN: u32 = 128; const MIN_OUTPUT_LEN: u32 = 32; const MAX_OUTPUT_LEN: u32 = 256;
macro_rules! set_param {
($obj: ident, $attr: ident, $val: ident, $t: ty, $min: expr, $max: expr) => {{
match $val.parse::<$t>() {
Ok(i) => match i {
$min..=$max => {
$obj.$attr = i;
Ok(())
}
_ => Err(Error::InvalidPasswordFormat),
},
Err(_) => Err(Error::InvalidPasswordFormat),
}
}};
}
pub struct Argon2Hash {
passes: u32,
mem_cost: u32,
lanes: u32,
output_len: u32,
salt: Vec<u8>,
norm: Normalization,
}
impl Argon2Hash {
pub fn new() -> Self {
Self {
passes: DEFAULT_PASSES,
mem_cost: DEFAULT_MEM_COST,
lanes: DEFAULT_LANES,
output_len: DEFAULT_OUTPUT_LEN,
salt: KeyBuilder::new()
.size(std_default::DEFAULT_SALT_LEN)
.as_vec(),
norm: Normalization::Nfkc,
}
}
}
impl HashingFunction for Argon2Hash {
fn get_id(&self) -> String {
"argon2".to_string()
}
fn get_parameters(&self) -> HashMap<String, String> {
let mut params = HashMap::new();
set_normalization!(self, norm, params, "norm".to_string());
params.insert("passes".to_string(), self.passes.to_string());
params.insert("mem".to_string(), self.mem_cost.to_string());
params.insert("lanes".to_string(), self.lanes.to_string());
params.insert("len".to_string(), self.output_len.to_string());
params
}
fn set_parameter(&mut self, name: &str, value: &str) -> Result<(), Error> {
match name {
"passes" => set_param!(self, passes, value, u32, MIN_PASSES, MAX_PASSES),
"mem" => set_param!(self, mem_cost, value, u32, MIN_MEM_COST, MAX_MEM_COST),
"lanes" => set_param!(self, lanes, value, u32, MIN_LANES, MAX_LANES),
"len" => set_param!(self, output_len, value, u32, MIN_OUTPUT_LEN, MAX_OUTPUT_LEN),
_ => Err(Error::InvalidPasswordFormat),
}
}
fn get_salt(&self) -> Option<Vec<u8>> {
Some(self.salt.clone())
}
fn set_salt(&mut self, salt: Vec<u8>) -> Result<(), Error> {
match salt.len() {
MIN_SALT_LENGTH..=MAX_SALT_LENGTH => {
self.salt = salt;
Ok(())
}
_ => Err(Error::InvalidPasswordFormat),
}
}
fn set_salt_len(&mut self, salt_len: usize) -> Result<(), Error> {
let salt = KeyBuilder::new().size(salt_len).as_vec();
self.set_salt(salt)
}
fn set_normalization(&mut self, norm: Normalization) -> Result<(), Error> {
self.norm = norm;
Ok(())
}
fn hash(&self, input: &[u8]) -> Vec<u8> {
let two: u32 = 2;
let config = argon2::Config {
ad: &[],
hash_length: self.output_len,
lanes: self.lanes,
thread_mode: argon2::ThreadMode::Parallel,
mem_cost: two.pow(self.mem_cost),
secret: &[],
time_cost: self.passes,
variant: argon2::Variant::Argon2i,
version: argon2::Version::Version13,
};
argon2::hash_raw(input, self.salt.as_slice(), &config).unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_id() {
assert_eq!(Argon2Hash::new().get_id(), "argon2".to_string());
}
#[test]
fn test_get_salt() {
let h = Argon2Hash {
passes: DEFAULT_PASSES,
mem_cost: DEFAULT_MEM_COST,
lanes: DEFAULT_LANES,
output_len: DEFAULT_OUTPUT_LEN,
salt: vec![0, 1, 2, 3, 4, 5],
norm: Normalization::Nfkc,
};
assert_eq!(h.get_salt().unwrap(), vec![0, 1, 2, 3, 4, 5]);
}
#[test]
fn test_salt_randomness() {
assert_ne!(
Argon2Hash::new().get_salt().unwrap(),
Argon2Hash::new().get_salt().unwrap()
);
}
#[test]
fn test_argon2_v13() {
let h = Argon2Hash {
passes: 2,
mem_cost: 16,
lanes: 4,
output_len: 24,
salt: "somesalt".to_string().into_bytes(),
norm: Normalization::Nfkc,
}
.hash(&"password".to_string().into_bytes());
assert_eq!(
h,
vec![
69, 215, 172, 114, 231, 111, 36, 43, 32, 183, 123, 155, 249, 191, 157, 89, 21, 137,
78, 102, 154, 36, 230, 198,
],
);
}
}