use anyhow::{Context, Result};
use argon2::password_hash::rand_core::OsRng;
use argon2::password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
use argon2::Argon2;
/// Hash a password with Argon2id using a fresh random salt. The salt is stored
/// inline in the returned PHC string, so `verify_password` needs nothing else.
pub fn hash_password(password: &str) -> Result<String> {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
let hash = argon2
.hash_password(password.as_bytes(), &salt)
.map_err(|e| anyhow::anyhow!("failed to hash password: {e}"))?;
Ok(hash.to_string())
}
/// Verify a password against a stored PHC hash. Returns `Ok(false)` for a
/// wrong password and `Err` only for a malformed hash / internal error.
pub fn verify_password(password: &str, hash: &str) -> Result<bool> {
let parsed = PasswordHash::new(hash).context("failed to parse password hash")?;
match Argon2::default().verify_password(password.as_bytes(), &parsed) {
Ok(()) => Ok(true),
Err(argon2::password_hash::Error::Password) => Ok(false),
Err(e) => Err(anyhow::anyhow!("password verification error: {e}")),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hashes_and_verifies() {
let hash = hash_password("correct horse battery staple").unwrap();
assert!(verify_password("correct horse battery staple", &hash).unwrap());
assert!(!verify_password("wrong", &hash).unwrap());
}
#[test]
fn salts_are_random() {
// Same input, different salt each call -> different hashes.
assert_ne!(hash_password("same").unwrap(), hash_password("same").unwrap());
}
}
neiam
/fieri-forever
the flavortown lunch engine
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
1.7 KiB · text
History
25c4237