forge
ExploreLog in

Tahinli/forge

public
0 watching
★ 0
1.5 KiB
1//! Password hashing with argon2id.
2
3use argon2::Argon2;
4use argon2::password_hash::phc::PasswordHash;
5use argon2::password_hash::{PasswordHasher, PasswordVerifier};
6
7use crate::error::{Error, Result};
8
9/// Hashes `pw` with argon2id (default parameters) and a random salt. Returns a PHC string.
10pub fn hash_password(pw: &str) -> Result<String> {
11 // The trait method draws a fresh salt from the OS RNG.
12 Argon2::default()
13 .hash_password(pw.as_bytes())
14 .map(|h| h.to_string())
15 .map_err(|e| Error::Crypto(format!("argon2: {e}")))
16}
17
18/// Returns true only if `pw` matches the PHC string `hash`. Any error yields false.
19pub fn verify_password(pw: &str, hash: &str) -> bool {
20 let Ok(parsed) = PasswordHash::new(hash) else {
21 return false;
22 };
23 Argon2::default()
24 .verify_password(pw.as_bytes(), &parsed)
25 .is_ok()
26}
27
28#[cfg(test)]
29mod tests {
30 use super::*;
31
32 #[test]
33 fn hash_then_verify() {
34 let hash = hash_password("correct horse").unwrap();
35 assert!(hash.starts_with("$argon2id$"));
36 assert!(verify_password("correct horse", &hash));
37 assert!(!verify_password("wrong", &hash));
38 }
39
40 #[test]
41 fn salts_differ_between_hashes() {
42 let a = hash_password("pw").unwrap();
43 let b = hash_password("pw").unwrap();
44 assert_ne!(a, b);
45 }
46
47 #[test]
48 fn verify_is_false_on_malformed_hash() {
49 assert!(!verify_password("pw", "not a phc string"));
50 assert!(!verify_password("pw", ""));
51 }
52}