#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use crate::error::{validate, Error, Result};
use super::params::ParamProvider; use super::{KdfAlgorithm, KdfOperation}; use super::{KeyDerivationFunction, PasswordHash, PasswordHashFunction, SecurityLevel};
use crate::hash::blake2::Blake2b;
use crate::hash::HashFunction; use crate::types::{Salt, SecretBytes};
use crate::Argon2Compatible;
use base64::Engine;
use core::convert::TryInto;
use rand::{CryptoRng, RngCore};
use std::collections::BTreeMap;
use std::time::Duration;
use zeroize::{Zeroize, Zeroizing};
const ARGON2_VERSION_1_3: u32 = 0x13;
const ARGON2_BLOCK_SIZE: usize = 1024;
const ARGON2_QWORDS_IN_BLOCK: usize = ARGON2_BLOCK_SIZE / 8; const ARGON2_SYNC_POINTS: u32 = 4;
const ARGON2_PREHASH_SEED_LENGTH: usize = 72;
fn create_blake2b_for_h0() -> Blake2b {
let mut param = [0u8; 64];
param[0] = 64; param[1] = 0; param[2] = 1; param[3] = 1; param[16] = 0; param[17] = 0;
Blake2b::with_parameter_block(param, 64)
}
fn blake2b_params(digest_len: u8) -> Blake2b {
let mut param = [0u8; 64];
param[0] = digest_len; param[1] = 0; param[2] = 1; param[3] = 1; param[16] = 0; param[17] = 0;
Blake2b::with_parameter_block(param, digest_len as usize)
}
#[derive(Clone)]
struct Block([u8; ARGON2_BLOCK_SIZE]);
impl Zeroize for Block {
fn zeroize(&mut self) {
self.0.iter_mut().for_each(|b| *b = 0);
}
}
type MemBlock = Block;
#[inline(always)]
fn mul_alpha(x: u64, y: u64) -> u64 {
2u64.wrapping_mul(x & 0xFFFF_FFFF)
.wrapping_mul(y & 0xFFFF_FFFF)
}
#[inline(always)]
fn blamka(a: u64, b: u64, c: u64, d: u64) -> (u64, u64, u64, u64) {
let mut a = a;
let mut b = b;
let mut c = c;
let mut d = d;
a = a.wrapping_add(b).wrapping_add(mul_alpha(a, b));
d ^= a;
d = d.rotate_right(32);
c = c.wrapping_add(d).wrapping_add(mul_alpha(c, d));
b ^= c;
b = b.rotate_right(24);
a = a.wrapping_add(b).wrapping_add(mul_alpha(a, b));
d ^= a;
d = d.rotate_right(16);
c = c.wrapping_add(d).wrapping_add(mul_alpha(c, d));
b ^= c;
b = b.rotate_right(63);
(a, b, c, d)
}
#[inline(always)]
fn blamka_round(state: &mut [u64; 16]) {
for &(i, j, k, l) in &[(0, 4, 8, 12), (1, 5, 9, 13), (2, 6, 10, 14), (3, 7, 11, 15)] {
let (na, nb, nc, nd) = blamka(state[i], state[j], state[k], state[l]);
state[i] = na;
state[j] = nb;
state[k] = nc;
state[l] = nd;
}
for &(i, j, k, l) in &[(0, 5, 10, 15), (1, 6, 11, 12), (2, 7, 8, 13), (3, 4, 9, 14)] {
let (na, nb, nc, nd) = blamka(state[i], state[j], state[k], state[l]);
state[i] = na;
state[j] = nb;
state[k] = nc;
state[l] = nd;
}
}
fn argon2_g(
x: &[u64; ARGON2_QWORDS_IN_BLOCK],
y: &[u64; ARGON2_QWORDS_IN_BLOCK],
) -> [u64; ARGON2_QWORDS_IN_BLOCK] {
let mut r = [0u64; ARGON2_QWORDS_IN_BLOCK];
for i in 0..ARGON2_QWORDS_IN_BLOCK {
r[i] = x[i] ^ y[i];
}
for chunk in r.chunks_exact_mut(16) {
let row: &mut [u64; 16] = chunk.try_into().unwrap();
blamka_round(row);
}
for reg in 0..8 {
let mut tmp = [0u64; 16];
for row in 0..8 {
let base = row * 16 + reg * 2; tmp[2 * row] = r[base];
tmp[2 * row + 1] = r[base + 1];
}
blamka_round(&mut tmp);
for row in 0..8 {
let base = row * 16 + reg * 2;
r[base] = tmp[2 * row];
r[base + 1] = tmp[2 * row + 1];
}
}
for i in 0..ARGON2_QWORDS_IN_BLOCK {
r[i] ^= x[i] ^ y[i];
}
r
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Zeroize)]
pub enum Algorithm {
Argon2d = 0,
Argon2i = 1,
Argon2id = 2,
}
#[derive(Clone, Zeroize)] pub struct Params<const S: usize>
where
Salt<S>: Argon2Compatible,
{
pub argon_type: Algorithm,
pub version: u32,
pub memory_cost: u32, pub time_cost: u32, pub parallelism: u32, pub output_len: usize,
pub salt: Salt<S>,
pub ad: Option<Zeroizing<Vec<u8>>>,
pub secret: Option<Zeroizing<Vec<u8>>>,
}
impl<const S: usize> Default for Params<S>
where
Salt<S>: Argon2Compatible,
{
fn default() -> Self {
Params {
argon_type: Algorithm::Argon2id,
version: ARGON2_VERSION_1_3,
memory_cost: 19 * 1024,
time_cost: 2,
parallelism: 1,
output_len: 32,
salt: Salt::<S>::zeroed(), ad: None,
secret: None,
}
}
}
#[derive(Clone)]
pub struct Argon2<const S: usize>
where
Salt<S>: Argon2Compatible,
{
params: Params<S>,
}
const MAX_PWD_LEN: u32 = 0xFFFFFFFF;
const MIN_SALT_LEN: usize = 8;
const MAX_SALT_LEN: u32 = 0xFFFFFFFF;
const MAX_AD_LEN: u32 = 0xFFFFFFFF;
const MAX_SECRET_LEN: u32 = 0xFFFFFFFF;
const MIN_LANES: u32 = 1;
const MAX_LANES: u32 = 0xFFFFFF;
const MIN_OUT_LEN: usize = 4;
const MAX_OUT_LEN: u32 = 0xFFFFFFFF;
const MIN_TIME_COST: u32 = 1;
const MIN_ABS_MEMORY_COST_KIB: u32 = 8;
impl<const S: usize> Argon2<S>
where
Salt<S>: Argon2Compatible,
{
pub fn new_with_params(params: Params<S>) -> Self {
Self { params }
}
pub fn hash_password(&self, password: &[u8]) -> Result<Zeroizing<Vec<u8>>> {
let p = &self.params;
let salt_bytes = p.salt.as_ref();
let ad_bytes = p.ad.as_ref().map(|z_vec| z_vec.as_slice());
let secret_bytes = p.secret.as_ref().map(|z_vec| z_vec.as_slice());
internal_argon2_core(
password,
salt_bytes,
ad_bytes,
secret_bytes,
p.argon_type,
p.version,
p.output_len,
p.memory_cost,
p.time_cost,
p.parallelism,
)
}
}
#[allow(clippy::too_many_arguments)]
fn fill_address_block_for_segment(
address_qwords: &mut [u64; ARGON2_QWORDS_IN_BLOCK],
pass: u32,
lane: u32,
slice: u32,
m_prime: u32,
t_cost: u32,
alg: Algorithm,
counter: u64, buf: &mut Block,
) -> Result<()> {
buf.zeroize();
let mut off = 0;
buf.0[off..off + 8].copy_from_slice(&(pass as u64).to_le_bytes());
off += 8;
buf.0[off..off + 8].copy_from_slice(&(lane as u64).to_le_bytes());
off += 8;
buf.0[off..off + 8].copy_from_slice(&(slice as u64).to_le_bytes());
off += 8;
buf.0[off..off + 8].copy_from_slice(&(m_prime as u64).to_le_bytes());
off += 8;
buf.0[off..off + 8].copy_from_slice(&(t_cost as u64).to_le_bytes());
off += 8;
let y = match alg {
Algorithm::Argon2i => 1,
Algorithm::Argon2id => 2,
_ => 0,
};
buf.0[off..off + 8].copy_from_slice(&(y as u64).to_le_bytes());
off += 8;
buf.0[off..off + 8].copy_from_slice(&counter.to_le_bytes());
let mut input_q = [0u64; ARGON2_QWORDS_IN_BLOCK];
for (i, chunk) in buf
.0
.chunks_exact(8)
.enumerate()
.take(ARGON2_QWORDS_IN_BLOCK)
{
input_q[i] = u64::from_le_bytes(chunk.try_into().unwrap());
}
let zero = [0u64; ARGON2_QWORDS_IN_BLOCK];
let block0 = argon2_g(&zero, &input_q);
let block1 = argon2_g(&zero, &block0);
address_qwords.copy_from_slice(&block1);
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn internal_argon2_core(
password: &[u8],
salt: &[u8],
ad: Option<&[u8]>,
secret: Option<&[u8]>,
argon_type: Algorithm,
version: u32,
output_len: usize,
memory_cost_kib: u32,
time_cost_iterations: u32,
parallelism_lanes: u32,
) -> Result<Zeroizing<Vec<u8>>> {
validate::parameter(
output_len >= MIN_OUT_LEN,
"output_len",
"value is below minimum",
)?;
validate::parameter(
output_len <= MAX_OUT_LEN as usize,
"output_len",
"value is above maximum",
)?;
validate::parameter(
password.len() <= MAX_PWD_LEN as usize,
"password_len",
"value is above maximum",
)?;
validate::parameter(
salt.len() >= MIN_SALT_LEN,
"salt_len",
"value is below minimum",
)?;
validate::parameter(
salt.len() <= MAX_SALT_LEN as usize,
"salt_len",
"value is above maximum",
)?;
if let Some(ad_data) = ad {
validate::parameter(
ad_data.len() <= MAX_AD_LEN as usize,
"ad_len",
"value is above maximum",
)?;
}
if let Some(secret_data) = secret {
validate::parameter(
secret_data.len() <= MAX_SECRET_LEN as usize,
"secret_len",
"value is above maximum",
)?;
}
validate::parameter(
time_cost_iterations >= MIN_TIME_COST,
"time_cost",
"value is below minimum",
)?;
validate::parameter(
parallelism_lanes >= MIN_LANES,
"parallelism_lanes",
"value is below minimum",
)?;
validate::parameter(
parallelism_lanes <= MAX_LANES,
"parallelism_lanes",
"value is above maximum",
)?;
let effective_min_mem_kib = 8 * parallelism_lanes;
validate::parameter(
memory_cost_kib >= MIN_ABS_MEMORY_COST_KIB,
"memory_cost_kib (absolute)",
"value is below minimum",
)?;
validate::parameter(
memory_cost_kib >= effective_min_mem_kib,
"memory_cost_kib (vs lanes)",
"value is below minimum",
)?;
if version != ARGON2_VERSION_1_3 {
return Err(Error::param("version", "unsupported Argon2 version"));
}
let mut h0_buffer_cap = ARGON2_PREHASH_SEED_LENGTH;
h0_buffer_cap = h0_buffer_cap.max(
4 * 7 + password.len() + salt.len() + secret.unwrap_or(&[]).len() + ad.unwrap_or(&[]).len(),
);
let mut h0_buffer = Zeroizing::new(Vec::with_capacity(h0_buffer_cap));
h0_buffer.extend_from_slice(¶llelism_lanes.to_le_bytes());
h0_buffer.extend_from_slice(&(output_len as u32).to_le_bytes());
h0_buffer.extend_from_slice(&memory_cost_kib.to_le_bytes());
h0_buffer.extend_from_slice(&time_cost_iterations.to_le_bytes());
h0_buffer.extend_from_slice(&version.to_le_bytes());
h0_buffer.extend_from_slice(&(argon_type as u32).to_le_bytes());
h0_buffer.extend_from_slice(&(password.len() as u32).to_le_bytes());
h0_buffer.extend_from_slice(password);
h0_buffer.extend_from_slice(&(salt.len() as u32).to_le_bytes());
h0_buffer.extend_from_slice(salt);
let secret_data = secret.unwrap_or(&[]);
h0_buffer.extend_from_slice(&(secret_data.len() as u32).to_le_bytes());
h0_buffer.extend_from_slice(secret_data);
let ad_data = ad.unwrap_or(&[]);
h0_buffer.extend_from_slice(&(ad_data.len() as u32).to_le_bytes());
h0_buffer.extend_from_slice(ad_data);
let mut h0_hasher = create_blake2b_for_h0();
h0_hasher.update(&h0_buffer)?;
let h0_digest = h0_hasher.finalize()?;
let mut h0 = Zeroizing::new(h0_digest.as_ref().to_vec());
h0_buffer.zeroize();
let num_memory_blocks_total = (memory_cost_kib / (parallelism_lanes * ARGON2_SYNC_POINTS))
* (parallelism_lanes * ARGON2_SYNC_POINTS);
let lane_length = num_memory_blocks_total / parallelism_lanes;
if lane_length == 0 {
return Err(Error::param(
"memory_cost_kib",
"Effective lane length is zero after rounding.",
));
}
let segment_length = lane_length / ARGON2_SYNC_POINTS;
let mut memory_matrix: Vec<MemBlock> =
vec![Block([0u8; ARGON2_BLOCK_SIZE]); num_memory_blocks_total as usize];
for lane_idx in 0..parallelism_lanes {
let mut block_seed = Zeroizing::new(Vec::with_capacity(h0.len() + 8));
block_seed.extend_from_slice(&h0);
block_seed.extend_from_slice(&0u32.to_le_bytes());
block_seed.extend_from_slice(&lane_idx.to_le_bytes());
let block0_val = h_prime_variable_output(&block_seed, ARGON2_BLOCK_SIZE)?;
memory_matrix[(lane_idx * lane_length) as usize]
.0
.copy_from_slice(&block0_val);
block_seed.clear();
block_seed.extend_from_slice(&h0);
block_seed.extend_from_slice(&1u32.to_le_bytes());
block_seed.extend_from_slice(&lane_idx.to_le_bytes());
let block1_val = h_prime_variable_output(&block_seed, ARGON2_BLOCK_SIZE)?;
memory_matrix[(lane_idx * lane_length + 1) as usize]
.0
.copy_from_slice(&block1_val);
}
h0.zeroize();
let mut address_block_qwords = [0u64; ARGON2_QWORDS_IN_BLOCK];
let mut input_block_buffer = Block([0u8; ARGON2_BLOCK_SIZE]);
for pass_idx in 0..time_cost_iterations {
for slice_idx in 0..ARGON2_SYNC_POINTS {
for lane_idx in 0..parallelism_lanes {
let data_independent_addressing_for_segment = match argon_type {
Algorithm::Argon2i => true,
Algorithm::Argon2d => false,
Algorithm::Argon2id => pass_idx == 0 && slice_idx < (ARGON2_SYNC_POINTS / 2),
};
let first_block_in_segment_offset = if pass_idx == 0 && slice_idx == 0 {
2
} else {
0
};
let mut address_block_counter = 0u64;
for block_in_segment_idx in first_block_in_segment_offset..segment_length {
let current_block_offset_in_lane =
slice_idx * segment_length + block_in_segment_idx;
let current_block_abs_idx =
(lane_idx * lane_length + current_block_offset_in_lane) as usize;
let prev_block_offset_in_lane = if current_block_offset_in_lane == 0 {
lane_length - 1
} else {
current_block_offset_in_lane - 1
};
let prev_block_abs_idx =
(lane_idx * lane_length + prev_block_offset_in_lane) as usize;
let pseudo_rand: u64 = if data_independent_addressing_for_segment {
let need_new = block_in_segment_idx == 0
|| block_in_segment_idx as usize % ARGON2_QWORDS_IN_BLOCK == 0;
if need_new {
address_block_counter += 1;
fill_address_block_for_segment(
&mut address_block_qwords,
pass_idx,
lane_idx,
slice_idx,
num_memory_blocks_total,
time_cost_iterations,
argon_type,
address_block_counter,
&mut input_block_buffer,
)?;
}
address_block_qwords[block_in_segment_idx as usize % ARGON2_QWORDS_IN_BLOCK]
} else {
let mut buf = [0u8; 8];
buf.copy_from_slice(&memory_matrix[prev_block_abs_idx].0[0..8]);
u64::from_le_bytes(buf)
};
let j1 = (pseudo_rand & 0xFFFF_FFFF) as u32; let j2 = (pseudo_rand >> 32) as u32;
let ref_lane_val = if pass_idx == 0 && slice_idx == 0 {
lane_idx } else {
j2 % parallelism_lanes };
let (ref_idx_in_lane, _area_size) = index_alpha(
pass_idx,
slice_idx,
block_in_segment_idx,
lane_length,
segment_length,
parallelism_lanes,
lane_idx,
ref_lane_val,
j1,
);
let ref_block_abs_idx = (ref_lane_val * lane_length + ref_idx_in_lane) as usize;
let prev_block_data = &memory_matrix[prev_block_abs_idx].0;
let ref_block_data = &memory_matrix[ref_block_abs_idx].0;
let cur_block_data = if pass_idx > 0 {
let mut data = [0u8; ARGON2_BLOCK_SIZE];
data.copy_from_slice(&memory_matrix[current_block_abs_idx].0);
data
} else {
[0u8; ARGON2_BLOCK_SIZE] };
let mut xv = [0u64; ARGON2_QWORDS_IN_BLOCK];
let mut yv = [0u64; ARGON2_QWORDS_IN_BLOCK];
for (i, chunk) in prev_block_data
.chunks_exact(8)
.enumerate()
.take(ARGON2_QWORDS_IN_BLOCK)
{
xv[i] = u64::from_le_bytes(chunk.try_into().unwrap());
}
for (i, chunk) in ref_block_data
.chunks_exact(8)
.enumerate()
.take(ARGON2_QWORDS_IN_BLOCK)
{
yv[i] = u64::from_le_bytes(chunk.try_into().unwrap());
}
let gq = argon2_g(&xv, &yv);
let mut gbytes = [0u8; ARGON2_BLOCK_SIZE];
for (i, &qword) in gq.iter().enumerate().take(ARGON2_QWORDS_IN_BLOCK) {
let start = i * 8;
gbytes[start..start + 8].copy_from_slice(&qword.to_le_bytes());
}
if pass_idx == 0 {
memory_matrix[current_block_abs_idx]
.0
.copy_from_slice(&gbytes);
} else {
for k in 0..ARGON2_BLOCK_SIZE {
memory_matrix[current_block_abs_idx].0[k] =
gbytes[k] ^ cur_block_data[k];
}
}
}
}
}
}
let mut final_block_xor_sum_vec =
Zeroizing::new(memory_matrix[(lane_length - 1) as usize].0.to_vec());
for lane_idx in 1..parallelism_lanes {
let last_block_in_lane_idx = (lane_idx * lane_length + (lane_length - 1)) as usize;
for k in 0..ARGON2_BLOCK_SIZE {
final_block_xor_sum_vec[k] ^= memory_matrix[last_block_in_lane_idx].0[k];
}
}
let final_hash_vec = h_prime_variable_output(&final_block_xor_sum_vec, output_len)?;
Ok(Zeroizing::new(final_hash_vec))
}
fn h_prime_variable_output(data: &[u8], t: usize) -> Result<Vec<u8>> {
if t == 0 {
return Ok(vec![]);
}
if t <= 64 {
let mut h = blake2b_params(t as u8);
h.update(&u32::to_le_bytes(t as u32))?;
h.update(data)?;
let v = h.finalize()?.as_ref().to_vec();
return Ok(v);
}
let ceil_div = |x: usize, y: usize| x.div_ceil(y);
let r = ceil_div(t, 32) - 2;
let mut out = Vec::with_capacity(t);
let mut h = blake2b_params(64);
h.update(&u32::to_le_bytes(t as u32))?;
h.update(data)?;
let mut prev = h.finalize()?.as_ref().to_vec();
out.extend_from_slice(&prev[..32]);
for _ in 1..r {
let mut h = blake2b_params(64);
h.update(&prev)?;
let v = h.finalize()?.as_ref().to_vec();
out.extend_from_slice(&v[..32]);
prev = v;
}
let final_len = t - 32 * r;
let mut h = blake2b_params(final_len as u8);
h.update(&prev)?;
let v = h.finalize()?.as_ref().to_vec();
out.extend_from_slice(&v);
Ok(out)
}
#[allow(clippy::too_many_arguments)]
fn index_alpha(
pass_idx: u32,
slice_idx: u32,
block_in_segment_idx: u32,
lane_length: u32,
segment_length: u32,
_parallelism_lanes: u32,
current_lane_idx: u32,
ref_lane_val: u32,
j1: u32,
) -> (u32, u32) {
let mut reference_area_size: u32;
if pass_idx == 0 {
if slice_idx == 0 {
reference_area_size = block_in_segment_idx.saturating_sub(1);
} else if ref_lane_val == current_lane_idx {
reference_area_size = slice_idx * segment_length + block_in_segment_idx;
reference_area_size = reference_area_size.saturating_sub(1); } else {
reference_area_size = slice_idx * segment_length;
if block_in_segment_idx == 0 {
reference_area_size = reference_area_size.saturating_sub(1);
}
}
} else {
if ref_lane_val == current_lane_idx {
reference_area_size = lane_length - segment_length + block_in_segment_idx;
reference_area_size = reference_area_size.saturating_sub(1);
} else {
reference_area_size = lane_length - segment_length;
if block_in_segment_idx == 0 {
reference_area_size = reference_area_size.saturating_sub(1);
}
}
}
let mut phi = j1 as u64;
phi = (phi * phi) >> 32; let relative_position = if reference_area_size == 0 {
0
} else {
let rhs = ((reference_area_size as u64) * phi) >> 32;
(reference_area_size as u64)
.saturating_sub(1)
.saturating_sub(rhs)
} as u32;
let start_position_offset = if pass_idx == 0 || slice_idx == ARGON2_SYNC_POINTS - 1 {
0
} else {
(slice_idx + 1) * segment_length
};
let ref_idx_in_lane = (start_position_offset + relative_position) % lane_length;
(ref_idx_in_lane, reference_area_size)
}
pub enum Argon2Algorithm {}
impl KdfAlgorithm for Argon2Algorithm {
const MIN_SALT_SIZE: usize = MIN_SALT_LEN;
const DEFAULT_OUTPUT_SIZE: usize = 32;
const ALGORITHM_ID: &'static str = "argon2";
fn name() -> String {
"Argon2".to_string()
}
fn security_level() -> SecurityLevel {
SecurityLevel::L128
}
}
impl<const S: usize> KeyDerivationFunction for Argon2<S>
where
Salt<S>: Argon2Compatible + Clone + Zeroize + Send + Sync + 'static,
Params<S>: Default + Clone + Zeroize + Send + Sync + 'static,
{
type Algorithm = Argon2Algorithm;
type Salt = Salt<S>;
fn new() -> Self {
Self {
params: Params::default(),
}
}
fn builder(&self) -> impl KdfOperation<'_, Self::Algorithm>
where
Self: Sized,
{
Argon2Builder {
params: self.params.clone(),
ikm: None,
salt_override: None,
info_override: None,
length_override: None,
}
}
fn generate_salt<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Salt {
let s = Salt::random_with_size(rng, S).expect("Salt generation failed");
debug_assert_eq!(s.as_ref().len(), S, "Salt length mismatch");
s
}
fn derive_key(
&self,
input: &[u8],
salt_override: Option<&[u8]>,
info_override: Option<&[u8]>,
length_override: usize,
) -> Result<Vec<u8>> {
let p = &self.params;
let effective_salt = salt_override.unwrap_or_else(|| p.salt.as_ref());
let effective_length = if length_override > 0 {
length_override
} else {
p.output_len
};
let effective_ad = info_override.or_else(|| p.ad.as_ref().map(|z_vec| z_vec.as_slice()));
let effective_secret = p.secret.as_ref().map(|z_vec| z_vec.as_slice());
let derived_bytes_zeroizing = internal_argon2_core(
input,
effective_salt,
effective_ad,
effective_secret,
p.argon_type,
p.version,
effective_length,
p.memory_cost,
p.time_cost,
p.parallelism,
)?;
Ok(derived_bytes_zeroizing.to_vec())
}
}
#[derive(Clone)]
pub struct Argon2Builder<'a, const S: usize>
where
Salt<S>: Argon2Compatible + Clone + Zeroize + Send + Sync + 'static,
Params<S>: Clone + Zeroize + Send + Sync + 'static,
{
params: Params<S>,
ikm: Option<&'a [u8]>,
salt_override: Option<&'a [u8]>,
info_override: Option<&'a [u8]>,
length_override: Option<usize>,
}
impl<const S: usize> Zeroize for Argon2Builder<'_, S>
where
Salt<S>: Argon2Compatible + Clone + Zeroize + Send + Sync + 'static,
Params<S>: Clone + Zeroize + Send + Sync + 'static,
{
fn zeroize(&mut self) {
self.params.zeroize();
}
}
impl<'a, const S: usize> KdfOperation<'a, Argon2Algorithm> for Argon2Builder<'a, S>
where
Salt<S>: Argon2Compatible + Clone + Zeroize + Send + Sync + 'static,
Params<S>: Default + Clone + Zeroize + Send + Sync + 'static,
{
fn with_ikm(mut self, ikm: &'a [u8]) -> Self {
self.ikm = Some(ikm);
self
}
fn with_salt(mut self, salt: &'a [u8]) -> Self {
self.salt_override = Some(salt);
self
}
fn with_info(mut self, info: &'a [u8]) -> Self {
self.info_override = Some(info);
self
}
fn with_output_length(mut self, len: usize) -> Self {
self.length_override = Some(len);
self
}
fn derive(self) -> Result<Vec<u8>> {
let ikm = self
.ikm
.ok_or_else(|| Error::param("input_key_material", "missing"))?;
let argon_instance_for_derivation = Argon2 {
params: self.params,
};
let final_length = self
.length_override
.unwrap_or(argon_instance_for_derivation.params.output_len);
argon_instance_for_derivation.derive_key(
ikm,
self.salt_override,
self.info_override,
final_length,
)
}
fn derive_array<const N: usize>(self) -> Result<[u8; N]> {
let ikm = self
.ikm
.ok_or_else(|| Error::param("input_key_material", "missing"))?;
let argon_instance_for_derivation = Argon2 {
params: self.params,
};
let vec_result = argon_instance_for_derivation.derive_key(
ikm,
self.salt_override,
self.info_override,
N,
)?;
vec_result
.try_into()
.map_err(|v_err: Vec<u8>| Error::Length {
context: "Argon2 derive_array output conversion",
expected: N,
actual: v_err.len(),
})
}
}
impl<const S: usize> ParamProvider for Argon2<S>
where
Salt<S>: Argon2Compatible,
Params<S>: Default + Clone + Zeroize + Send + Sync + 'static,
{
type Params = Params<S>;
fn with_params(params: Self::Params) -> Self {
Self { params }
}
fn params(&self) -> &Self::Params {
&self.params
}
fn set_params(&mut self, params: Self::Params) {
self.params = params;
}
}
impl<const S: usize> PasswordHashFunction for Argon2<S>
where
Salt<S>: Argon2Compatible + Clone + Zeroize + Send + Sync + 'static,
Params<S>: Default + Clone + Zeroize + Send + Sync + 'static,
{
type Password = SecretBytes<32>;
fn hash_password(&self, password: &Self::Password) -> Result<PasswordHash> {
let hashed_output_zeroizing = self.hash_password(password.as_ref())?;
let type_str = match self.params.argon_type {
Algorithm::Argon2d => "argon2d",
Algorithm::Argon2i => "argon2i",
Algorithm::Argon2id => "argon2id",
};
let mut ph_params_map = BTreeMap::new();
ph_params_map.insert("v".to_string(), self.params.version.to_string());
ph_params_map.insert("m".to_string(), self.params.memory_cost.to_string());
ph_params_map.insert("t".to_string(), self.params.time_cost.to_string());
ph_params_map.insert("p".to_string(), self.params.parallelism.to_string());
if let Some(ad_val) = &self.params.ad {
ph_params_map.insert(
"data".to_string(),
base64::engine::general_purpose::STANDARD_NO_PAD.encode(ad_val),
);
}
Ok(PasswordHash {
algorithm: type_str.to_string(),
params: ph_params_map,
salt: Zeroizing::new(self.params.salt.as_ref().to_vec()),
hash: hashed_output_zeroizing,
})
}
fn verify(&self, password: &Self::Password, stored_hash: &PasswordHash) -> Result<bool> {
let argon_variant_from_hash = match stored_hash.algorithm.as_str() {
"argon2d" => Algorithm::Argon2d,
"argon2i" => Algorithm::Argon2i,
"argon2id" => Algorithm::Argon2id,
_ => {
return Err(Error::param(
"algorithm",
"Unsupported algorithm in stored hash",
))
}
};
let version = stored_hash.param_as_u32("v")?;
if version != ARGON2_VERSION_1_3 {
return Err(Error::param("version", "Version mismatch in stored hash"));
}
let memory_cost = stored_hash.param_as_u32("m")?;
let time_cost = stored_hash.param_as_u32("t")?;
let parallelism = stored_hash.param_as_u32("p")?;
let ad_from_params: Option<Zeroizing<Vec<u8>>> = stored_hash
.params
.get("data")
.map(|s| {
base64::engine::general_purpose::STANDARD_NO_PAD
.decode(s)
.map(Zeroizing::new)
})
.transpose()
.map_err(|_| {
Error::param(
"data",
"Invalid AD encoding in stored hash (expected Base64)",
)
})?;
let secret_for_verification = self.params.secret.as_ref().map(|z_vec| z_vec.as_slice());
let computed_hash_zeroizing = internal_argon2_core(
password.as_ref(),
&stored_hash.salt,
ad_from_params.as_ref().map(|z| z.as_slice()),
secret_for_verification,
argon_variant_from_hash,
version,
stored_hash.hash.len(),
memory_cost,
time_cost,
parallelism,
)?;
Ok(crate::kdf::common::constant_time_eq(
&computed_hash_zeroizing,
&stored_hash.hash,
))
}
fn benchmark(&self) -> Duration {
Duration::from_millis(150) }
fn recommended_params(_target_duration: Duration) -> Self::Params {
Params::default() }
}
#[cfg(test)]
mod tests;