Skip to main content

shared/utils/crypto/
password_hasher.rs

1use std::str::FromStr;
2
3use argon2::{
4    Argon2, Params, PasswordHash, PasswordHasher, PasswordVerifier,
5    password_hash::{SaltString, rand_core::OsRng},
6};
7
8use crate::error::{CoreError, InternalError, Result};
9
10pub trait Hashable: Send + Sync {
11    fn hash(&self, value: &str) -> Result<String>;
12    fn verify(&self, a: &str, b: &str) -> Result<bool>;
13}
14
15// =============================================================================
16// Password Hasher - Argon
17// =============================================================================
18#[derive(Clone)]
19pub struct Argon2Password {
20    memory_kib: u32,
21    iterations: u32,
22    parallelism: u32,
23}
24impl Default for Argon2Password {
25    fn default() -> Self {
26        pub const DEFAULT_M_COST: u32 = 19 * 1024; // ~19 MiB
27        pub const DEFAULT_T_COST: u32 = 2;
28        pub const DEFAULT_P_COST: u32 = 1;
29
30        Self {
31            memory_kib: DEFAULT_M_COST,
32            iterations: DEFAULT_T_COST,
33            parallelism: DEFAULT_P_COST,
34        }
35    }
36}
37impl Argon2Password {
38    pub fn new(m_cost: u32, t_cost: u32, p_cost: u32) -> Self {
39        Self {
40            memory_kib: m_cost,
41            iterations: t_cost,
42            parallelism: p_cost,
43        }
44    }
45}
46
47impl Hashable for Argon2Password {
48    fn hash(&self, password: &str) -> Result<String> {
49        let params = Params::new(
50            self.memory_kib,  // m_cost: memory size in KiB (19 MB)
51            self.iterations,  // t_cost: number of iterations
52            self.parallelism, // p_cost: parallelism (threads)
53            None,             // output length (None = default 32 bytes)
54        )
55        .unwrap();
56
57        let salt = SaltString::generate(&mut OsRng);
58
59        let hash = Argon2::new(
60            argon2::Algorithm::default(),
61            argon2::Version::default(),
62            params,
63        )
64        .hash_password(password.as_bytes(), &salt)
65        .map_err(|e| {
66            tracing::error!("Failed to hash user password: {:?}", e);
67            CoreError::Internal(InternalError::Hashing)
68        })?;
69
70        Ok(hash.to_string())
71    }
72
73    fn verify(&self, password: &str, hash: &str) -> Result<bool> {
74        static DUMMY_HASH: &str = "$argon2id$v=19$m=65536,t=3,p=4$\
75     Lm1Jk9XQ2E1o8XxZMZ1jPQ$\
76     8vBxrT9uC1NQb3lQfa2RyEBJxK2Sr6ELrRvsGqIzJxA";
77
78        let parsed = PasswordHash::new(hash)
79            .or_else(|_| PasswordHash::new(DUMMY_HASH))
80            .map_err(|_| CoreError::Internal(InternalError::Hashing))?;
81
82        let isvalid = Argon2::default()
83            .verify_password(password.as_bytes(), &parsed)
84            .is_ok();
85
86        Ok(isvalid)
87    }
88}
89
90// =============================================================================
91// Password Hasher - BCrypt
92// =============================================================================
93#[derive(Clone)]
94pub struct BcryptPassword {
95    cost: u32,
96}
97impl BcryptPassword {
98    pub fn new(cost: u32) -> Self {
99        Self { cost }
100    }
101}
102
103impl Hashable for BcryptPassword {
104    fn hash(&self, value: &str) -> Result<String> {
105        let hash = bcrypt::hash(value, self.cost).map_err(|e| {
106            tracing::error!("Failed to hash user password: {:?}", e);
107            CoreError::Internal(InternalError::Hashing)
108        })?;
109
110        Ok(hash.to_string())
111    }
112
113    fn verify(&self, password: &str, hash: &str) -> Result<bool> {
114        static DUMMY_HASH: &str = "$2b$12$kgQ2Rl.I22hVIJVklF9OceDIv8EBoMXtlw6U15pVLlmleTLfRUMRe";
115
116        let hash_to_verify = if bcrypt::HashParts::from_str(hash).is_ok() {
117            hash.to_string()
118        } else {
119            DUMMY_HASH.to_string()
120        };
121
122        let is_valid = bcrypt::verify(password, &hash_to_verify)
123            .map_err(|_| CoreError::Internal(InternalError::Hashing))?;
124
125        Ok(is_valid)
126    }
127}