#[cfg(debug_assertions)] use argon2::{Algorithm, Params, Version}; use argon2::{ Argon2, password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng}, }; #[derive(Debug, thiserror::Error)] pub enum HashPasswordError { #[error("failed to initialize password hasher: {0}")] Hasher(String), #[error("failed to hash password: {0}")] Hash(String), } pub fn hash_password(password: &str, pepper: &str) -> Result { let salt = SaltString::generate(&mut OsRng); let password = format!("{password}{pepper}"); password_hasher()? .hash_password(password.as_bytes(), &salt) .map(|hash| hash.to_string()) .map_err(|error| HashPasswordError::Hash(error.to_string())) } pub fn verify_password(password: &str, pepper: &str, password_hash: &str) -> bool { let Ok(parsed) = PasswordHash::new(password_hash) else { return false; }; let password = format!("{password}{pepper}"); match password_hasher() { Ok(argon2) => argon2.verify_password(password.as_bytes(), &parsed).is_ok(), Err(_) => false, } } fn password_hasher() -> Result, HashPasswordError> { #[cfg(debug_assertions)] { let params = Params::new(8 * 1024, 1, 1, None) .map_err(|error| HashPasswordError::Hasher(error.to_string()))?; Ok(Argon2::new(Algorithm::Argon2id, Version::V0x13, params)) } #[cfg(not(debug_assertions))] { Ok(Argon2::default()) } } #[cfg(test)] mod tests { use super::{hash_password, verify_password}; #[test] fn hashes_and_verifies_password() { let password_hash = hash_password("test-password", "pepper").unwrap(); assert!(verify_password("test-password", "pepper", &password_hash)); assert!(!verify_password("wrong-password", "pepper", &password_hash)); } }