use crate::error::{CryptoError, CryptoResult};
use argon2::{
Argon2, PasswordHash, PasswordHasher as Argon2PasswordHasher, PasswordVerifier, Version, password_hash::SaltString,
};
use zeroize::Zeroize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PasswordAlgorithm {
Bcrypt,
Argon2,
}
#[derive(Debug, Clone)]
pub struct PasswordHasherConfig {
pub algorithm: PasswordAlgorithm,
pub bcrypt_cost: u32,
pub argon2_memory_cost: u32,
pub argon2_time_cost: u32,
pub argon2_parallelism: u32,
}
impl Default for PasswordHasherConfig {
fn default() -> Self {
Self {
algorithm: PasswordAlgorithm::Bcrypt,
bcrypt_cost: 12,
argon2_memory_cost: 19456,
argon2_time_cost: 2,
argon2_parallelism: 1,
}
}
}
#[derive(Debug, Clone)]
pub struct PasswordHasher {
config: PasswordHasherConfig,
}
impl PasswordHasher {
pub fn new(config: PasswordHasherConfig) -> Self {
Self { config }
}
pub fn default() -> Self {
Self::new(PasswordHasherConfig::default())
}
pub fn hash_password(&self, password: &str) -> CryptoResult<String> {
let mut password_bytes = password.as_bytes().to_vec();
let result = match self.config.algorithm {
PasswordAlgorithm::Bcrypt => self.hash_bcrypt(&password_bytes),
PasswordAlgorithm::Argon2 => self.hash_argon2(&password_bytes),
};
password_bytes.zeroize();
result
}
pub fn verify_password(&self, password: &str, hash: &str) -> CryptoResult<bool> {
let mut password_bytes = password.as_bytes().to_vec();
let result = match self.config.algorithm {
PasswordAlgorithm::Bcrypt => self.verify_bcrypt(&password_bytes, hash),
PasswordAlgorithm::Argon2 => self.verify_argon2(&password_bytes, hash),
};
password_bytes.zeroize();
result
}
fn hash_bcrypt(&self, password: &[u8]) -> CryptoResult<String> {
bcrypt::hash(password, self.config.bcrypt_cost).map_err(|_| CryptoError::PasswordHashError)
}
fn verify_bcrypt(&self, password: &[u8], hash: &str) -> CryptoResult<bool> {
bcrypt::verify(password, hash).map_err(|_| CryptoError::PasswordVerifyError)
}
fn hash_argon2(&self, password: &[u8]) -> CryptoResult<String> {
use argon2::password_hash::rand_core::OsRng;
let mut rng = OsRng;
let salt = SaltString::generate(&mut rng);
let argon2 = Argon2::new(
argon2::Algorithm::Argon2id,
Version::V0x13,
argon2::Params::new(
self.config.argon2_memory_cost,
self.config.argon2_time_cost,
self.config.argon2_parallelism,
None,
)
.map_err(|_| CryptoError::PasswordHashError)?,
);
let password_hash = argon2.hash_password(password, &salt).map_err(|_| CryptoError::PasswordHashError)?;
Ok(password_hash.to_string())
}
fn verify_argon2(&self, password: &[u8], hash: &str) -> CryptoResult<bool> {
let parsed_hash = PasswordHash::new(hash).map_err(|_| CryptoError::PasswordVerifyError)?;
let argon2 = Argon2::default();
Ok(argon2.verify_password(password, &parsed_hash).is_ok())
}
}
impl Default for PasswordHasher {
fn default() -> Self {
Self::new(PasswordHasherConfig::default())
}
}