1use crate::error::{CryptoError, CryptoResult};
4use argon2::{
5 Argon2, PasswordHash, PasswordHasher as Argon2PasswordHasher, PasswordVerifier, Version, password_hash::SaltString,
6};
7use zeroize::Zeroize;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum PasswordAlgorithm {
12 Bcrypt,
14 Argon2,
16}
17
18#[derive(Debug, Clone)]
20pub struct PasswordHasherConfig {
21 pub algorithm: PasswordAlgorithm,
23 pub bcrypt_cost: u32,
25 pub argon2_memory_cost: u32,
27 pub argon2_time_cost: u32,
29 pub argon2_parallelism: u32,
31}
32
33impl Default for PasswordHasherConfig {
34 fn default() -> Self {
35 Self {
36 algorithm: PasswordAlgorithm::Bcrypt,
37 bcrypt_cost: 12,
38 argon2_memory_cost: 19456,
39 argon2_time_cost: 2,
40 argon2_parallelism: 1,
41 }
42 }
43}
44
45#[derive(Debug, Clone)]
47pub struct PasswordHasher {
48 config: PasswordHasherConfig,
49}
50
51impl PasswordHasher {
52 pub fn new(config: PasswordHasherConfig) -> Self {
54 Self { config }
55 }
56
57 pub fn default() -> Self {
59 Self::new(PasswordHasherConfig::default())
60 }
61
62 pub fn hash_password(&self, password: &str) -> CryptoResult<String> {
64 let mut password_bytes = password.as_bytes().to_vec();
65 let result = match self.config.algorithm {
66 PasswordAlgorithm::Bcrypt => self.hash_bcrypt(&password_bytes),
67 PasswordAlgorithm::Argon2 => self.hash_argon2(&password_bytes),
68 };
69 password_bytes.zeroize();
70 result
71 }
72
73 pub fn verify_password(&self, password: &str, hash: &str) -> CryptoResult<bool> {
75 let mut password_bytes = password.as_bytes().to_vec();
76 let result = match self.config.algorithm {
77 PasswordAlgorithm::Bcrypt => self.verify_bcrypt(&password_bytes, hash),
78 PasswordAlgorithm::Argon2 => self.verify_argon2(&password_bytes, hash),
79 };
80 password_bytes.zeroize();
81 result
82 }
83
84 fn hash_bcrypt(&self, password: &[u8]) -> CryptoResult<String> {
86 bcrypt::hash(password, self.config.bcrypt_cost).map_err(|_| CryptoError::PasswordHashError)
87 }
88
89 fn verify_bcrypt(&self, password: &[u8], hash: &str) -> CryptoResult<bool> {
91 bcrypt::verify(password, hash).map_err(|_| CryptoError::PasswordVerifyError)
92 }
93
94 fn hash_argon2(&self, password: &[u8]) -> CryptoResult<String> {
96 use argon2::password_hash::rand_core::OsRng;
97 let mut rng = OsRng;
98 let salt = SaltString::generate(&mut rng);
99 let argon2 = Argon2::new(
100 argon2::Algorithm::Argon2id,
101 Version::V0x13,
102 argon2::Params::new(
103 self.config.argon2_memory_cost,
104 self.config.argon2_time_cost,
105 self.config.argon2_parallelism,
106 None,
107 )
108 .map_err(|_| CryptoError::PasswordHashError)?,
109 );
110 let password_hash = argon2.hash_password(password, &salt).map_err(|_| CryptoError::PasswordHashError)?;
111 Ok(password_hash.to_string())
112 }
113
114 fn verify_argon2(&self, password: &[u8], hash: &str) -> CryptoResult<bool> {
116 let parsed_hash = PasswordHash::new(hash).map_err(|_| CryptoError::PasswordVerifyError)?;
117 let argon2 = Argon2::default();
118 Ok(argon2.verify_password(password, &parsed_hash).is_ok())
119 }
120}
121
122impl Default for PasswordHasher {
123 fn default() -> Self {
124 Self::new(PasswordHasherConfig::default())
125 }
126}