1#[cfg(not(feature = "std"))]
8use alloc::vec::Vec;
9
10use crate::error::{validate, Error, Result};
11use super::params::ParamProvider; use super::{KdfAlgorithm, KdfOperation}; use super::{KeyDerivationFunction, PasswordHash, PasswordHashFunction, SecurityLevel};
15use crate::hash::blake2::Blake2b;
16use crate::hash::HashFunction; use 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
26const ARGON2_VERSION_1_3: u32 = 0x13;
28const ARGON2_BLOCK_SIZE: usize = 1024;
29const ARGON2_QWORDS_IN_BLOCK: usize = ARGON2_BLOCK_SIZE / 8; const ARGON2_SYNC_POINTS: u32 = 4; const ARGON2_PREHASH_SEED_LENGTH: usize = 72;
33
34fn create_blake2b_for_h0() -> Blake2b {
44 let mut param = [0u8; 64];
46 param[0] = 64; param[1] = 0; param[2] = 1; param[3] = 1; param[16] = 0; param[17] = 0; Blake2b::with_parameter_block(param, 64)
57}
58
59fn blake2b_params(digest_len: u8) -> Blake2b {
71 let mut param = [0u8; 64];
72 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)
82}
83
84#[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
95type MemBlock = Block; #[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 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 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
149fn argon2_g(
152 x: &[u64; ARGON2_QWORDS_IN_BLOCK],
153 y: &[u64; ARGON2_QWORDS_IN_BLOCK],
154) -> [u64; ARGON2_QWORDS_IN_BLOCK] {
155 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 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 for reg in 0..8 {
169 let mut tmp = [0u64; 16];
171 for row in 0..8 {
172 let base = row * 16 + reg * 2; tmp[2 * row] = r[base];
177 tmp[2 * row + 1] = r[base + 1];
178 }
179
180 blamka_round(&mut tmp); 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 for i in 0..ARGON2_QWORDS_IN_BLOCK {
191 r[i] ^= x[i] ^ y[i];
192 }
193
194 r
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq, Zeroize)]
199pub enum Algorithm {
200 Argon2d = 0,
203 Argon2i = 1,
206 Argon2id = 2,
209}
210
211#[derive(Clone, Zeroize)] pub struct Params<const S: usize>
217where
218 Salt<S>: Argon2Compatible,
219{
220 pub argon_type: Algorithm,
223 pub version: u32,
225 pub memory_cost: u32, pub time_cost: u32, pub parallelism: u32, pub output_len: usize,
233 pub salt: Salt<S>,
235 pub ad: Option<Zeroizing<Vec<u8>>>,
237 pub secret: Option<Zeroizing<Vec<u8>>>,
239}
240
241impl<const S: usize> Default for Params<S>
244where
245 Salt<S>: Argon2Compatible,
246{
247 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(), ad: None,
258 secret: None,
259 }
260 }
261}
262
263#[derive(Clone)]
269pub struct Argon2<const S: usize>
270where
271 Salt<S>: Argon2Compatible,
272{
273 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 pub fn new_with_params(params: Params<S>) -> Self {
296 Self { params }
297 }
298
299 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#[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, buf: &mut Block,
342) -> Result<()> {
343 buf.zeroize();
345
346 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 buf.0[off..off + 8].copy_from_slice(&counter.to_le_bytes());
369 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 let zero = [0u64; ARGON2_QWORDS_IN_BLOCK];
384 let block0 = argon2_g(&zero, &input_q);
385 let block1 = argon2_g(&zero, &block0);
386
387 address_qwords.copy_from_slice(&block1);
389 Ok(())
390}
391
392#[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 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 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(¶llelism_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 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 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 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 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 let pseudo_rand: u64 = if data_independent_addressing_for_segment {
599 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 address_block_counter += 1;
608
609 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 address_block_qwords[block_in_segment_idx as usize % ARGON2_QWORDS_IN_BLOCK]
625 } else {
626 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 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 {
638 lane_idx } else {
640 j2 % parallelism_lanes };
642
643 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 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] };
668
669 let mut xv = [0u64; ARGON2_QWORDS_IN_BLOCK];
671 let mut yv = [0u64; ARGON2_QWORDS_IN_BLOCK];
672
673 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 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 let gq = argon2_g(&xv, &yv);
693
694 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 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
731fn h_prime_variable_output(data: &[u8], t: usize) -> Result<Vec<u8>> {
733 if t == 0 {
735 return Ok(vec![]);
736 }
737
738 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 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 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 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 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#[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 let mut reference_area_size: u32;
798
799 if pass_idx == 0 {
800 if slice_idx == 0 {
801 reference_area_size = block_in_segment_idx.saturating_sub(1);
803 } else if ref_lane_val == current_lane_idx {
804 reference_area_size = slice_idx * segment_length + block_in_segment_idx;
806 reference_area_size = reference_area_size.saturating_sub(1); } else {
808 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 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 let mut phi = j1 as u64;
829 phi = (phi * phi) >> 32; 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 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
851pub 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 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#[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 }
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 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) }
1148
1149 fn recommended_params(_target_duration: Duration) -> Self::Params {
1150 Params::default() }
1152}
1153
1154#[cfg(test)]
1155mod tests;