//! Password hashing using Argon2id //! //! SEC-9: Explicitly uses Argon2id (hybrid variant) for password hashing //! Argon2id provides resistance against both side-channel and GPU attacks use anyhow::{anyhow, Result}; use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2, Algorithm, Version, Params, }; /// Hash a password using Argon2id /// /// SEC-9: Explicitly configured to use Argon2id variant /// - Algorithm: Argon2id (hybrid of Argon2i and Argon2d) /// - Version: 0x13 (latest version) /// - Memory: 19456 KiB (default) /// - Iterations: 2 (default) /// - Parallelism: 1 (default) pub fn hash_password(password: &str) -> Result { let salt = SaltString::generate(&mut OsRng); // Explicitly use Argon2id (Algorithm::Argon2id) let argon2 = Argon2::new( Algorithm::Argon2id, // SEC-9: Explicit Argon2id variant Version::V0x13, // Latest version Params::default(), // Default params (19456 KiB, 2 iterations, 1 parallelism) ); let hash = argon2 .hash_password(password.as_bytes(), &salt) .map_err(|e| anyhow!("Failed to hash password: {}", e))?; Ok(hash.to_string()) } /// Verify a password against a stored hash pub fn verify_password(password: &str, hash: &str) -> Result { let parsed_hash = PasswordHash::new(hash) .map_err(|e| anyhow!("Invalid password hash format: {}", e))?; // Argon2::default() uses Argon2id, but we verify against the hash's embedded algorithm let argon2 = Argon2::default(); Ok(argon2.verify_password(password.as_bytes(), &parsed_hash).is_ok()) } /// Generate a random password (for initial admin) pub fn generate_random_password(length: usize) -> String { use rand::Rng; const CHARSET: &[u8] = b"ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789!@#$%"; let mut rng = rand::thread_rng(); (0..length) .map(|_| { let idx = rng.gen_range(0..CHARSET.len()); CHARSET[idx] as char }) .collect() } #[cfg(test)] mod tests { use super::*; #[test] fn test_hash_and_verify() { let password = "test_password_123"; let hash = hash_password(password).unwrap(); assert!(verify_password(password, &hash).unwrap()); assert!(!verify_password("wrong_password", &hash).unwrap()); } #[test] fn test_random_password() { let password = generate_random_password(16); assert_eq!(password.len(), 16); } }