use crate::error::{Error, Result};
use crate::models::hash_algorithm::HashingAlgorithm;
use argon2::{Algorithm, Argon2, Params, Version};
use serde::{Deserialize, Serialize};
pub const DEFAULT_OUTPUT_LEN: usize = 32;
pub fn owasp_minimum_2025() -> Params {
Params::new(19_456, 2, 1, Some(DEFAULT_OUTPUT_LEN))
.expect("OWASP-2025 minimum params must be valid")
}
pub fn rfc9106_first_recommended() -> Params {
Params::new(1 << 21, 1, 4, Some(DEFAULT_OUTPUT_LEN))
.expect("RFC 9106 first-recommended params must be valid")
}
#[derive(
Clone,
Copy,
Debug,
Default,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
)]
pub struct Argon2id;
#[derive(
Clone,
Copy,
Debug,
Default,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
)]
pub struct Argon2i;
#[derive(
Clone,
Copy,
Debug,
Default,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
)]
pub struct Argon2d;
fn hash_with(
algo: Algorithm,
params: Params,
password: &str,
salt: &str,
) -> Result<Vec<u8>> {
let engine = Argon2::new(algo, Version::V0x13, params);
let mut out = vec![0u8; DEFAULT_OUTPUT_LEN];
engine
.hash_password_into(
password.as_bytes(),
salt.as_bytes(),
&mut out,
)
.map_err(|e| {
Error::hashing(
crate::error::HashingErrorKind::Argon2,
e.to_string(),
)
})?;
Ok(out)
}
impl HashingAlgorithm for Argon2id {
fn hash_password(password: &str, salt: &str) -> Result<Vec<u8>> {
hash_with(
Algorithm::Argon2id,
owasp_minimum_2025(),
password,
salt,
)
}
}
impl HashingAlgorithm for Argon2i {
fn hash_password(password: &str, salt: &str) -> Result<Vec<u8>> {
hash_with(
Algorithm::Argon2i,
owasp_minimum_2025(),
password,
salt,
)
}
}
impl HashingAlgorithm for Argon2d {
fn hash_password(password: &str, salt: &str) -> Result<Vec<u8>> {
hash_with(
Algorithm::Argon2d,
owasp_minimum_2025(),
password,
salt,
)
}
}
pub fn verify(
algo: Algorithm,
params: Params,
password: &str,
salt: &str,
stored: &[u8],
) -> Result<bool> {
use subtle::ConstantTimeEq;
if stored.len() != DEFAULT_OUTPUT_LEN {
return Ok(false);
}
let mut calculated = vec![0u8; stored.len()];
Argon2::new(algo, Version::V0x13, params)
.hash_password_into(
password.as_bytes(),
salt.as_bytes(),
&mut calculated,
)
.map_err(|e| {
Error::hashing(
crate::error::HashingErrorKind::Argon2,
e.to_string(),
)
})?;
Ok(bool::from(calculated.ct_eq(stored)))
}