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::{phc::PasswordHash, PasswordHasher, PasswordVerifier},
9    Argon2,
10};
11
12#[cfg(feature = "server")]
13/// 使用 Argon2 对明文密码进行哈希。
14pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
15    let argon2 = Argon2::default();
16    let password_hash = argon2.hash_password(password.as_bytes())?;
17    Ok(password_hash.to_string())
18}
19
20#[cfg(feature = "server")]
21/// 校验明文密码是否与已存储的哈希匹配。
22pub fn verify_password(password: &str, hash: &str) -> Result<bool, argon2::password_hash::Error> {
23    let parsed_hash = PasswordHash::new(hash)?;
24    let argon2 = Argon2::default();
25    match argon2.verify_password(password.as_bytes(), &parsed_hash) {
26        Ok(()) => Ok(true),
27        Err(argon2::password_hash::Error::PasswordInvalid) => Ok(false),
28        Err(e) => Err(e),
29    }
30}
31
32#[cfg(all(test, feature = "server"))]
33mod tests {
34    use super::*;
35
36    #[test]
37    fn verify_legacy_argon2id_phc() {
38        // Argon2 0.5.3 tests/phc_strings.rs, from the reference implementation:
39        // https://github.com/RustCrypto/password-hashes/blob/argon2-v0.5.3/argon2/tests/phc_strings.rs
40        let hash = "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc";
41        assert_eq!(verify_password("password", hash), Ok(true));
42        assert_eq!(verify_password("sassword", hash), Ok(false));
43    }
44
45    #[test]
46    fn hash_and_verify_roundtrip() {
47        let hash = hash_password("mypassword123").unwrap();
48        assert!(verify_password("mypassword123", &hash).unwrap());
49    }
50
51    #[test]
52    fn verify_wrong_password_returns_false() {
53        let hash = hash_password("correctpassword").unwrap();
54        assert!(!verify_password("wrongpassword", &hash).unwrap());
55    }
56
57    #[test]
58    fn different_hashes_for_same_password() {
59        let hash1 = hash_password("samepassword").unwrap();
60        let hash2 = hash_password("samepassword").unwrap();
61        assert_ne!(hash1, hash2);
62    }
63
64    #[test]
65    fn verify_invalid_hash_returns_error() {
66        let result = verify_password("password", "not-a-valid-hash");
67        assert!(result.is_err());
68    }
69
70    #[test]
71    fn hash_empty_password() {
72        let hash = hash_password("").unwrap();
73        assert!(verify_password("", &hash).unwrap());
74    }
75
76    #[test]
77    fn hash_long_password() {
78        let long_pw = "a".repeat(1000);
79        let hash = hash_password(&long_pw).unwrap();
80        assert!(verify_password(&long_pw, &hash).unwrap());
81    }
82}