1#[cfg(feature = "alloc")]
8use crate::alloc_prelude::*;
9
10#[cfg(not(feature = "std"))]
11use alloc::vec::Vec;
12
13use crate::error::{validate, Error, Result};
14use super::params::ParamProvider; use super::{KdfAlgorithm, KdfOperation}; use super::{KeyDerivationFunction, PasswordHash, PasswordHashFunction, SecurityLevel};
18use crate::hash::blake2::Blake2b;
19use crate::hash::HashFunction; use 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
32const ARGON2_VERSION_1_3: u32 = 0x13;
34const ARGON2_BLOCK_SIZE: usize = 1024;
35const ARGON2_QWORDS_IN_BLOCK: usize = ARGON2_BLOCK_SIZE / 8; const ARGON2_SYNC_POINTS: u32 = 4; const ARGON2_PREHASH_SEED_LENGTH: usize = 72;
39
40fn create_blake2b_for_h0() -> Blake2b {
50 let mut param = [0u8; 64];
52 param[0] = 64; param[1] = 0; param[2] = 1; param[3] = 1; param[16] = 0; param[17] = 0; Blake2b::with_parameter_block(param, 64)
63}
64
65fn blake2b_params(digest_len: u8) -> Blake2b {
77 let mut param = [0u8; 64];
78 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)
88}
89
90#[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#[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 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 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
182fn argon2_g(
185 x: &[u64; ARGON2_QWORDS_IN_BLOCK],
186 y: &[u64; ARGON2_QWORDS_IN_BLOCK],
187) -> Zeroizing<[u64; ARGON2_QWORDS_IN_BLOCK]> {
188 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 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 for reg in 0..8 {
202 let mut tmp = Zeroizing::new([0u64; 16]);
204 for row in 0..8 {
205 let base = row * 16 + reg * 2; tmp[2 * row] = r[base];
210 tmp[2 * row + 1] = r[base + 1];
211 }
212
213 blamka_round(&mut tmp); 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 for i in 0..ARGON2_QWORDS_IN_BLOCK {
224 r[i] ^= x[i] ^ y[i];
225 }
226
227 r
228}
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq)]
232pub enum Algorithm {
233 Argon2d = 0,
236 Argon2i = 1,
239 Argon2id = 2,
242}
243
244impl Zeroize for Algorithm {
245 fn zeroize(&mut self) {
246 *self = Self::Argon2d;
247 }
248}
249
250#[derive(Clone)]
255pub struct Params<const S: usize>
256where
257 Salt<S>: Argon2Compatible,
258{
259 pub argon_type: Algorithm,
262 pub version: u32,
264 pub memory_cost: u32, pub time_cost: u32, pub parallelism: u32, pub output_len: usize,
272 pub salt: Salt<S>,
274 pub ad: Option<Vec<u8>>,
276 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
297impl<const S: usize> Default for Params<S>
300where
301 Salt<S>: Argon2Compatible,
302{
303 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(), ad: None,
314 secret: None,
315 }
316 }
317}
318
319#[derive(Clone)]
325pub struct Argon2<const S: usize>
326where
327 Salt<S>: Argon2Compatible,
328{
329 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 pub fn new_with_params(params: Params<S>) -> Self {
352 Self { params }
353 }
354
355 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#[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, buf: &mut Block,
398) -> Result<()> {
399 buf.zeroize();
401
402 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 write_u64_le(&mut buf.0[off..off + 8], counter);
425 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 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 address_qwords.copy_from_slice(&block1[..]);
445 Ok(())
446}
447
448#[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 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 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(¶llelism_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 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 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 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 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 let pseudo_rand: u64 = if data_independent_addressing_for_segment {
654 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 address_block_counter += 1;
663
664 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 address_block_qwords[block_in_segment_idx as usize % ARGON2_QWORDS_IN_BLOCK]
680 } else {
681 read_u64_le(&memory_matrix[prev_block_abs_idx].0[..8])
683 };
684
685 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 {
691 lane_idx } else {
693 j2 % parallelism_lanes };
695
696 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 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 let mut xv = Zeroizing::new([0u64; ARGON2_QWORDS_IN_BLOCK]);
723 let mut yv = Zeroizing::new([0u64; ARGON2_QWORDS_IN_BLOCK]);
724
725 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 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 let gq = argon2_g(&xv, &yv);
745
746 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
774fn h_prime_variable_output(data: &[u8], t: usize) -> Result<ZeroizingBytes> {
776 if t == 0 {
778 return Ok(Zeroizing::new(boxed_bytes_zeroed(0)));
779 }
780
781 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 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 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 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 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#[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 let mut reference_area_size: u32;
852
853 if pass_idx == 0 {
854 if slice_idx == 0 {
855 reference_area_size = block_in_segment_idx.saturating_sub(1);
857 } else if ref_lane_val == current_lane_idx {
858 reference_area_size = slice_idx * segment_length + block_in_segment_idx;
860 reference_area_size = reference_area_size.saturating_sub(1); } else {
862 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 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 let mut phi = j1 as u64;
883 phi = (phi * phi) >> 32; 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 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
905pub 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 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#[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 }
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 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) }
1200
1201 fn recommended_params(_target_duration: Duration) -> Self::Params {
1202 Params::default() }
1204}
1205
1206#[cfg(test)]
1207mod tests;