use argon2::password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
use argon2::{Algorithm, Argon2, Params, Version};
use rand_core::OsRng;
use crate::PasswordError;
pub const DEFAULT_M_COST_KIB: u32 = 19 * 1024;
pub const DEFAULT_T_COST: u32 = 2;
pub const DEFAULT_P_COST: u32 = 1;
pub const ARGON2_FAST: Params = match Params::new(256, 2, 1, None) {
Ok(p) => p,
Err(_) => panic!("argon2 fast params"),
};
pub fn argon2_default() -> Argon2<'static> {
let params = Params::new(DEFAULT_M_COST_KIB, DEFAULT_T_COST, DEFAULT_P_COST, None)
.expect("valid argon2 params");
Argon2::new(Algorithm::Argon2id, Version::V0x13, params)
}
fn argon2_with_params(params: Params) -> Argon2<'static> {
Argon2::new(Algorithm::Argon2id, Version::V0x13, params)
}
pub fn hash_password(plain: &str) -> Result<String, PasswordError> {
hash_password_with(plain, argon2_default())
}
pub fn hash_password_with(plain: &str, argon2: Argon2<'_>) -> Result<String, PasswordError> {
let salt = SaltString::generate(&mut OsRng);
let phc = argon2
.hash_password(plain.as_bytes(), &salt)
.map_err(|e| PasswordError::Hash(e.to_string()))?
.to_string();
Ok(phc)
}
pub fn argon2_fast() -> Argon2<'static> {
argon2_with_params(ARGON2_FAST)
}
pub fn hash_password_fast(plain: &str) -> Result<String, PasswordError> {
hash_password_with(plain, argon2_fast())
}
pub fn verify_password(plain: &str, phc: &str) -> Result<bool, PasswordError> {
verify_password_with(plain, phc, argon2_default())
}
pub fn verify_password_with(
plain: &str,
phc: &str,
argon2: Argon2<'_>,
) -> Result<bool, PasswordError> {
let parsed = PasswordHash::new(phc).map_err(|_| PasswordError::InvalidHash)?;
Ok(argon2.verify_password(plain.as_bytes(), &parsed).is_ok())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_fast() {
let h = hash_password_fast("correct horse battery staple").unwrap();
assert!(h.starts_with("$argon2id$"));
assert!(verify_password_with("correct horse battery staple", &h, argon2_fast()).unwrap());
assert!(!verify_password_with("wrong", &h, argon2_fast()).unwrap());
}
#[test]
fn wrong_password_rejected() {
let h = hash_password_fast("secret").unwrap();
assert!(!verify_password_with("Secret", &h, argon2_fast()).unwrap());
}
}