use argon2::{Algorithm, Argon2, Params, Version};
pub const KEK_LEN: usize = 32;
pub const ARGON2_M_COST_KIB: u32 = 65_536;
pub const ARGON2_T_COST: u32 = 3;
pub const ARGON2_P_COST: u32 = 1;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct KdfParams {
pub m_cost_kib: u32,
pub t_cost: u32,
pub p_cost: u32,
}
impl KdfParams {
pub const fn current() -> Self {
Self {
m_cost_kib: ARGON2_M_COST_KIB,
t_cost: ARGON2_T_COST,
p_cost: ARGON2_P_COST,
}
}
}
pub fn derive_kek(password: &[u8], salt: &[u8], params: KdfParams) -> Result<[u8; KEK_LEN], String> {
let p = Params::new(params.m_cost_kib, params.t_cost, params.p_cost, Some(KEK_LEN))
.map_err(|e| format!("Argon2 params invalid: {e}"))?;
let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, p);
let mut out = [0u8; KEK_LEN];
argon
.hash_password_into(password, salt, &mut out)
.map_err(|e| format!("Argon2 derivation failed: {e}"))?;
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn fast() -> KdfParams {
KdfParams { m_cost_kib: 256, t_cost: 1, p_cost: 1 }
}
#[test]
fn deterministic_for_same_inputs() {
let salt = b"0123456789abcdef";
let a = derive_kek(b"correct horse", salt, fast()).unwrap();
let b = derive_kek(b"correct horse", salt, fast()).unwrap();
assert_eq!(a, b);
}
#[test]
fn different_salt_changes_kek() {
let a = derive_kek(b"pw", b"salt-aaaaaaaaaaa", fast()).unwrap();
let b = derive_kek(b"pw", b"salt-bbbbbbbbbbb", fast()).unwrap();
assert_ne!(a, b);
}
#[test]
fn different_password_changes_kek() {
let salt = b"0123456789abcdef";
let a = derive_kek(b"pw-one", salt, fast()).unwrap();
let b = derive_kek(b"pw-two", salt, fast()).unwrap();
assert_ne!(a, b);
}
#[test]
fn different_params_change_kek() {
let salt = b"0123456789abcdef";
let a = derive_kek(b"pw", salt, KdfParams { m_cost_kib: 256, t_cost: 1, p_cost: 1 }).unwrap();
let b = derive_kek(b"pw", salt, KdfParams { m_cost_kib: 512, t_cost: 2, p_cost: 1 }).unwrap();
assert_ne!(a, b);
}
#[test]
fn output_is_32_bytes() {
let k = derive_kek(b"pw", b"0123456789abcdef", fast()).unwrap();
assert_eq!(k.len(), KEK_LEN);
}
#[test]
fn invalid_params_return_error() {
let bad = KdfParams { m_cost_kib: 1, t_cost: 1, p_cost: 1 };
assert!(derive_kek(b"pw", b"0123456789abcdef", bad).is_err());
}
}