Skip to main content

yggdrasil/auth/
password.rs

1//! 密码哈希与校验(Argon2)。
2//!
3//! 使用随机 salt 生成 PHC 字符串格式哈希,并通过 Argon2 验证。
4//! 仅在 `feature = "server"` 启用的服务端构建中使用。
5
6#[cfg(feature = "server")]
7use argon2::{
8    password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
9    Argon2,
10};
11#[cfg(feature = "server")]
12use rand::rngs::OsRng;
13
14#[cfg(feature = "server")]
15/// 使用 Argon2 对明文密码进行哈希。
16pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
17    let salt = SaltString::generate(&mut OsRng);
18    let argon2 = Argon2::default();
19    let password_hash = argon2.hash_password(password.as_bytes(), &salt)?;
20    Ok(password_hash.to_string())
21}
22
23#[cfg(feature = "server")]
24/// 校验明文密码是否与已存储的哈希匹配。
25pub fn verify_password(password: &str, hash: &str) -> Result<bool, argon2::password_hash::Error> {
26    let parsed_hash = PasswordHash::new(hash)?;
27    let argon2 = Argon2::default();
28    match argon2.verify_password(password.as_bytes(), &parsed_hash) {
29        Ok(()) => Ok(true),
30        Err(argon2::password_hash::Error::Password) => Ok(false),
31        Err(e) => Err(e),
32    }
33}
34
35#[cfg(all(test, feature = "server"))]
36mod tests {
37    use super::*;
38
39    #[test]
40    fn hash_and_verify_roundtrip() {
41        let hash = hash_password("mypassword123").unwrap();
42        assert!(verify_password("mypassword123", &hash).unwrap());
43    }
44
45    #[test]
46    fn verify_wrong_password_returns_false() {
47        let hash = hash_password("correctpassword").unwrap();
48        assert!(!verify_password("wrongpassword", &hash).unwrap());
49    }
50
51    #[test]
52    fn different_hashes_for_same_password() {
53        let hash1 = hash_password("samepassword").unwrap();
54        let hash2 = hash_password("samepassword").unwrap();
55        assert_ne!(hash1, hash2);
56    }
57
58    #[test]
59    fn verify_invalid_hash_returns_error() {
60        let result = verify_password("password", "not-a-valid-hash");
61        assert!(result.is_err());
62    }
63
64    #[test]
65    fn hash_empty_password() {
66        let hash = hash_password("").unwrap();
67        assert!(verify_password("", &hash).unwrap());
68    }
69
70    #[test]
71    fn hash_long_password() {
72        let long_pw = "a".repeat(1000);
73        let hash = hash_password(&long_pw).unwrap();
74        assert!(verify_password(&long_pw, &hash).unwrap());
75    }
76}