use crate::error::Error; use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2, }; pub(crate) fn hash(value: &str) -> Result { let salt = SaltString::generate(&mut OsRng); // Argon2 with default params (Argon2id v19) let argon2 = Argon2::default(); // Hash value to PHC string ($argon2id$v=19$...) argon2 .hash_password(value.as_bytes(), &salt) .map(|hashed| hashed.to_string()) .map_err(|_| Error::Hash) } fn verify(value: &str, hashed_value: &str) -> Result { // Verify value against PHC string. // // NOTE: hash params from `parsed_hash` are used instead of what is configured in the // `Argon2` instance. let parsed_hash = PasswordHash::new(hashed_value).map_err(|_| Error::Parse)?; let hashes_match = Argon2::default() .verify_password(value.as_bytes(), &parsed_hash) .is_ok(); Ok(hashes_match) } #[derive(Debug)] pub struct Hash { plain: Option, hash: String, } impl Hash { pub fn from_plain(plain: impl Into) -> Result { let plain = plain.into(); let hash = hash(&plain)?; Ok(Self { plain: Some(plain), hash, }) } pub fn from_hash(hash: impl Into) -> Self { Self { plain: None, hash: hash.into(), } } pub fn plain(&self) -> Option<&str> { self.plain.as_deref() } pub fn hash(&self) -> &str { self.hash.as_ref() } pub fn compare(&self, plain: &str) -> Result { verify(plain, self.hash()) } } #[cfg(test)] mod tests { use crate::hash::Hash; #[test] fn can_be_compared_with_hashed() { let value = Hash::from_plain("Testing the hashing").unwrap(); // Create from hashed value let value_from_hash = Hash::from_hash(value.hash()); // Make sure when hashing the plain text version, it can match the hashed one let does_value_match = value_from_hash.compare(value.plain().unwrap()).unwrap(); assert!(does_value_match); } }