use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use anyhow::{Context, Result};
/// Hash a password using Argon2
pub fn hash_password(password: &str) -> Result<String> {
// Fresh random salt per hash (argon2's getrandom feature generates it).
// The salt is embedded in the returned PHC string, so `verify_password`
// keeps working against older hashes.
let argon2 = Argon2::default();
let password_hash = argon2
.hash_password(password.as_bytes())
.context("Failed to hash password")?;
Ok(password_hash.to_string())
}
/// Verify a password against its hash
pub fn verify_password(password: &str, hash: &str) -> Result<bool> {
let parsed_hash = PasswordHash::new(hash).context("Failed to parse password hash")?;
let argon2 = Argon2::default();
match argon2.verify_password(password.as_bytes(), &parsed_hash) {
Ok(()) => Ok(true),
Err(argon2::password_hash::Error::PasswordInvalid) => Ok(false),
Err(e) => Err(anyhow::anyhow!("Password verification error: {}", e)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_password_hashing() {
let password = "test_password_123";
let hash = hash_password(password).expect("Failed to hash password");
// Verify correct password
assert!(verify_password(password, &hash).unwrap());
// Verify incorrect password
assert!(!verify_password("wrong_password", &hash).unwrap());
}
#[test]
fn test_hashes_are_salted_per_call() {
let password = "test_password_123";
let a = hash_password(password).expect("Failed to hash password");
let b = hash_password(password).expect("Failed to hash password");
// Two hashes of the same password must not collide, or the hashes
// leak which accounts share a password.
assert_ne!(a, b);
assert!(verify_password(password, &a).unwrap());
assert!(verify_password(password, &b).unwrap());
}
}
⭐
Log in to mark this repository.
2.0 KiB · text
History
6228e32