Skip to main content

authx_core/crypto/
hashing.rs

1use argon2::{
2    Argon2, Params, Version,
3    password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng},
4};
5use sha2::{Digest, Sha256};
6use tracing::instrument;
7
8use crate::error::{AuthError, Result};
9
10fn argon2() -> Result<Argon2<'static>> {
11    let params = Params::new(65536, 3, 4, None).map_err(|e| AuthError::HashError(e.to_string()))?;
12    Ok(Argon2::new(
13        argon2::Algorithm::Argon2id,
14        Version::V0x13,
15        params,
16    ))
17}
18
19#[instrument(skip(password))]
20pub fn hash_password(password: &str) -> Result<String> {
21    let salt = SaltString::generate(&mut OsRng);
22    let hash = argon2()?
23        .hash_password(password.as_bytes(), &salt)
24        .map_err(|e| AuthError::HashError(e.to_string()))?
25        .to_string();
26
27    tracing::debug!("password hashed");
28    Ok(hash)
29}
30
31#[instrument(skip(password, hash))]
32pub fn verify_password(hash: &str, password: &str) -> Result<bool> {
33    let parsed = PasswordHash::new(hash).map_err(|e| AuthError::HashError(e.to_string()))?;
34
35    let ok = argon2()?
36        .verify_password(password.as_bytes(), &parsed)
37        .is_ok();
38
39    tracing::debug!(matched = ok, "password verified");
40    Ok(ok)
41}
42
43/// SHA-256 hex digest — used for session token storage, never for passwords.
44pub fn sha256_hex(data: &[u8]) -> String {
45    let mut hasher = Sha256::new();
46    hasher.update(data);
47    hex::encode(hasher.finalize())
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn round_trip_correct_password() {
56        let hash = hash_password("correct-horse-battery-staple").unwrap();
57        assert!(verify_password(&hash, "correct-horse-battery-staple").unwrap());
58    }
59
60    #[test]
61    fn wrong_password_returns_false() {
62        let hash = hash_password("secret").unwrap();
63        assert!(!verify_password(&hash, "wrong").unwrap());
64    }
65
66    #[test]
67    fn sha256_hex_is_deterministic() {
68        let a = sha256_hex(b"hello");
69        let b = sha256_hex(b"hello");
70        assert_eq!(a, b);
71        assert_eq!(a.len(), 64);
72    }
73
74    #[test]
75    fn sha256_hex_different_inputs_differ() {
76        assert_ne!(sha256_hex(b"a"), sha256_hex(b"b"));
77    }
78}