dcrypt_algorithms/kdf/argon2/
mod.rs

1//! Argon2 password hashing function with proper error handling
2//!
3//! This module provides an implementation of the Argon2 password hashing function,
4//! which is designed to be resilient against various attacks including
5//! time-memory trade-offs and side-channel attacks.
6
7#[cfg(not(feature = "std"))]
8use alloc::vec::Vec;
9
10use crate::error::{validate, Error, Result};
11// KdfAlgorithm trait, and PasswordHash related types
12use super::params::ParamProvider; // For ParamProvider trait
13use super::{KdfAlgorithm, KdfOperation}; // KdfAlgorithm is the trait, Argon2Algorithm is our marker
14use super::{KeyDerivationFunction, PasswordHash, PasswordHashFunction, SecurityLevel};
15use crate::hash::blake2::Blake2b;
16use crate::hash::HashFunction; // Applied Edit 3
17use crate::types::{Salt, SecretBytes};
18use crate::Argon2Compatible;
19use base64::Engine;
20use core::convert::TryInto;
21use rand::{CryptoRng, RngCore};
22use std::collections::BTreeMap;
23use std::time::Duration;
24use zeroize::{Zeroize, Zeroizing};
25
26// Argon2 specific constants
27const ARGON2_VERSION_1_3: u32 = 0x13;
28const ARGON2_BLOCK_SIZE: usize = 1024;
29const ARGON2_QWORDS_IN_BLOCK: usize = ARGON2_BLOCK_SIZE / 8; // 128 qwords
30const ARGON2_SYNC_POINTS: u32 = 4; // Number of synchronization points (slices) per pass per lane
31
32const ARGON2_PREHASH_SEED_LENGTH: usize = 72;
33
34/// Creates a Blake2b instance configured for Argon2's H₀ function (pre-hash)
35/// according to RFC 9106 Table 3.
36///
37/// Per RFC 9106 Table 3, H₀ parameters are:
38/// - digest_length = 64
39/// - key_length = 0
40/// - fanout = 1 (tree hashing is disabled)
41/// - depth = 1
42/// - inner_length = 0
43fn create_blake2b_for_h0() -> Blake2b {
44    // Build the parameter block according to RFC 9106 Table 3
45    let mut param = [0u8; 64];
46    param[0] = 64; // digest_length = 64
47    param[1] = 0; // key_length = 0
48    param[2] = 1; // fanout = 1
49    param[3] = 1; // depth = 1
50                  // leaf_length (4..7) = 0
51                  // node_offset (8..15) = 0
52    param[16] = 0; // node_depth = 0
53    param[17] = 0; // inner_length = 0 (critical for H₀)
54
55    // Initialize Blake2b with these parameters
56    Blake2b::with_parameter_block(param, 64)
57}
58
59/// A simple Hʷ(x) = BLAKE2b(digest_length = x, tree disabled) for Argon2's H′ function
60///
61/// Per RFC 9106 § 3.2, H^x() should be plain BLAKE2b with tree-hashing disabled.
62/// - digest_length = output_len
63/// - key_length = 0
64/// - fanout = 1 (tree hashing is disabled)
65/// - depth = 1
66/// - inner_length = 0 (disables tree hashing)
67///
68/// # Arguments
69/// * `digest_len` - The desired output length in bytes
70fn blake2b_params(digest_len: u8) -> Blake2b {
71    let mut param = [0u8; 64];
72    param[0] = digest_len; // digest_length = x
73    param[1] = 0; // key_length = 0
74    param[2] = 1; // fanout = 1
75    param[3] = 1; // depth = 1
76                  // leaf_length (4..7) = 0
77                  // node_offset (8..15) = 0
78    param[16] = 0; // node_depth = 0
79    param[17] = 0; // inner_length = 0 disables tree hashing
80
81    Blake2b::with_parameter_block(param, digest_len as usize)
82}
83
84// Applied Edit 5: New Block struct and type alias
85/// Block structure used by Argon2 algorithm
86#[derive(Clone)]
87struct Block([u8; ARGON2_BLOCK_SIZE]);
88
89impl Zeroize for Block {
90    fn zeroize(&mut self) {
91        self.0.iter_mut().for_each(|b| *b = 0);
92    }
93}
94
95/// Memory block type alias
96type MemBlock = Block; // replace the old alias
97
98// ─── BLAMKA round + G mixing ──────────────────────────────────────────
99
100#[inline(always)]
101fn mul_alpha(x: u64, y: u64) -> u64 {
102    2u64.wrapping_mul(x & 0xFFFF_FFFF)
103        .wrapping_mul(y & 0xFFFF_FFFF)
104}
105
106#[inline(always)]
107fn blamka(a: u64, b: u64, c: u64, d: u64) -> (u64, u64, u64, u64) {
108    let mut a = a;
109    let mut b = b;
110    let mut c = c;
111    let mut d = d;
112
113    a = a.wrapping_add(b).wrapping_add(mul_alpha(a, b));
114    d ^= a;
115    d = d.rotate_right(32);
116    c = c.wrapping_add(d).wrapping_add(mul_alpha(c, d));
117    b ^= c;
118    b = b.rotate_right(24);
119    a = a.wrapping_add(b).wrapping_add(mul_alpha(a, b));
120    d ^= a;
121    d = d.rotate_right(16);
122    c = c.wrapping_add(d).wrapping_add(mul_alpha(c, d));
123    b ^= c;
124    b = b.rotate_right(63);
125
126    (a, b, c, d)
127}
128
129#[inline(always)]
130fn blamka_round(state: &mut [u64; 16]) {
131    // Column step
132    for &(i, j, k, l) in &[(0, 4, 8, 12), (1, 5, 9, 13), (2, 6, 10, 14), (3, 7, 11, 15)] {
133        let (na, nb, nc, nd) = blamka(state[i], state[j], state[k], state[l]);
134        state[i] = na;
135        state[j] = nb;
136        state[k] = nc;
137        state[l] = nd;
138    }
139    // Diagonal step
140    for &(i, j, k, l) in &[(0, 5, 10, 15), (1, 6, 11, 12), (2, 7, 8, 13), (3, 4, 9, 14)] {
141        let (na, nb, nc, nd) = blamka(state[i], state[j], state[k], state[l]);
142        state[i] = na;
143        state[j] = nb;
144        state[k] = nc;
145        state[l] = nd;
146    }
147}
148
149/// The Argon2 G mixing function (exactly one blamka_round per row and column,
150/// plus feed-forward) as specified in RFC 9106 § 3.5/fig. 15.
151fn argon2_g(
152    x: &[u64; ARGON2_QWORDS_IN_BLOCK],
153    y: &[u64; ARGON2_QWORDS_IN_BLOCK],
154) -> [u64; ARGON2_QWORDS_IN_BLOCK] {
155    // 1) R = X ⊕ Y
156    let mut r = [0u64; ARGON2_QWORDS_IN_BLOCK];
157    for i in 0..ARGON2_QWORDS_IN_BLOCK {
158        r[i] = x[i] ^ y[i];
159    }
160
161    // 2) Row rounds (8 rows of 16 qwords each)
162    for chunk in r.chunks_exact_mut(16) {
163        let row: &mut [u64; 16] = chunk.try_into().unwrap();
164        blamka_round(row);
165    }
166
167    // 3) Column rounds - correct 16-word selection
168    for reg in 0..8 {
169        // eight 128-bit registers per row
170        let mut tmp = [0u64; 16];
171        for row in 0..8 {
172            // RFC 9106 Fig. 15 – R is 8×8 of 16-byte registers,
173            // so successive rows are 16 q-words apart and successive
174            // columns are *1* q-word apart.
175            let base = row * 16 + reg * 2; // low word of register ( *2 because each register = 2 q-words )
176            tmp[2 * row] = r[base];
177            tmp[2 * row + 1] = r[base + 1];
178        }
179
180        blamka_round(&mut tmp); // one BLAKE2 round on 16 q-words
181
182        for row in 0..8 {
183            let base = row * 16 + reg * 2;
184            r[base] = tmp[2 * row];
185            r[base + 1] = tmp[2 * row + 1];
186        }
187    }
188
189    // 4) Feed-forward: R_i = R_i ⊕ X_i ⊕ Y_i
190    for i in 0..ARGON2_QWORDS_IN_BLOCK {
191        r[i] ^= x[i] ^ y[i];
192    }
193
194    r
195}
196
197/// Argon2 variant types
198#[derive(Debug, Clone, Copy, PartialEq, Eq, Zeroize)]
199pub enum Algorithm {
200    /// Argon2d variant - uses data-dependent memory access, which offers
201    /// the highest resistance against GPU cracking attacks but is vulnerable to side-channel attacks
202    Argon2d = 0,
203    /// Argon2i variant - uses data-independent memory access, which offers
204    /// better protection against side-channel attacks but less resistance against GPU attacks
205    Argon2i = 1,
206    /// Argon2id variant - hybrid approach that combines Argon2i and Argon2d
207    /// for a good balance between resistance to both side-channel and GPU attacks
208    Argon2id = 2,
209}
210
211/// Parameters for the Argon2 password hashing function
212///
213/// Argon2 is configurable with several parameters that affect its memory and time cost.
214/// This struct encapsulates all the parameters needed to control the behavior of the algorithm.
215#[derive(Clone, Zeroize)] // MODIFIED: Removed Default from derive
216pub struct Params<const S: usize>
217where
218    Salt<S>: Argon2Compatible,
219{
220    // MODIFIED: Removed Salt<S>: Default bound
221    /// The Argon2 variant to use (Argon2d, Argon2i, or Argon2id)
222    pub argon_type: Algorithm,
223    /// The Argon2 version (should be 0x13 for v1.3)
224    pub version: u32,
225    /// Memory usage in kibibytes (KiB)
226    pub memory_cost: u32, // KiB
227    /// Number of iterations (time cost parameter)
228    pub time_cost: u32, // Iterations
229    /// Degree of parallelism (number of threads)
230    pub parallelism: u32, // Lanes
231    /// Length of the output hash in bytes
232    pub output_len: usize,
233    /// Salt value for this hash operation
234    pub salt: Salt<S>,
235    /// Optional associated data that will be included in the hash calculation
236    pub ad: Option<Zeroizing<Vec<u8>>>,
237    /// Optional secret value that can be used as an additional input
238    pub secret: Option<Zeroizing<Vec<u8>>>,
239}
240
241// Instead of relying on derive(Default), we'll implement it explicitly
242// to ensure all the fields are initialized properly
243impl<const S: usize> Default for Params<S>
244where
245    Salt<S>: Argon2Compatible,
246{
247    // MODIFIED: Removed Salt<S>: Default bound
248    fn default() -> Self {
249        Params {
250            argon_type: Algorithm::Argon2id,
251            version: ARGON2_VERSION_1_3,
252            memory_cost: 19 * 1024,
253            time_cost: 2,
254            parallelism: 1,
255            output_len: 32,
256            salt: Salt::<S>::zeroed(), // MODIFIED: Use Salt::zeroed()
257            ad: None,
258            secret: None,
259        }
260    }
261}
262
263/// Argon2 password hashing implementation
264///
265/// This struct provides methods for password hashing using the Argon2 algorithm,
266/// which is designed to be resistant against various attacks including GPU cracking
267/// and side-channel attacks.
268#[derive(Clone)]
269pub struct Argon2<const S: usize>
270where
271    Salt<S>: Argon2Compatible,
272{
273    /// Configuration parameters for the Argon2 instance
274    params: Params<S>,
275}
276
277const MAX_PWD_LEN: u32 = 0xFFFFFFFF;
278const MIN_SALT_LEN: usize = 8;
279const MAX_SALT_LEN: u32 = 0xFFFFFFFF;
280const MAX_AD_LEN: u32 = 0xFFFFFFFF;
281const MAX_SECRET_LEN: u32 = 0xFFFFFFFF;
282
283const MIN_LANES: u32 = 1;
284const MAX_LANES: u32 = 0xFFFFFF;
285const MIN_OUT_LEN: usize = 4;
286const MAX_OUT_LEN: u32 = 0xFFFFFFFF;
287const MIN_TIME_COST: u32 = 1;
288const MIN_ABS_MEMORY_COST_KIB: u32 = 8;
289
290impl<const S: usize> Argon2<S>
291where
292    Salt<S>: Argon2Compatible,
293{
294    /// Creates a new Argon2 instance with the specified parameters
295    pub fn new_with_params(params: Params<S>) -> Self {
296        Self { params }
297    }
298
299    /// Hashes a password using the configured Argon2 parameters
300    ///
301    /// # Arguments
302    /// * `password` - The password to hash
303    ///
304    /// # Returns
305    /// * A `Result` containing the hashed password as a zeroizing byte vector
306    pub fn hash_password(&self, password: &[u8]) -> Result<Zeroizing<Vec<u8>>> {
307        let p = &self.params;
308        let salt_bytes = p.salt.as_ref();
309        let ad_bytes = p.ad.as_ref().map(|z_vec| z_vec.as_slice());
310        let secret_bytes = p.secret.as_ref().map(|z_vec| z_vec.as_slice());
311
312        internal_argon2_core(
313            password,
314            salt_bytes,
315            ad_bytes,
316            secret_bytes,
317            p.argon_type,
318            p.version,
319            p.output_len,
320            p.memory_cost,
321            p.time_cost,
322            p.parallelism,
323        )
324    }
325}
326
327/// Fills an address block for a segment in the data-independent addressing mode
328///
329/// This function computes the address values needed for data-independent addressing
330/// in Argon2i and Argon2id variants.
331#[allow(clippy::too_many_arguments)]
332fn fill_address_block_for_segment(
333    address_qwords: &mut [u64; ARGON2_QWORDS_IN_BLOCK],
334    pass: u32,
335    lane: u32,
336    slice: u32,
337    m_prime: u32,
338    t_cost: u32,
339    alg: Algorithm,
340    counter: u64, // New parameter for the block counter!
341    buf: &mut Block,
342) -> Result<()> {
343    // 1) zero the 1024-byte buffer
344    buf.zeroize();
345
346    // 2) write the first 7 qwords: pass, lane, slice, m′, t, y, counter
347    let mut off = 0;
348    buf.0[off..off + 8].copy_from_slice(&(pass as u64).to_le_bytes());
349    off += 8;
350    buf.0[off..off + 8].copy_from_slice(&(lane as u64).to_le_bytes());
351    off += 8;
352    buf.0[off..off + 8].copy_from_slice(&(slice as u64).to_le_bytes());
353    off += 8;
354    buf.0[off..off + 8].copy_from_slice(&(m_prime as u64).to_le_bytes());
355    off += 8;
356    buf.0[off..off + 8].copy_from_slice(&(t_cost as u64).to_le_bytes());
357    off += 8;
358
359    let y = match alg {
360        Algorithm::Argon2i => 1,
361        Algorithm::Argon2id => 2,
362        _ => 0,
363    };
364    buf.0[off..off + 8].copy_from_slice(&(y as u64).to_le_bytes());
365    off += 8;
366
367    // Set the counter (monotonically increasing for each block in the segment)
368    buf.0[off..off + 8].copy_from_slice(&counter.to_le_bytes());
369    // No need to bump off further, the rest is zero
370
371    // unpack into u64 array for G function - FIXED: Using iterator
372    let mut input_q = [0u64; ARGON2_QWORDS_IN_BLOCK];
373    for (i, chunk) in buf
374        .0
375        .chunks_exact(8)
376        .enumerate()
377        .take(ARGON2_QWORDS_IN_BLOCK)
378    {
379        input_q[i] = u64::from_le_bytes(chunk.try_into().unwrap());
380    }
381
382    // Apply G twice, per RFC 9106 § 3.3
383    let zero = [0u64; ARGON2_QWORDS_IN_BLOCK];
384    let block0 = argon2_g(&zero, &input_q);
385    let block1 = argon2_g(&zero, &block0);
386
387    // Copy result to output array
388    address_qwords.copy_from_slice(&block1);
389    Ok(())
390}
391
392/// Internal implementation of the Argon2 algorithm
393///
394/// This function handles the core Argon2 logic with parameter validation and
395/// memory management for the hashing process.
396#[allow(clippy::too_many_arguments)]
397fn internal_argon2_core(
398    password: &[u8],
399    salt: &[u8],
400    ad: Option<&[u8]>,
401    secret: Option<&[u8]>,
402    argon_type: Algorithm,
403    version: u32,
404    output_len: usize,
405    memory_cost_kib: u32,
406    time_cost_iterations: u32,
407    parallelism_lanes: u32,
408) -> Result<Zeroizing<Vec<u8>>> {
409    validate::parameter(
410        output_len >= MIN_OUT_LEN,
411        "output_len",
412        "value is below minimum",
413    )?;
414    validate::parameter(
415        output_len <= MAX_OUT_LEN as usize,
416        "output_len",
417        "value is above maximum",
418    )?;
419    // FIXED: Removed always-true comparisons
420    validate::parameter(
421        password.len() <= MAX_PWD_LEN as usize,
422        "password_len",
423        "value is above maximum",
424    )?;
425    validate::parameter(
426        salt.len() >= MIN_SALT_LEN,
427        "salt_len",
428        "value is below minimum",
429    )?;
430    validate::parameter(
431        salt.len() <= MAX_SALT_LEN as usize,
432        "salt_len",
433        "value is above maximum",
434    )?;
435
436    if let Some(ad_data) = ad {
437        validate::parameter(
438            ad_data.len() <= MAX_AD_LEN as usize,
439            "ad_len",
440            "value is above maximum",
441        )?;
442    }
443    if let Some(secret_data) = secret {
444        validate::parameter(
445            secret_data.len() <= MAX_SECRET_LEN as usize,
446            "secret_len",
447            "value is above maximum",
448        )?;
449    }
450
451    validate::parameter(
452        time_cost_iterations >= MIN_TIME_COST,
453        "time_cost",
454        "value is below minimum",
455    )?;
456    // FIXED: Removed always-true comparison with MAX_TIME_COST
457    validate::parameter(
458        parallelism_lanes >= MIN_LANES,
459        "parallelism_lanes",
460        "value is below minimum",
461    )?;
462    validate::parameter(
463        parallelism_lanes <= MAX_LANES,
464        "parallelism_lanes",
465        "value is above maximum",
466    )?;
467
468    let effective_min_mem_kib = 8 * parallelism_lanes;
469    validate::parameter(
470        memory_cost_kib >= MIN_ABS_MEMORY_COST_KIB,
471        "memory_cost_kib (absolute)",
472        "value is below minimum",
473    )?;
474    validate::parameter(
475        memory_cost_kib >= effective_min_mem_kib,
476        "memory_cost_kib (vs lanes)",
477        "value is below minimum",
478    )?;
479
480    if version != ARGON2_VERSION_1_3 {
481        return Err(Error::param("version", "unsupported Argon2 version"));
482    }
483
484    let mut h0_buffer_cap = ARGON2_PREHASH_SEED_LENGTH;
485    h0_buffer_cap = h0_buffer_cap.max(
486        4 * 7 + password.len() + salt.len() + secret.unwrap_or(&[]).len() + ad.unwrap_or(&[]).len(),
487    );
488    let mut h0_buffer = Zeroizing::new(Vec::with_capacity(h0_buffer_cap));
489
490    h0_buffer.extend_from_slice(&parallelism_lanes.to_le_bytes());
491    h0_buffer.extend_from_slice(&(output_len as u32).to_le_bytes());
492    h0_buffer.extend_from_slice(&memory_cost_kib.to_le_bytes());
493    h0_buffer.extend_from_slice(&time_cost_iterations.to_le_bytes());
494    h0_buffer.extend_from_slice(&version.to_le_bytes());
495    h0_buffer.extend_from_slice(&(argon_type as u32).to_le_bytes());
496
497    h0_buffer.extend_from_slice(&(password.len() as u32).to_le_bytes());
498    h0_buffer.extend_from_slice(password);
499    h0_buffer.extend_from_slice(&(salt.len() as u32).to_le_bytes());
500    h0_buffer.extend_from_slice(salt);
501
502    let secret_data = secret.unwrap_or(&[]);
503    h0_buffer.extend_from_slice(&(secret_data.len() as u32).to_le_bytes());
504    h0_buffer.extend_from_slice(secret_data);
505
506    let ad_data = ad.unwrap_or(&[]);
507    h0_buffer.extend_from_slice(&(ad_data.len() as u32).to_le_bytes());
508    h0_buffer.extend_from_slice(ad_data);
509
510    // Calculate H0 using standard Blake2b-512 with inner_length=0 as per RFC 9106 Table 3
511    // IMPORTANT: H0 uses inner_length=0, unlike H' which uses inner_length=64
512    // RFC 9106 Table 3 specifies:
513    // - H0: digest_length=64, inner_length=0, fanout=1 (tree hashing is disabled)
514    // - H': digest_length=output_len, inner_length=64, fanout=1
515    let mut h0_hasher = create_blake2b_for_h0();
516    h0_hasher.update(&h0_buffer)?;
517    let h0_digest = h0_hasher.finalize()?;
518    let mut h0 = Zeroizing::new(h0_digest.as_ref().to_vec());
519    h0_buffer.zeroize();
520
521    // Memory allocation according to RFC 9106
522    // m' = floor(m / (p*4)) * (p*4)
523    let num_memory_blocks_total = (memory_cost_kib / (parallelism_lanes * ARGON2_SYNC_POINTS))
524        * (parallelism_lanes * ARGON2_SYNC_POINTS);
525
526    let lane_length = num_memory_blocks_total / parallelism_lanes;
527
528    if lane_length == 0 {
529        return Err(Error::param(
530            "memory_cost_kib",
531            "Effective lane length is zero after rounding.",
532        ));
533    }
534    let segment_length = lane_length / ARGON2_SYNC_POINTS;
535
536    let mut memory_matrix: Vec<MemBlock> =
537        vec![Block([0u8; ARGON2_BLOCK_SIZE]); num_memory_blocks_total as usize];
538
539    for lane_idx in 0..parallelism_lanes {
540        let mut block_seed = Zeroizing::new(Vec::with_capacity(h0.len() + 8));
541
542        block_seed.extend_from_slice(&h0);
543        block_seed.extend_from_slice(&0u32.to_le_bytes());
544        block_seed.extend_from_slice(&lane_idx.to_le_bytes());
545        let block0_val = h_prime_variable_output(&block_seed, ARGON2_BLOCK_SIZE)?;
546        memory_matrix[(lane_idx * lane_length) as usize]
547            .0
548            .copy_from_slice(&block0_val);
549        block_seed.clear();
550
551        block_seed.extend_from_slice(&h0);
552        block_seed.extend_from_slice(&1u32.to_le_bytes());
553        block_seed.extend_from_slice(&lane_idx.to_le_bytes());
554        let block1_val = h_prime_variable_output(&block_seed, ARGON2_BLOCK_SIZE)?;
555        memory_matrix[(lane_idx * lane_length + 1) as usize]
556            .0
557            .copy_from_slice(&block1_val);
558    }
559    h0.zeroize();
560
561    // This array will hold the generated address block for data-independent addressing
562    let mut address_block_qwords = [0u64; ARGON2_QWORDS_IN_BLOCK];
563    let mut input_block_buffer = Block([0u8; ARGON2_BLOCK_SIZE]);
564
565    for pass_idx in 0..time_cost_iterations {
566        for slice_idx in 0..ARGON2_SYNC_POINTS {
567            for lane_idx in 0..parallelism_lanes {
568                let data_independent_addressing_for_segment = match argon_type {
569                    Algorithm::Argon2i => true,
570                    Algorithm::Argon2d => false,
571                    Algorithm::Argon2id => pass_idx == 0 && slice_idx < (ARGON2_SYNC_POINTS / 2),
572                };
573
574                let first_block_in_segment_offset = if pass_idx == 0 && slice_idx == 0 {
575                    2
576                } else {
577                    0
578                };
579
580                // FIXED: Address block counter moved outside the inner loop and initialized to 0
581                let mut address_block_counter = 0u64;
582
583                for block_in_segment_idx in first_block_in_segment_offset..segment_length {
584                    let current_block_offset_in_lane =
585                        slice_idx * segment_length + block_in_segment_idx;
586                    let current_block_abs_idx =
587                        (lane_idx * lane_length + current_block_offset_in_lane) as usize;
588
589                    let prev_block_offset_in_lane = if current_block_offset_in_lane == 0 {
590                        lane_length - 1
591                    } else {
592                        current_block_offset_in_lane - 1
593                    };
594                    let prev_block_abs_idx =
595                        (lane_idx * lane_length + prev_block_offset_in_lane) as usize;
596
597                    // Get pseudo-random values for reference block selection
598                    let pseudo_rand: u64 = if data_independent_addressing_for_segment {
599                        // need_new == true  ➜ (re)generate a 1024-byte address-block
600                        //   • start of the segment  (block_in_segment_idx == 0)
601                        //   • every 128th block thereafter
602                        let need_new = block_in_segment_idx == 0
603                            || block_in_segment_idx as usize % ARGON2_QWORDS_IN_BLOCK == 0;
604
605                        if need_new {
606                            // FIXED: Increment the counter by 1 each time instead of using division
607                            address_block_counter += 1;
608
609                            // Generate a fresh address block
610                            fill_address_block_for_segment(
611                                &mut address_block_qwords,
612                                pass_idx,
613                                lane_idx,
614                                slice_idx,
615                                num_memory_blocks_total,
616                                time_cost_iterations,
617                                argon_type,
618                                address_block_counter,
619                                &mut input_block_buffer,
620                            )?;
621                        }
622
623                        // J_i ← address_block[i mod 128]
624                        address_block_qwords[block_in_segment_idx as usize % ARGON2_QWORDS_IN_BLOCK]
625                    } else {
626                        // For data-dependent addressing, get the first 8 bytes of the previous block
627                        let mut buf = [0u8; 8];
628                        buf.copy_from_slice(&memory_matrix[prev_block_abs_idx].0[0..8]);
629                        u64::from_le_bytes(buf)
630                    };
631
632                    // --- get the two 32-bit words out of the 64-bit pseudo-random value ---
633                    let j1 = (pseudo_rand & 0xFFFF_FFFF) as u32; // LOW  32 bits  = "first" bits (little-endian)
634                    let j2 = (pseudo_rand >> 32) as u32; // HIGH 32 bits  = "second" bits
635
636                    // --- choose the reference lane (RFC 9106 §3.4.1 step 4) ---------------
637                    let ref_lane_val = if pass_idx == 0 && slice_idx == 0 {
638                        lane_idx // special case in the very first slice
639                    } else {
640                        j2 % parallelism_lanes // ***use   J₂   here***
641                    };
642
643                    // Determine reference position within the lane
644                    let (ref_idx_in_lane, _area_size) = index_alpha(
645                        pass_idx,
646                        slice_idx,
647                        block_in_segment_idx,
648                        lane_length,
649                        segment_length,
650                        parallelism_lanes,
651                        lane_idx,
652                        ref_lane_val,
653                        j1,
654                    );
655                    let ref_block_abs_idx = (ref_lane_val * lane_length + ref_idx_in_lane) as usize;
656
657                    let prev_block_data = &memory_matrix[prev_block_abs_idx].0;
658                    let ref_block_data = &memory_matrix[ref_block_abs_idx].0;
659
660                    // Get the current block's data (needed for pass > 0) - clone it to avoid borrowing issues
661                    let cur_block_data = if pass_idx > 0 {
662                        let mut data = [0u8; ARGON2_BLOCK_SIZE];
663                        data.copy_from_slice(&memory_matrix[current_block_abs_idx].0);
664                        data
665                    } else {
666                        [0u8; ARGON2_BLOCK_SIZE] // Dummy array for pass 0, won't be used
667                    };
668
669                    // Parse the blocks into 128-u64 arrays with proper XOR for pass > 0
670                    let mut xv = [0u64; ARGON2_QWORDS_IN_BLOCK];
671                    let mut yv = [0u64; ARGON2_QWORDS_IN_BLOCK];
672
673                    // FIXED: Using iterator instead of index loop
674                    for (i, chunk) in prev_block_data
675                        .chunks_exact(8)
676                        .enumerate()
677                        .take(ARGON2_QWORDS_IN_BLOCK)
678                    {
679                        xv[i] = u64::from_le_bytes(chunk.try_into().unwrap());
680                    }
681
682                    // FIXED: Using iterator instead of index loop
683                    for (i, chunk) in ref_block_data
684                        .chunks_exact(8)
685                        .enumerate()
686                        .take(ARGON2_QWORDS_IN_BLOCK)
687                    {
688                        yv[i] = u64::from_le_bytes(chunk.try_into().unwrap());
689                    }
690
691                    // Mix with the true Argon2 G function
692                    let gq = argon2_g(&xv, &yv);
693
694                    // Serialize back into bytes - FIXED: Using iterator
695                    let mut gbytes = [0u8; ARGON2_BLOCK_SIZE];
696                    for (i, &qword) in gq.iter().enumerate().take(ARGON2_QWORDS_IN_BLOCK) {
697                        let start = i * 8;
698                        gbytes[start..start + 8].copy_from_slice(&qword.to_le_bytes());
699                    }
700
701                    // Final update matches RFC spec exactly
702                    if pass_idx == 0 {
703                        memory_matrix[current_block_abs_idx]
704                            .0
705                            .copy_from_slice(&gbytes);
706                    } else {
707                        for k in 0..ARGON2_BLOCK_SIZE {
708                            memory_matrix[current_block_abs_idx].0[k] =
709                                gbytes[k] ^ cur_block_data[k];
710                        }
711                    }
712                }
713            }
714        }
715    }
716
717    let mut final_block_xor_sum_vec =
718        Zeroizing::new(memory_matrix[(lane_length - 1) as usize].0.to_vec());
719
720    for lane_idx in 1..parallelism_lanes {
721        let last_block_in_lane_idx = (lane_idx * lane_length + (lane_length - 1)) as usize;
722        for k in 0..ARGON2_BLOCK_SIZE {
723            final_block_xor_sum_vec[k] ^= memory_matrix[last_block_in_lane_idx].0[k];
724        }
725    }
726
727    let final_hash_vec = h_prime_variable_output(&final_block_xor_sum_vec, output_len)?;
728    Ok(Zeroizing::new(final_hash_vec))
729}
730
731// RFC 9106 §3.3 "Variable-length hash function H′":
732fn h_prime_variable_output(data: &[u8], t: usize) -> Result<Vec<u8>> {
733    // T = 0 ⇒ empty
734    if t == 0 {
735        return Ok(vec![]);
736    }
737
738    // For T ≤ 64, just one BLAKE2b(T) call
739    if t <= 64 {
740        let mut h = blake2b_params(t as u8);
741        h.update(&u32::to_le_bytes(t as u32))?;
742        h.update(data)?;
743        let v = h.finalize()?.as_ref().to_vec();
744        return Ok(v);
745    }
746
747    // T > 64: compute r = ⌈T/32⌉ - 2, produce r of 32 B each + one of (T - 32r)
748    // FIXED: Using div_ceil
749    let ceil_div = |x: usize, y: usize| x.div_ceil(y);
750    let r = ceil_div(t, 32) - 2;
751
752    let mut out = Vec::with_capacity(t);
753    // --- V₁:
754    let mut h = blake2b_params(64);
755    h.update(&u32::to_le_bytes(t as u32))?;
756    h.update(data)?;
757    let mut prev = h.finalize()?.as_ref().to_vec();
758    out.extend_from_slice(&prev[..32]);
759
760    // --- V₂..Vᵣ:
761    for _ in 1..r {
762        let mut h = blake2b_params(64);
763        h.update(&prev)?;
764        let v = h.finalize()?.as_ref().to_vec();
765        out.extend_from_slice(&v[..32]);
766        prev = v;
767    }
768
769    // --- Vᵣ₊₁ (final):
770    let final_len = t - 32 * r;
771    let mut h = blake2b_params(final_len as u8);
772    h.update(&prev)?;
773    let v = h.finalize()?.as_ref().to_vec();
774    out.extend_from_slice(&v);
775
776    Ok(out)
777}
778
779/// Calculates the position of a reference block in the memory matrix
780///
781/// Exact transcription of the algorithm used in the reference C
782/// implementation (src/core.c:index_alpha) that matches all RFC 9106
783/// test-vectors.
784#[allow(clippy::too_many_arguments)]
785fn index_alpha(
786    pass_idx: u32,
787    slice_idx: u32,
788    block_in_segment_idx: u32,
789    lane_length: u32,
790    segment_length: u32,
791    _parallelism_lanes: u32,
792    current_lane_idx: u32,
793    ref_lane_val: u32,
794    j1: u32,
795) -> (u32, u32) {
796    // ── size of the candidate window |W| ───────────────────────────────
797    let mut reference_area_size: u32;
798
799    if pass_idx == 0 {
800        if slice_idx == 0 {
801            // first slice of first pass
802            reference_area_size = block_in_segment_idx.saturating_sub(1);
803        } else if ref_lane_val == current_lane_idx {
804            // same lane
805            reference_area_size = slice_idx * segment_length + block_in_segment_idx;
806            reference_area_size = reference_area_size.saturating_sub(1); // exclude current block
807        } else {
808            // different lane
809            reference_area_size = slice_idx * segment_length;
810            if block_in_segment_idx == 0 {
811                reference_area_size = reference_area_size.saturating_sub(1);
812            }
813        }
814    } else {
815        // pass > 0
816        if ref_lane_val == current_lane_idx {
817            reference_area_size = lane_length - segment_length + block_in_segment_idx;
818            reference_area_size = reference_area_size.saturating_sub(1);
819        } else {
820            reference_area_size = lane_length - segment_length;
821            if block_in_segment_idx == 0 {
822                reference_area_size = reference_area_size.saturating_sub(1);
823            }
824        }
825    }
826
827    // ── map J₁ to a relative position inside |W| ───────────────────────
828    let mut phi = j1 as u64;
829    phi = (phi * phi) >> 32; // J₁² / 2³²
830    let relative_position = if reference_area_size == 0 {
831        0
832    } else {
833        let rhs = ((reference_area_size as u64) * phi) >> 32;
834        (reference_area_size as u64)
835            .saturating_sub(1)
836            .saturating_sub(rhs)
837    } as u32;
838
839    // ── starting offset (same for all lanes on pass > 0) ───────────────
840    let start_position_offset = if pass_idx == 0 || slice_idx == ARGON2_SYNC_POINTS - 1 {
841        0
842    } else {
843        (slice_idx + 1) * segment_length
844    };
845
846    let ref_idx_in_lane = (start_position_offset + relative_position) % lane_length;
847
848    (ref_idx_in_lane, reference_area_size)
849}
850
851/// Argon2 algorithm identifier for use with the KDF trait system
852///
853/// This enum serves as a type marker for the Argon2 algorithm when used with
854/// the generic key derivation function interfaces.
855pub enum Argon2Algorithm {}
856impl KdfAlgorithm for Argon2Algorithm {
857    const MIN_SALT_SIZE: usize = MIN_SALT_LEN;
858    const DEFAULT_OUTPUT_SIZE: usize = 32;
859    const ALGORITHM_ID: &'static str = "argon2";
860
861    fn name() -> String {
862        "Argon2".to_string()
863    }
864    fn security_level() -> SecurityLevel {
865        SecurityLevel::L128
866    }
867}
868
869impl<const S: usize> KeyDerivationFunction for Argon2<S>
870where
871    Salt<S>: Argon2Compatible + Clone + Zeroize + Send + Sync + 'static,
872    Params<S>: Default + Clone + Zeroize + Send + Sync + 'static,
873{
874    type Algorithm = Argon2Algorithm;
875    type Salt = Salt<S>;
876
877    fn new() -> Self {
878        Self {
879            params: Params::default(),
880        }
881    }
882
883    // FIXED: Elided lifetime
884    fn builder(&self) -> impl KdfOperation<'_, Self::Algorithm>
885    where
886        Self: Sized,
887    {
888        Argon2Builder {
889            params: self.params.clone(),
890            ikm: None,
891            salt_override: None,
892            info_override: None,
893            length_override: None,
894        }
895    }
896
897    fn generate_salt<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Salt {
898        let s = Salt::random_with_size(rng, S).expect("Salt generation failed");
899        debug_assert_eq!(s.as_ref().len(), S, "Salt length mismatch");
900        s
901    }
902
903    fn derive_key(
904        &self,
905        input: &[u8],
906        salt_override: Option<&[u8]>,
907        info_override: Option<&[u8]>,
908        length_override: usize,
909    ) -> Result<Vec<u8>> {
910        let p = &self.params;
911        let effective_salt = salt_override.unwrap_or_else(|| p.salt.as_ref());
912        let effective_length = if length_override > 0 {
913            length_override
914        } else {
915            p.output_len
916        };
917        let effective_ad = info_override.or_else(|| p.ad.as_ref().map(|z_vec| z_vec.as_slice()));
918        let effective_secret = p.secret.as_ref().map(|z_vec| z_vec.as_slice());
919
920        let derived_bytes_zeroizing = internal_argon2_core(
921            input,
922            effective_salt,
923            effective_ad,
924            effective_secret,
925            p.argon_type,
926            p.version,
927            effective_length,
928            p.memory_cost,
929            p.time_cost,
930            p.parallelism,
931        )?;
932        Ok(derived_bytes_zeroizing.to_vec())
933    }
934}
935
936/// Builder for Argon2 key derivation operations
937///
938/// This struct provides a fluent interface for configuring and executing
939/// Argon2 key derivation operations with various parameters.
940#[derive(Clone)]
941pub struct Argon2Builder<'a, const S: usize>
942where
943    Salt<S>: Argon2Compatible + Clone + Zeroize + Send + Sync + 'static,
944    Params<S>: Clone + Zeroize + Send + Sync + 'static,
945{
946    params: Params<S>,
947    ikm: Option<&'a [u8]>,
948    salt_override: Option<&'a [u8]>,
949    info_override: Option<&'a [u8]>,
950    length_override: Option<usize>,
951}
952
953impl<const S: usize> Zeroize for Argon2Builder<'_, S>
954where
955    Salt<S>: Argon2Compatible + Clone + Zeroize + Send + Sync + 'static,
956    Params<S>: Clone + Zeroize + Send + Sync + 'static,
957{
958    fn zeroize(&mut self) {
959        self.params.zeroize();
960        // References ikm, salt_override, info_override are not zeroized here.
961        // length_override is usize, typically not zeroized.
962    }
963}
964
965impl<'a, const S: usize> KdfOperation<'a, Argon2Algorithm> for Argon2Builder<'a, S>
966where
967    Salt<S>: Argon2Compatible + Clone + Zeroize + Send + Sync + 'static,
968    Params<S>: Default + Clone + Zeroize + Send + Sync + 'static,
969{
970    fn with_ikm(mut self, ikm: &'a [u8]) -> Self {
971        self.ikm = Some(ikm);
972        self
973    }
974    fn with_salt(mut self, salt: &'a [u8]) -> Self {
975        self.salt_override = Some(salt);
976        self
977    }
978    fn with_info(mut self, info: &'a [u8]) -> Self {
979        self.info_override = Some(info);
980        self
981    }
982    fn with_output_length(mut self, len: usize) -> Self {
983        self.length_override = Some(len);
984        self
985    }
986
987    fn derive(self) -> Result<Vec<u8>> {
988        let ikm = self
989            .ikm
990            .ok_or_else(|| Error::param("input_key_material", "missing"))?;
991        let argon_instance_for_derivation = Argon2 {
992            params: self.params,
993        };
994        let final_length = self
995            .length_override
996            .unwrap_or(argon_instance_for_derivation.params.output_len);
997
998        argon_instance_for_derivation.derive_key(
999            ikm,
1000            self.salt_override,
1001            self.info_override,
1002            final_length,
1003        )
1004    }
1005
1006    fn derive_array<const N: usize>(self) -> Result<[u8; N]> {
1007        let ikm = self
1008            .ikm
1009            .ok_or_else(|| Error::param("input_key_material", "missing"))?;
1010        let argon_instance_for_derivation = Argon2 {
1011            params: self.params,
1012        };
1013
1014        let vec_result = argon_instance_for_derivation.derive_key(
1015            ikm,
1016            self.salt_override,
1017            self.info_override,
1018            N,
1019        )?;
1020
1021        vec_result
1022            .try_into()
1023            .map_err(|v_err: Vec<u8>| Error::Length {
1024                context: "Argon2 derive_array output conversion",
1025                expected: N,
1026                actual: v_err.len(),
1027            })
1028    }
1029}
1030
1031impl<const S: usize> ParamProvider for Argon2<S>
1032where
1033    Salt<S>: Argon2Compatible,
1034    Params<S>: Default + Clone + Zeroize + Send + Sync + 'static,
1035{
1036    type Params = Params<S>;
1037
1038    fn with_params(params: Self::Params) -> Self {
1039        Self { params }
1040    }
1041    fn params(&self) -> &Self::Params {
1042        &self.params
1043    }
1044    fn set_params(&mut self, params: Self::Params) {
1045        self.params = params;
1046    }
1047}
1048
1049impl<const S: usize> PasswordHashFunction for Argon2<S>
1050where
1051    Salt<S>: Argon2Compatible + Clone + Zeroize + Send + Sync + 'static,
1052    Params<S>: Default + Clone + Zeroize + Send + Sync + 'static,
1053{
1054    type Password = SecretBytes<32>;
1055
1056    fn hash_password(&self, password: &Self::Password) -> Result<PasswordHash> {
1057        let hashed_output_zeroizing = self.hash_password(password.as_ref())?;
1058
1059        let type_str = match self.params.argon_type {
1060            Algorithm::Argon2d => "argon2d",
1061            Algorithm::Argon2i => "argon2i",
1062            Algorithm::Argon2id => "argon2id",
1063        };
1064
1065        let mut ph_params_map = BTreeMap::new();
1066        ph_params_map.insert("v".to_string(), self.params.version.to_string());
1067        ph_params_map.insert("m".to_string(), self.params.memory_cost.to_string());
1068        ph_params_map.insert("t".to_string(), self.params.time_cost.to_string());
1069        ph_params_map.insert("p".to_string(), self.params.parallelism.to_string());
1070        if let Some(ad_val) = &self.params.ad {
1071            // PHC spec §3: keyid/data fields are *unpadded* Base64.
1072            ph_params_map.insert(
1073                "data".to_string(),
1074                base64::engine::general_purpose::STANDARD_NO_PAD.encode(ad_val),
1075            );
1076        }
1077
1078        Ok(PasswordHash {
1079            algorithm: type_str.to_string(),
1080            params: ph_params_map,
1081            salt: Zeroizing::new(self.params.salt.as_ref().to_vec()),
1082            hash: hashed_output_zeroizing,
1083        })
1084    }
1085
1086    fn verify(&self, password: &Self::Password, stored_hash: &PasswordHash) -> Result<bool> {
1087        let argon_variant_from_hash = match stored_hash.algorithm.as_str() {
1088            "argon2d" => Algorithm::Argon2d,
1089            "argon2i" => Algorithm::Argon2i,
1090            "argon2id" => Algorithm::Argon2id,
1091            _ => {
1092                return Err(Error::param(
1093                    "algorithm",
1094                    "Unsupported algorithm in stored hash",
1095                ))
1096            }
1097        };
1098
1099        let version = stored_hash.param_as_u32("v")?;
1100        if version != ARGON2_VERSION_1_3 {
1101            return Err(Error::param("version", "Version mismatch in stored hash"));
1102        }
1103
1104        let memory_cost = stored_hash.param_as_u32("m")?;
1105        let time_cost = stored_hash.param_as_u32("t")?;
1106        let parallelism = stored_hash.param_as_u32("p")?;
1107
1108        let ad_from_params: Option<Zeroizing<Vec<u8>>> = stored_hash
1109            .params
1110            .get("data")
1111            .map(|s| {
1112                base64::engine::general_purpose::STANDARD_NO_PAD
1113                    .decode(s)
1114                    .map(Zeroizing::new)
1115            })
1116            .transpose()
1117            .map_err(|_| {
1118                Error::param(
1119                    "data",
1120                    "Invalid AD encoding in stored hash (expected Base64)",
1121                )
1122            })?;
1123
1124        let secret_for_verification = self.params.secret.as_ref().map(|z_vec| z_vec.as_slice());
1125
1126        let computed_hash_zeroizing = internal_argon2_core(
1127            password.as_ref(),
1128            &stored_hash.salt,
1129            ad_from_params.as_ref().map(|z| z.as_slice()),
1130            secret_for_verification,
1131            argon_variant_from_hash,
1132            version,
1133            stored_hash.hash.len(),
1134            memory_cost,
1135            time_cost,
1136            parallelism,
1137        )?;
1138
1139        Ok(crate::kdf::common::constant_time_eq(
1140            &computed_hash_zeroizing,
1141            &stored_hash.hash,
1142        ))
1143    }
1144
1145    fn benchmark(&self) -> Duration {
1146        Duration::from_millis(150) // Placeholder
1147    }
1148
1149    fn recommended_params(_target_duration: Duration) -> Self::Params {
1150        Params::default() // Placeholder
1151    }
1152}
1153
1154#[cfg(test)]
1155mod tests;