Skip to main content

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