use crate::errors::{CrabError, CrabResult};
use crate::secrets::SecretVec;
use argon2::{Algorithm, Argon2, Params, Version};
#[derive(Debug, Clone)]
pub struct Argon2Params {
pub memory_cost: u32,
pub time_cost: u32,
pub parallelism: u32,
}
impl Default for Argon2Params {
fn default() -> Self {
Self {
memory_cost: 65536, time_cost: 3,
parallelism: 4,
}
}
}
impl Argon2Params {
pub fn interactive() -> Self {
Self::default()
}
pub fn high_security() -> Self {
Self {
memory_cost: 262144, time_cost: 5,
parallelism: 4,
}
}
pub fn low_memory() -> Self {
Self {
memory_cost: 32768, time_cost: 4,
parallelism: 2,
}
}
}
pub fn argon2_derive(password: &[u8], salt: &[u8], key_len: usize) -> CrabResult<SecretVec> {
argon2_derive_with_params(password, salt, key_len, &Argon2Params::default())
}
pub fn argon2_derive_with_params(
password: &[u8],
salt: &[u8],
key_len: usize,
params: &Argon2Params,
) -> CrabResult<SecretVec> {
if salt.len() < 16 {
return Err(CrabError::invalid_input("Argon2 requires salt of at least 16 bytes"));
}
if key_len == 0 || key_len > 4294967295 {
return Err(CrabError::invalid_input("Invalid key length"));
}
let argon2_params =
Params::new(params.memory_cost, params.time_cost, params.parallelism, Some(key_len))?;
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, argon2_params);
let mut output = vec![0u8; key_len];
argon2
.hash_password_into(password, salt, &mut output)
.map_err(|e| CrabError::key_error(format!("Argon2 derivation failed: {}", e)))?;
Ok(SecretVec::new(output))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_argon2_basic() {
let password = b"password123";
let salt = b"saltsaltsaltsalt";
let key = argon2_derive(password, salt, 32).unwrap();
assert_eq!(key.len(), 32);
}
#[test]
fn test_argon2_deterministic() {
let password = b"test_password";
let salt = b"test_salt_16byte";
let key1 = argon2_derive(password, salt, 32).unwrap();
let key2 = argon2_derive(password, salt, 32).unwrap();
assert_eq!(key1.as_slice(), key2.as_slice());
}
#[test]
fn test_argon2_different_passwords() {
let salt = b"same_salt_16byte";
let key1 = argon2_derive(b"password1", salt, 32).unwrap();
let key2 = argon2_derive(b"password2", salt, 32).unwrap();
assert_ne!(key1.as_slice(), key2.as_slice());
}
#[test]
fn test_argon2_different_salts() {
let password = b"same_password";
let key1 = argon2_derive(password, b"salt1_16bytes!!!", 32).unwrap();
let key2 = argon2_derive(password, b"salt2_16bytes!!!", 32).unwrap();
assert_ne!(key1.as_slice(), key2.as_slice());
}
#[test]
fn test_argon2_salt_too_short() {
let result = argon2_derive(b"password", b"short", 32);
assert!(result.is_err());
}
#[test]
fn test_argon2_custom_params() {
let password = b"password";
let salt = b"saltsaltsaltsalt";
let params = Argon2Params::low_memory();
let key = argon2_derive_with_params(password, salt, 32, ¶ms).unwrap();
assert_eq!(key.len(), 32);
}
#[test]
fn test_argon2_high_security() {
let password = b"password";
let salt = b"saltsaltsaltsalt";
let params = Argon2Params::high_security();
let key = argon2_derive_with_params(password, salt, 32, ¶ms).unwrap();
assert_eq!(key.len(), 32);
}
#[test]
fn test_argon2_variable_output_length() {
let password = b"password";
let salt = b"saltsaltsaltsalt";
let key16 = argon2_derive(password, salt, 16).unwrap();
let key32 = argon2_derive(password, salt, 32).unwrap();
let key64 = argon2_derive(password, salt, 64).unwrap();
assert_eq!(key16.len(), 16);
assert_eq!(key32.len(), 32);
assert_eq!(key64.len(), 64);
}
}