use std::fmt;
use argon2::{Algorithm, Argon2, Params, Version};
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::image_io::phash::PHashSalt;
const M_COST: u32 = 131_072;
const T_COST: u32 = 4;
const PARALLELISM: u32 = 2;
const MASTER_KEY_LEN: usize = 32;
#[derive(Debug)]
pub enum KdfError {
Argon2Error(String),
EmptyPassword,
}
impl fmt::Display for KdfError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
KdfError::Argon2Error(message) => {
write!(f, "argon2id key derivation failed: {message}")
}
KdfError::EmptyPassword => write!(f, "the password must not be empty"),
}
}
}
impl std::error::Error for KdfError {}
#[derive(ZeroizeOnDrop)]
pub struct MasterKey([u8; MASTER_KEY_LEN]);
impl MasterKey {
pub(crate) fn new(bytes: [u8; MASTER_KEY_LEN]) -> Self {
Self(bytes)
}
pub(crate) fn as_bytes(&self) -> &[u8] {
&self.0
}
}
pub trait KeyDeriver: Send + Sync {
fn derive(&self, password: &[u8], salt: &PHashSalt) -> Result<MasterKey, KdfError>;
}
pub struct Argon2Kdf {
m_cost: u32,
t_cost: u32,
parallelism: u32,
}
impl Argon2Kdf {
pub fn default_secure() -> Self {
Self {
m_cost: M_COST,
t_cost: T_COST,
parallelism: PARALLELISM,
}
}
#[cfg(any(test, feature = "test-utils"))]
pub fn low_cost_for_tests() -> Self {
Self {
m_cost: 8,
t_cost: 1,
parallelism: 1,
}
}
}
impl KeyDeriver for Argon2Kdf {
fn derive(&self, password: &[u8], salt: &PHashSalt) -> Result<MasterKey, KdfError> {
if password.is_empty() {
return Err(KdfError::EmptyPassword);
}
let params = Params::new(self.m_cost, self.t_cost, self.parallelism, None)
.map_err(|err| KdfError::Argon2Error(err.to_string()))?;
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
let mut bytes = [0u8; MASTER_KEY_LEN];
let outcome = argon2
.hash_password_into(password, salt.as_bytes(), &mut bytes)
.map_err(|err| KdfError::Argon2Error(err.to_string()));
let result = outcome.map(|()| MasterKey::new(bytes));
bytes.zeroize();
result
}
}