1#[cfg(feature = "alloc")]
2extern crate alloc;
3
4#[cfg(feature = "alloc")]
5use alloc::{string::String, vec, vec::Vec};
6use core::mem::MaybeUninit;
7
8use crate::{Hasher, blake2::Blake2b};
9
10#[path = "fill.rs"]
11mod fill;
12
13#[cfg(target_arch = "x86_64")]
14#[path = "fill_avx2.rs"]
15mod fill_avx2;
16
17#[cfg(target_arch = "aarch64")]
18#[path = "fill_neon.rs"]
19mod fill_neon;
20
21#[cfg(test)]
22use fill::{compress, permutation_p};
23
24const VERSION: u32 = 0x13;
26
27const SYNC_POINTS: u32 = 4;
29
30const BLOCK_SIZE: usize = 1024;
32
33#[allow(dead_code)]
35const ARGON2D: u32 = 0;
36const ARGON2I: u32 = 1;
37const ARGON2ID: u32 = 2;
38
39#[cfg(feature = "alloc")]
41const DEFAULT_TAG_LENGTH: usize = 64;
42
43#[derive(Debug, Clone)]
60pub struct Params {
61 pub iterations: u32,
63 pub memory: u32,
65 pub parallelism: u32,
67}
68
69impl Default for Params {
70 fn default() -> Self {
72 Params {
73 iterations: 3,
74 memory: 65536,
75 parallelism: 4,
76 }
77 }
78}
79
80#[cfg(feature = "alloc")]
81impl Params {
82 fn validate(&self, out_len: usize) -> Result<(), Argon2Error> {
90 if self.iterations < 1 {
91 return Err(Argon2Error::InvalidParams("iterations must be >= 1"));
92 }
93 if self.parallelism < 1 {
94 return Err(Argon2Error::InvalidParams("parallelism must be >= 1"));
95 }
96 if self.parallelism > (1 << 24) - 1 {
97 return Err(Argon2Error::InvalidParams("parallelism must be <= 2^24 - 1"));
98 }
99 if out_len < 4 {
100 return Err(Argon2Error::InvalidParams("output length must be >= 4"));
101 }
102 check_u32_len(out_len, "output length must be <= 2^32 - 1")?;
103 if self.memory < 8 * self.parallelism {
106 return Err(Argon2Error::InvalidParams("memory must be >= 8*parallelism"));
107 }
108 Ok(())
109 }
110}
111
112#[cfg(feature = "alloc")]
117#[inline(always)]
118fn check_u32_len(len: usize, msg: &'static str) -> Result<(), Argon2Error> {
119 if u32::try_from(len).is_err() {
120 Err(Argon2Error::InvalidParams(msg))
121 } else {
122 Ok(())
123 }
124}
125
126#[derive(Debug, Clone, PartialEq, Eq)]
128#[cfg(feature = "alloc")]
129pub enum Argon2Error {
130 InvalidParams(&'static str),
132 InvalidEncoding(&'static str),
134 VerifyMismatch,
136}
137
138#[cfg(feature = "alloc")]
139impl core::fmt::Display for Argon2Error {
140 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
141 match self {
142 Argon2Error::InvalidParams(msg) => write!(f, "argon2: invalid params: {}", msg),
143 Argon2Error::InvalidEncoding(msg) => write!(f, "argon2: invalid encoding: {}", msg),
144 Argon2Error::VerifyMismatch => write!(f, "argon2: verification failed"),
145 }
146 }
147}
148
149#[cfg(feature = "alloc")]
186pub fn derive_key(
187 out: &mut [u8],
188 password: &[u8],
189 salt: &[u8],
190 secret: &[u8],
191 ad: &[u8],
192 params: &Params,
193) -> Result<(), Argon2Error> {
194 argon2_core(ARGON2ID, password, salt, secret, ad, params, out)
195}
196
197#[cfg(feature = "alloc")]
216pub fn hash_password(password: &[u8], salt: &[u8], params: &Params) -> Result<String, Argon2Error> {
217 let mut tag = [0u8; DEFAULT_TAG_LENGTH];
218 derive_key(&mut tag, password, salt, &[], &[], params)?;
219 Ok(encode_phc(params, salt, &tag))
220}
221
222#[cfg(feature = "alloc")]
235pub fn verify_password(password: &[u8], encoded: &str, limits: Option<&Params>) -> Result<(), Argon2Error> {
236 let (params, salt, expected_tag) = decode_phc(encoded)?;
237
238 if let Some(limits) = limits
239 && (params.iterations > limits.iterations
240 || params.memory > limits.memory
241 || params.parallelism > limits.parallelism)
242 {
243 return Err(Argon2Error::InvalidParams("encoded parameters exceed the configured limits"));
244 }
245
246 let mut computed_tag = vec![0u8; expected_tag.len()];
247 derive_key(&mut computed_tag, password, &salt, &[], &[], ¶ms)?;
248
249 constant_time_eq::constant_time_eq(&computed_tag, &expected_tag).ok_or(Argon2Error::VerifyMismatch)
250}
251
252#[cfg(feature = "alloc")]
262pub fn encode_phc(params: &Params, salt: &[u8], tag: &[u8]) -> String {
263 let salt_b64 = base64_encode_no_pad(salt);
264 let tag_b64 = base64_encode_no_pad(tag);
265 alloc::format!(
266 "$argon2id$v=19$m={},t={},p={}${}${}",
267 params.memory,
268 params.iterations,
269 params.parallelism,
270 salt_b64,
271 tag_b64
272 )
273}
274
275#[cfg(feature = "alloc")]
285pub fn decode_phc(encoded: &str) -> Result<(Params, Vec<u8>, Vec<u8>), Argon2Error> {
286 let parts: Vec<&str> = encoded.split('$').collect();
287 if parts.len() != 6 {
289 return Err(Argon2Error::InvalidEncoding("invalid PHC string format"));
290 }
291 if !parts[0].is_empty() {
292 return Err(Argon2Error::InvalidEncoding("must start with $"));
293 }
294 if parts[1] != "argon2id" {
295 return Err(Argon2Error::InvalidEncoding("unsupported algorithm"));
296 }
297 if parts[2] != "v=19" {
298 return Err(Argon2Error::InvalidEncoding("unsupported version"));
299 }
300
301 let param_parts: Vec<&str> = parts[3].split(',').collect();
303 if param_parts.len() != 3 {
304 return Err(Argon2Error::InvalidEncoding("invalid parameters"));
305 }
306
307 let memory = parse_param(param_parts[0], "m=")?;
308 let iterations = parse_param(param_parts[1], "t=")?;
309 let parallelism = parse_param(param_parts[2], "p=")?;
310
311 let salt = base64_decode_no_pad(parts[4]).map_err(|_| Argon2Error::InvalidEncoding("invalid base64 in salt"))?;
312 let tag = base64_decode_no_pad(parts[5]).map_err(|_| Argon2Error::InvalidEncoding("invalid base64 in hash"))?;
313
314 let params = Params {
315 iterations,
316 memory,
317 parallelism,
318 };
319
320 params.validate(tag.len())?;
323
324 Ok((params, salt, tag))
325}
326
327#[derive(Clone)]
336#[repr(align(64))]
337struct Block {
338 v: [u64; 128],
339}
340
341impl Block {
342 #[inline(always)]
343 const fn zero() -> Self {
344 Block {
345 v: [0u64; 128],
346 }
347 }
348
349 #[inline(always)]
350 fn xor_with(&mut self, other: &Block) {
351 for (dest, source) in self.v.iter_mut().zip(other.v.iter()) {
352 *dest ^= *source;
353 }
354 }
355
356 #[inline(always)]
358 fn from_bytes(bytes: &[u8; BLOCK_SIZE]) -> Self {
359 let mut v = [0u64; 128];
360 for (word, chunk) in v.iter_mut().zip(bytes.as_chunks::<8>().0) {
361 *word = u64::from_le_bytes(*chunk);
362 }
363 Block {
364 v,
365 }
366 }
367
368 #[inline(always)]
370 fn to_bytes(&self) -> [u8; BLOCK_SIZE] {
371 let mut out = [0u8; BLOCK_SIZE];
372 for (chunk, word) in out.as_chunks_mut::<8>().0.iter_mut().zip(self.v.iter()) {
373 *chunk = word.to_le_bytes();
374 }
375 out
376 }
377}
378
379#[cfg(feature = "alloc")]
381fn argon2_core(
382 argon_type: u32,
383 password: &[u8],
384 salt: &[u8],
385 secret: &[u8],
386 ad: &[u8],
387 params: &Params,
388 out: &mut [u8],
389) -> Result<(), Argon2Error> {
390 argon2_core_with_backend(argon_type, password, salt, secret, ad, params, out, detect_backend())
391}
392
393#[cfg(feature = "alloc")]
396#[allow(clippy::too_many_arguments)]
397fn argon2_core_with_backend(
398 argon_type: u32,
399 password: &[u8],
400 salt: &[u8],
401 secret: &[u8],
402 ad: &[u8],
403 params: &Params,
404 out: &mut [u8],
405 backend: Backend,
406) -> Result<(), Argon2Error> {
407 params.validate(out.len())?;
411 check_u32_len(password.len(), "password must be <= 2^32 - 1 bytes")?;
412 check_u32_len(salt.len(), "salt must be <= 2^32 - 1 bytes")?;
413 check_u32_len(secret.len(), "secret must be <= 2^32 - 1 bytes")?;
414 check_u32_len(ad.len(), "associated data must be <= 2^32 - 1 bytes")?;
415
416 let p = params.parallelism;
417 let t = params.iterations;
418 let m = params.memory;
419 let tag_length = out.len() as u32;
420
421 let h0 = compute_h0(argon_type, password, salt, secret, ad, p, tag_length, m, t);
423
424 let m_prime = 4 * p * (m / (4 * p));
426 let q = m_prime / p; let mem = Memory::uninit(m_prime as usize);
432
433 for i in 0..p {
435 let mut input = [0u8; 72];
436 input[..64].copy_from_slice(&h0);
437 input[68..72].copy_from_slice(&i.to_le_bytes());
438
439 let mut block_bytes = [0u8; BLOCK_SIZE];
440
441 input[64..68].copy_from_slice(&0u32.to_le_bytes());
443 variable_length_hash_into(&input, &mut block_bytes);
444 mem.write((i * q) as usize, Block::from_bytes(&block_bytes));
445
446 input[64..68].copy_from_slice(&1u32.to_le_bytes());
448 variable_length_hash_into(&input, &mut block_bytes);
449 mem.write((i * q + 1) as usize, Block::from_bytes(&block_bytes));
450 }
451
452 fill_memory(backend, &mem, argon_type, p, q, t, m_prime);
454
455 let mut final_block = mem.get((q - 1) as usize).clone();
457 for i in 1..p {
458 let idx = (i * q + q - 1) as usize;
459 final_block.xor_with(mem.get(idx));
460 }
461
462 let final_bytes = final_block.to_bytes();
464 variable_length_hash_into(&final_bytes, out);
465
466 Ok(())
467}
468
469#[cfg(feature = "alloc")]
478fn fill_memory(backend: Backend, memory: &Memory, argon_type: u32, p: u32, q: u32, t: u32, m_prime: u32) {
479 #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
480 {
481 const MIN_PARALLEL_SEGMENT_LENGTH: u32 = 64;
483 let segment_length = q / SYNC_POINTS;
484 if p > 1 && segment_length >= MIN_PARALLEL_SEGMENT_LENGTH {
485 let barrier = std::sync::Barrier::new(p as usize);
488 std::thread::scope(|scope| {
489 let barrier = &barrier;
490 for lane in 0..p {
491 scope.spawn(move || {
492 for pass in 0..t {
493 for slice in 0..SYNC_POINTS {
494 fill_segment(backend, memory, argon_type, pass, lane, slice, p, q, t, m_prime);
495 barrier.wait();
496 }
497 }
498 });
499 }
500 });
501 return;
502 }
503 }
504
505 for pass in 0..t {
506 for slice in 0..SYNC_POINTS {
507 for lane in 0..p {
508 fill_segment(backend, memory, argon_type, pass, lane, slice, p, q, t, m_prime);
509 }
510 }
511 }
512}
513
514#[derive(Clone, Copy, PartialEq, Eq, Debug)]
519#[allow(dead_code)] enum Backend {
521 Scalar,
523 #[cfg(target_arch = "aarch64")]
525 Neon,
526 #[cfg(target_arch = "aarch64")]
529 NeonSha3,
530 #[cfg(target_arch = "x86_64")]
532 Avx2,
533}
534
535#[allow(clippy::needless_return)]
541fn detect_backend() -> Backend {
542 #[cfg(target_arch = "aarch64")]
543 {
544 #[cfg(feature = "std")]
545 {
546 if std::arch::is_aarch64_feature_detected!("sha3") {
547 return Backend::NeonSha3;
548 }
549 return Backend::Neon;
550 }
551
552 #[cfg(all(not(feature = "std"), target_feature = "sha3"))]
553 return Backend::NeonSha3;
554
555 #[cfg(all(not(feature = "std"), not(target_feature = "sha3")))]
556 return Backend::Neon;
557 }
558
559 #[cfg(target_arch = "x86_64")]
560 {
561 #[cfg(feature = "std")]
562 {
563 if std::arch::is_x86_feature_detected!("avx2") {
564 return Backend::Avx2;
565 }
566 return Backend::Scalar;
567 }
568
569 #[cfg(all(not(feature = "std"), target_feature = "avx2"))]
570 return Backend::Avx2;
571
572 #[cfg(all(not(feature = "std"), not(target_feature = "avx2")))]
573 return Backend::Scalar;
574 }
575
576 #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
577 return Backend::Scalar;
578}
579
580#[cfg(feature = "alloc")]
588#[allow(clippy::too_many_arguments)]
589fn fill_segment(
590 backend: Backend,
591 memory: &Memory,
592 argon_type: u32,
593 pass: u32,
594 lane: u32,
595 slice: u32,
596 lanes: u32,
597 q: u32, t: u32, m_prime: u32, ) {
601 let segment_length = q / SYNC_POINTS;
602
603 let need_pseudo_rands = argon_type == ARGON2I || (argon_type == ARGON2ID && pass == 0 && slice < 2);
607 let mut addr_words = [0u64; 128];
608 let mut addr_chunk = u32::MAX;
609
610 let start_index = if pass == 0 && slice == 0 { 2 } else { 0 };
611
612 for s in start_index..segment_length {
613 let j = slice * segment_length + s; let cur_index = (lane * q + j) as usize;
615
616 let prev_index = if j == 0 {
618 (lane * q + q - 1) as usize
619 } else {
620 (lane * q + j - 1) as usize
621 };
622
623 let (j1, j2) = if need_pseudo_rands {
625 let chunk = s / 128;
626 if chunk != addr_chunk {
627 generate_address_block(pass, lane, slice, t, argon_type, m_prime, (chunk + 1) as u64, &mut addr_words);
628 addr_chunk = chunk;
629 }
630 let val = addr_words[(s % 128) as usize];
631 ((val & 0xFFFFFFFF) as u32, (val >> 32) as u32)
632 } else {
633 let word = memory.first_word(prev_index);
635 (word as u32, (word >> 32) as u32)
636 };
637
638 let ref_lane = if pass == 0 && slice == 0 { lane } else { j2 % lanes };
640
641 let ref_index = index_alpha(pass, slice, lanes, segment_length, s, q, ref_lane == lane, j1);
642 let ref_block_index = (ref_lane * q + ref_index) as usize;
643
644 unsafe {
653 let prev = memory.get(prev_index);
654 let reference = memory.get(ref_block_index);
655 let cur = memory.get_mut(cur_index);
656 match backend {
657 Backend::Scalar => fill::fill_block(prev, reference, cur, pass != 0),
658 #[cfg(target_arch = "aarch64")]
659 Backend::Neon => fill_neon::fill_block(prev, reference, cur, pass != 0),
660 #[cfg(target_arch = "aarch64")]
661 Backend::NeonSha3 => fill_neon::fill_block_sha3(prev, reference, cur, pass != 0),
662 #[cfg(target_arch = "x86_64")]
663 Backend::Avx2 => fill_avx2::fill_block(prev, reference, cur, pass != 0),
664 }
665 }
666 }
667}
668
669#[cfg(feature = "alloc")]
672fn generate_address_block(
673 pass: u32,
674 lane: u32,
675 slice: u32,
676 t: u32,
677 argon_type: u32,
678 m_prime: u32,
679 counter: u64,
680 out: &mut [u64; 128],
681) {
682 let mut input = Block::zero();
684 input.v[0] = pass as u64;
685 input.v[1] = lane as u64;
686 input.v[2] = slice as u64;
687 input.v[3] = m_prime as u64;
688 input.v[4] = t as u64;
689 input.v[5] = argon_type as u64;
690 input.v[6] = counter;
691
692 let zero_block = Block::zero();
693 let mut tmp = Block::zero();
694 fill::fill_block_ref(&zero_block, &input, &mut tmp, false);
695 let mut addr_block = Block::zero();
696 fill::fill_block_ref(&zero_block, &tmp, &mut addr_block, false);
697 out.copy_from_slice(&addr_block.v);
698}
699
700fn index_alpha(
702 pass: u32,
703 slice: u32,
704 _lanes: u32,
705 segment_length: u32,
706 index_in_segment: u32,
707 q: u32,
708 same_lane: bool,
709 j1: u32,
710) -> u32 {
711 let reference_area_size = if pass == 0 {
713 if slice == 0 {
715 index_in_segment.saturating_sub(1)
717 } else {
718 if same_lane {
719 slice * segment_length + index_in_segment - 1
720 } else {
721 slice * segment_length - if index_in_segment == 0 { 1 } else { 0 }
722 }
723 }
724 } else {
725 if same_lane {
727 q - segment_length + index_in_segment - 1
728 } else {
729 q - segment_length - if index_in_segment == 0 { 1 } else { 0 }
730 }
731 };
732
733 if reference_area_size == 0 {
734 return 0;
735 }
736
737 let j1_64 = j1 as u64;
739 let x = (j1_64 * j1_64) >> 32;
740 let y = (reference_area_size as u64 * x) >> 32;
741 let relative_position = (reference_area_size as u64 - 1 - y) as u32;
742
743 let start_position = if pass == 0 {
745 0
746 } else {
747 if slice == SYNC_POINTS - 1 {
748 0
749 } else {
750 (slice + 1) * segment_length
751 }
752 };
753
754 (start_position + relative_position) % q
755}
756
757#[cfg(feature = "alloc")]
759fn compute_h0(
760 argon_type: u32,
761 password: &[u8],
762 salt: &[u8],
763 secret: &[u8],
764 ad: &[u8],
765 p: u32,
766 tag_length: u32,
767 m: u32,
768 t: u32,
769) -> [u8; 64] {
770 let mut blake = Blake2b::new_keyed(&[], 64);
771
772 blake.update(&p.to_le_bytes());
773 blake.update(&tag_length.to_le_bytes());
774 blake.update(&m.to_le_bytes());
775 blake.update(&t.to_le_bytes());
776 blake.update(&VERSION.to_le_bytes());
777 blake.update(&argon_type.to_le_bytes());
778 blake.update(&(password.len() as u32).to_le_bytes());
779 blake.update(password);
780 blake.update(&(salt.len() as u32).to_le_bytes());
781 blake.update(salt);
782 blake.update(&(secret.len() as u32).to_le_bytes());
783 blake.update(secret);
784 blake.update(&(ad.len() as u32).to_le_bytes());
785 blake.update(ad);
786
787 let hash = blake.sum();
788 let mut result = [0u8; 64];
789 result.copy_from_slice(&hash.as_ref()[..64]);
790 result
791}
792
793#[cfg(feature = "alloc")]
798fn variable_length_hash_into(input: &[u8], out: &mut [u8]) {
799 let tag_length = out.len();
800 debug_assert!(u32::try_from(tag_length).is_ok(), "tag length must fit in a u32");
801
802 if tag_length <= 64 {
803 let mut blake = Blake2b::new_keyed(&[], tag_length);
805 blake.update(&(tag_length as u32).to_le_bytes());
806 blake.update(input);
807 let hash = blake.sum();
808 out.copy_from_slice(&hash.as_ref()[..tag_length]);
809 return;
810 }
811
812 let r = tag_length.div_ceil(32) - 2;
815
816 let mut v = [0u8; 64];
818 {
819 let mut blake = Blake2b::new_keyed(&[], 64);
820 blake.update(&(tag_length as u32).to_le_bytes());
821 blake.update(input);
822 let hash = blake.sum();
823 v.copy_from_slice(&hash.as_ref()[..64]);
824 }
825
826 out[..32].copy_from_slice(&v[..32]);
828
829 let mut offset = 32;
831 for _ in 2..=r {
832 let mut blake = Blake2b::new_keyed(&[], 64);
833 blake.update(&v);
834 let hash = blake.sum();
835 v.copy_from_slice(&hash.as_ref()[..64]);
836 out[offset..offset + 32].copy_from_slice(&v[..32]);
837 offset += 32;
838 }
839
840 let remaining = tag_length - 32 * r;
842 let mut blake = Blake2b::new_keyed(&[], remaining);
843 blake.update(&v);
844 let hash = blake.sum();
845 out[offset..offset + remaining].copy_from_slice(&hash.as_ref()[..remaining]);
846}
847
848struct Memory {
866 blocks: Vec<MaybeUninit<Block>>,
867}
868
869unsafe impl Send for Memory {}
874unsafe impl Sync for Memory {}
875
876impl Memory {
877 fn uninit(len: usize) -> Self {
883 let mut blocks: Vec<MaybeUninit<Block>> = Vec::with_capacity(len);
884 unsafe { blocks.set_len(len) };
888 Memory {
889 blocks,
890 }
891 }
892
893 #[inline(always)]
895 fn base_ptr(&self) -> *mut Block {
896 self.blocks.as_ptr() as *mut Block
897 }
898
899 #[inline(always)]
901 fn len(&self) -> usize {
902 self.blocks.len()
903 }
904
905 #[inline(always)]
910 fn write(&self, index: usize, block: Block) {
911 debug_assert!(index < self.len());
912 unsafe { core::ptr::write(self.base_ptr().add(index), block) };
915 }
916
917 #[inline(always)]
922 fn get(&self, index: usize) -> &Block {
923 debug_assert!(index < self.len());
924 unsafe { &*self.base_ptr().add(index) }
927 }
928
929 #[inline(always)]
931 fn first_word(&self, index: usize) -> u64 {
932 self.get(index).v[0]
933 }
934
935 #[inline(always)]
940 fn get_mut(&self, index: usize) -> *mut Block {
941 debug_assert!(index < self.len());
942 unsafe { self.base_ptr().add(index) }
945 }
946}
947
948#[cfg(feature = "alloc")]
953fn base64_encode_no_pad(input: &[u8]) -> String {
954 base64::encode(input, base64::Alphabet::StandardNoPadding)
955}
956
957#[cfg(feature = "alloc")]
958fn base64_decode_no_pad(input: &str) -> Result<Vec<u8>, ()> {
959 base64::decode(input.as_bytes(), base64::Alphabet::StandardNoPadding).map_err(|_| ())
960}
961
962#[cfg(feature = "alloc")]
963fn parse_param(s: &str, prefix: &str) -> Result<u32, Argon2Error> {
964 if !s.starts_with(prefix) {
965 return Err(Argon2Error::InvalidEncoding("invalid parameter prefix"));
966 }
967 s[prefix.len()..]
968 .parse::<u32>()
969 .map_err(|_| Argon2Error::InvalidEncoding("invalid parameter value"))
970}
971
972#[cfg(test)]
977mod tests {
978 use super::*;
979
980 fn derive_key_typed(
981 argon_type: u32,
982 password: &[u8],
983 salt: &[u8],
984 secret: &[u8],
985 ad: &[u8],
986 iterations: u32,
987 memory: u32,
988 parallelism: u32,
989 tag_length: u32,
990 ) -> Vec<u8> {
991 let params = Params {
992 iterations: iterations,
993 memory: memory,
994 parallelism: parallelism,
995 };
996 let mut out = vec![0u8; tag_length as usize];
997 argon2_core(argon_type, password, salt, secret, ad, ¶ms, &mut out).unwrap();
998 out
999 }
1000
1001 #[allow(clippy::too_many_arguments)]
1003 fn derive_key_typed_backend(
1004 backend: Backend,
1005 argon_type: u32,
1006 password: &[u8],
1007 salt: &[u8],
1008 secret: &[u8],
1009 ad: &[u8],
1010 iterations: u32,
1011 memory: u32,
1012 parallelism: u32,
1013 tag_length: u32,
1014 ) -> Vec<u8> {
1015 let params = Params {
1016 iterations,
1017 memory,
1018 parallelism,
1019 };
1020 let mut out = vec![0u8; tag_length as usize];
1021 argon2_core_with_backend(argon_type, password, salt, secret, ad, ¶ms, &mut out, backend).unwrap();
1022 out
1023 }
1024
1025 fn available_backends() -> Vec<Backend> {
1028 let mut backends = vec![Backend::Scalar];
1029
1030 #[cfg(target_arch = "aarch64")]
1031 {
1032 backends.push(Backend::Neon);
1033 #[cfg(feature = "std")]
1034 if std::arch::is_aarch64_feature_detected!("sha3") {
1035 backends.push(Backend::NeonSha3);
1036 }
1037 }
1038
1039 #[cfg(target_arch = "x86_64")]
1040 {
1041 #[cfg(feature = "std")]
1042 if std::arch::is_x86_feature_detected!("avx2") {
1043 backends.push(Backend::Avx2);
1044 }
1045 }
1046
1047 backends
1048 }
1049
1050 #[test]
1053 fn test_backends_match_reference() {
1054 let password = b"password";
1055 let salt = b"somesalt";
1056 for backend in available_backends() {
1057 for v in GO_VECTORS.iter() {
1058 let expected = hex::decode(v.hash).unwrap();
1059 let result = derive_key_typed_backend(
1060 backend,
1061 v.mode,
1062 password,
1063 salt,
1064 &[],
1065 &[],
1066 v.time,
1067 v.memory,
1068 v.threads,
1069 expected.len() as u32,
1070 );
1071 assert_eq!(
1072 result, expected,
1073 "backend {:?} failed Go vector (mode={}, t={}, m={}, p={})",
1074 backend, v.mode, v.time, v.memory, v.threads
1075 );
1076 }
1077 }
1078 }
1079
1080 fn derive_key_vec(
1082 tag_length: usize,
1083 password: &[u8],
1084 salt: &[u8],
1085 secret: &[u8],
1086 ad: &[u8],
1087 params: &Params,
1088 ) -> Vec<u8> {
1089 let mut out = vec![0u8; tag_length];
1090 derive_key(&mut out, password, salt, secret, ad, params).unwrap();
1091 out
1092 }
1093
1094 fn variable_length_hash_vec(input: &[u8], tag_length: usize) -> Vec<u8> {
1096 let mut out = vec![0u8; tag_length];
1097 variable_length_hash_into(input, &mut out);
1098 out
1099 }
1100
1101 #[test]
1108 fn test_rfc9106_argon2d() {
1109 let pwd = vec![0x01u8; 32];
1110 let salt = vec![0x02u8; 16];
1111 let secret = vec![0x03u8; 8];
1112 let ad = vec![0x04u8; 12];
1113 let expected = hex::decode("512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb").unwrap();
1114 let result = derive_key_typed(ARGON2D, &pwd, &salt, &secret, &ad, 3, 32, 4, 32);
1115 assert_eq!(result, expected);
1116 }
1117
1118 #[test]
1119 fn test_rfc9106_argon2i() {
1120 let pwd = vec![0x01u8; 32];
1121 let salt = vec![0x02u8; 16];
1122 let secret = vec![0x03u8; 8];
1123 let ad = vec![0x04u8; 12];
1124 let expected = hex::decode("c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8").unwrap();
1125 let result = derive_key_typed(ARGON2I, &pwd, &salt, &secret, &ad, 3, 32, 4, 32);
1126 assert_eq!(result, expected);
1127 }
1128
1129 #[test]
1136 fn test_h0() {
1137 let pwd = vec![0x01u8; 32];
1138 let salt = vec![0x02u8; 16];
1139 let secret = vec![0x03u8; 8];
1140 let ad = vec![0x04u8; 12];
1141 let h0 = compute_h0(ARGON2ID, &pwd, &salt, &secret, &ad, 4, 32, 32, 3);
1142 let expected = "2889de487eb42ae500c0007ed9252f1069eadec40d5765b485de6dc2437a67b8546a2f0acc1a0882db8fcf74714b472e94df421a5da1112ffa11434370a1e997";
1143 assert_eq!(hex::encode(h0), expected);
1144 }
1145
1146 struct Vec3 {
1152 mode: u32,
1153 time: u32,
1154 memory: u32,
1155 threads: u32,
1156 hash: &'static str,
1157 }
1158
1159 const GO_VECTORS: &[Vec3] = &[
1160 Vec3 {
1161 mode: ARGON2I,
1162 time: 1,
1163 memory: 64,
1164 threads: 1,
1165 hash: "b9c401d1844a67d50eae3967dc28870b22e508092e861a37",
1166 },
1167 Vec3 {
1168 mode: ARGON2D,
1169 time: 1,
1170 memory: 64,
1171 threads: 1,
1172 hash: "8727405fd07c32c78d64f547f24150d3f2e703a89f981a19",
1173 },
1174 Vec3 {
1175 mode: ARGON2ID,
1176 time: 1,
1177 memory: 64,
1178 threads: 1,
1179 hash: "655ad15eac652dc59f7170a7332bf49b8469be1fdb9c28bb",
1180 },
1181 Vec3 {
1182 mode: ARGON2I,
1183 time: 2,
1184 memory: 64,
1185 threads: 1,
1186 hash: "8cf3d8f76a6617afe35fac48eb0b7433a9a670ca4a07ed64",
1187 },
1188 Vec3 {
1189 mode: ARGON2D,
1190 time: 2,
1191 memory: 64,
1192 threads: 1,
1193 hash: "3be9ec79a69b75d3752acb59a1fbb8b295a46529c48fbb75",
1194 },
1195 Vec3 {
1196 mode: ARGON2ID,
1197 time: 2,
1198 memory: 64,
1199 threads: 1,
1200 hash: "068d62b26455936aa6ebe60060b0a65870dbfa3ddf8d41f7",
1201 },
1202 Vec3 {
1203 mode: ARGON2I,
1204 time: 2,
1205 memory: 64,
1206 threads: 2,
1207 hash: "2089f3e78a799720f80af806553128f29b132cafe40d059f",
1208 },
1209 Vec3 {
1210 mode: ARGON2D,
1211 time: 2,
1212 memory: 64,
1213 threads: 2,
1214 hash: "68e2462c98b8bc6bb60ec68db418ae2c9ed24fc6748a40e9",
1215 },
1216 Vec3 {
1217 mode: ARGON2ID,
1218 time: 2,
1219 memory: 64,
1220 threads: 2,
1221 hash: "350ac37222f436ccb5c0972f1ebd3bf6b958bf2071841362",
1222 },
1223 Vec3 {
1224 mode: ARGON2I,
1225 time: 3,
1226 memory: 256,
1227 threads: 2,
1228 hash: "f5bbf5d4c3836af13193053155b73ec7476a6a2eb93fd5e6",
1229 },
1230 Vec3 {
1231 mode: ARGON2D,
1232 time: 3,
1233 memory: 256,
1234 threads: 2,
1235 hash: "f4f0669218eaf3641f39cc97efb915721102f4b128211ef2",
1236 },
1237 Vec3 {
1238 mode: ARGON2ID,
1239 time: 3,
1240 memory: 256,
1241 threads: 2,
1242 hash: "4668d30ac4187e6878eedeacf0fd83c5a0a30db2cc16ef0b",
1243 },
1244 Vec3 {
1245 mode: ARGON2I,
1246 time: 4,
1247 memory: 4096,
1248 threads: 4,
1249 hash: "a11f7b7f3f93f02ad4bddb59ab62d121e278369288a0d0e7",
1250 },
1251 Vec3 {
1252 mode: ARGON2D,
1253 time: 4,
1254 memory: 4096,
1255 threads: 4,
1256 hash: "935598181aa8dc2b720914aa6435ac8d3e3a4210c5b0fb2d",
1257 },
1258 Vec3 {
1259 mode: ARGON2ID,
1260 time: 4,
1261 memory: 4096,
1262 threads: 4,
1263 hash: "145db9733a9f4ee43edf33c509be96b934d505a4efb33c5a",
1264 },
1265 Vec3 {
1266 mode: ARGON2I,
1267 time: 4,
1268 memory: 1024,
1269 threads: 8,
1270 hash: "0cdd3956aa35e6b475a7b0c63488822f774f15b43f6e6e17",
1271 },
1272 Vec3 {
1273 mode: ARGON2D,
1274 time: 4,
1275 memory: 1024,
1276 threads: 8,
1277 hash: "83604fc2ad0589b9d055578f4d3cc55bc616df3578a896e9",
1278 },
1279 Vec3 {
1280 mode: ARGON2ID,
1281 time: 4,
1282 memory: 1024,
1283 threads: 8,
1284 hash: "8dafa8e004f8ea96bf7c0f93eecf67a6047476143d15577f",
1285 },
1286 Vec3 {
1287 mode: ARGON2I,
1288 time: 2,
1289 memory: 64,
1290 threads: 3,
1291 hash: "5cab452fe6b8479c8661def8cd703b611a3905a6d5477fe6",
1292 },
1293 Vec3 {
1294 mode: ARGON2D,
1295 time: 2,
1296 memory: 64,
1297 threads: 3,
1298 hash: "22474a423bda2ccd36ec9afd5119e5c8949798cadf659f51",
1299 },
1300 Vec3 {
1301 mode: ARGON2ID,
1302 time: 2,
1303 memory: 64,
1304 threads: 3,
1305 hash: "4a15b31aec7c2590b87d1f520be7d96f56658172deaa3079",
1306 },
1307 Vec3 {
1308 mode: ARGON2I,
1309 time: 3,
1310 memory: 1024,
1311 threads: 6,
1312 hash: "d236b29c2b2a09babee842b0dec6aa1e83ccbdea8023dced",
1313 },
1314 Vec3 {
1315 mode: ARGON2D,
1316 time: 3,
1317 memory: 1024,
1318 threads: 6,
1319 hash: "a3351b0319a53229152023d9206902f4ef59661cdca89481",
1320 },
1321 Vec3 {
1322 mode: ARGON2ID,
1323 time: 3,
1324 memory: 1024,
1325 threads: 6,
1326 hash: "1640b932f4b60e272f5d2207b9a9c626ffa1bd88d2349016",
1327 },
1328 ];
1329
1330 #[test]
1331 fn test_go_vectors() {
1332 let password = b"password";
1333 let salt = b"somesalt";
1334 for (i, v) in GO_VECTORS.iter().enumerate() {
1335 let expected = hex::decode(v.hash).unwrap();
1336 let result = derive_key_typed(
1337 v.mode,
1338 password,
1339 salt,
1340 &[],
1341 &[],
1342 v.time,
1343 v.memory,
1344 v.threads,
1345 expected.len() as u32,
1346 );
1347 assert_eq!(
1348 result, expected,
1349 "Go vector {} failed (mode={}, t={}, m={}, p={})",
1350 i, v.mode, v.time, v.memory, v.threads
1351 );
1352 }
1353 }
1354
1355 struct CVector {
1362 mode: u32,
1363 time: u32,
1364 memory: u32,
1365 threads: u32,
1366 hash: &'static str,
1367 pwd: &'static str,
1368 slt: &'static str,
1369 }
1370
1371 const C_VECTORS: &[CVector] = &[
1372 CVector {
1373 mode: ARGON2I,
1374 time: 2,
1375 memory: 65536,
1376 threads: 1,
1377 hash: "c1628832147d9720c5bd1cfd61367078729f6dfb6f8fea9ff98158e0d7816ed0",
1378 pwd: "password",
1379 slt: "somesalt",
1380 },
1381 CVector {
1382 mode: ARGON2I,
1383 time: 2,
1384 memory: 262144,
1385 threads: 1,
1386 hash: "296dbae80b807cdceaad44ae741b506f14db0959267b183b118f9b24229bc7cb",
1387 pwd: "password",
1388 slt: "somesalt",
1389 },
1390 CVector {
1391 mode: ARGON2I,
1392 time: 2,
1393 memory: 256,
1394 threads: 1,
1395 hash: "89e9029f4637b295beb027056a7336c414fadd43f6b208645281cb214a56452f",
1396 pwd: "password",
1397 slt: "somesalt",
1398 },
1399 CVector {
1400 mode: ARGON2I,
1401 time: 2,
1402 memory: 256,
1403 threads: 2,
1404 hash: "4ff5ce2769a1d7f4c8a491df09d41a9fbe90e5eb02155a13e4c01e20cd4eab61",
1405 pwd: "password",
1406 slt: "somesalt",
1407 },
1408 CVector {
1409 mode: ARGON2I,
1410 time: 1,
1411 memory: 65536,
1412 threads: 1,
1413 hash: "d168075c4d985e13ebeae560cf8b94c3b5d8a16c51916b6f4ac2da3ac11bbecf",
1414 pwd: "password",
1415 slt: "somesalt",
1416 },
1417 CVector {
1418 mode: ARGON2I,
1419 time: 4,
1420 memory: 65536,
1421 threads: 1,
1422 hash: "aaa953d58af3706ce3df1aefd4a64a84e31d7f54175231f1285259f88174ce5b",
1423 pwd: "password",
1424 slt: "somesalt",
1425 },
1426 CVector {
1427 mode: ARGON2I,
1428 time: 2,
1429 memory: 65536,
1430 threads: 1,
1431 hash: "14ae8da01afea8700c2358dcef7c5358d9021282bd88663a4562f59fb74d22ee",
1432 pwd: "differentpassword",
1433 slt: "somesalt",
1434 },
1435 CVector {
1436 mode: ARGON2I,
1437 time: 2,
1438 memory: 65536,
1439 threads: 1,
1440 hash: "b0357cccfbef91f3860b0dba447b2348cbefecadaf990abfe9cc40726c521271",
1441 pwd: "password",
1442 slt: "diffsalt",
1443 },
1444 CVector {
1445 mode: ARGON2ID,
1446 time: 2,
1447 memory: 65536,
1448 threads: 1,
1449 hash: "09316115d5cf24ed5a15a31a3ba326e5cf32edc24702987c02b6566f61913cf7",
1450 pwd: "password",
1451 slt: "somesalt",
1452 },
1453 CVector {
1454 mode: ARGON2ID,
1455 time: 2,
1456 memory: 262144,
1457 threads: 1,
1458 hash: "78fe1ec91fb3aa5657d72e710854e4c3d9b9198c742f9616c2f085bed95b2e8c",
1459 pwd: "password",
1460 slt: "somesalt",
1461 },
1462 CVector {
1463 mode: ARGON2ID,
1464 time: 2,
1465 memory: 256,
1466 threads: 1,
1467 hash: "9dfeb910e80bad0311fee20f9c0e2b12c17987b4cac90c2ef54d5b3021c68bfe",
1468 pwd: "password",
1469 slt: "somesalt",
1470 },
1471 CVector {
1472 mode: ARGON2ID,
1473 time: 2,
1474 memory: 256,
1475 threads: 2,
1476 hash: "6d093c501fd5999645e0ea3bf620d7b8be7fd2db59c20d9fff9539da2bf57037",
1477 pwd: "password",
1478 slt: "somesalt",
1479 },
1480 CVector {
1481 mode: ARGON2ID,
1482 time: 1,
1483 memory: 65536,
1484 threads: 1,
1485 hash: "f6a5adc1ba723dddef9b5ac1d464e180fcd9dffc9d1cbf76cca2fed795d9ca98",
1486 pwd: "password",
1487 slt: "somesalt",
1488 },
1489 CVector {
1490 mode: ARGON2ID,
1491 time: 4,
1492 memory: 65536,
1493 threads: 1,
1494 hash: "9025d48e68ef7395cca9079da4c4ec3affb3c8911fe4f86d1a2520856f63172c",
1495 pwd: "password",
1496 slt: "somesalt",
1497 },
1498 CVector {
1499 mode: ARGON2ID,
1500 time: 2,
1501 memory: 65536,
1502 threads: 1,
1503 hash: "0b84d652cf6b0c4beaef0dfe278ba6a80df6696281d7e0d2891b817d8c458fde",
1504 pwd: "differentpassword",
1505 slt: "somesalt",
1506 },
1507 CVector {
1508 mode: ARGON2ID,
1509 time: 2,
1510 memory: 65536,
1511 threads: 1,
1512 hash: "bdf32b05ccc42eb15d58fd19b1f856b113da1e9a5874fdcc544308565aa8141c",
1513 pwd: "password",
1514 slt: "diffsalt",
1515 },
1516 ];
1517
1518 #[test]
1519 fn test_c_reference_vectors() {
1520 for (i, v) in C_VECTORS.iter().enumerate() {
1521 let expected = hex::decode(v.hash).unwrap();
1522 let result = derive_key_typed(
1523 v.mode,
1524 v.pwd.as_bytes(),
1525 v.slt.as_bytes(),
1526 &[],
1527 &[],
1528 v.time,
1529 v.memory,
1530 v.threads,
1531 expected.len() as u32,
1532 );
1533 assert_eq!(
1534 result, expected,
1535 "C ref vector {} failed (mode={}, t={}, m={}, p={})",
1536 i, v.mode, v.time, v.memory, v.threads
1537 );
1538 }
1539 }
1540
1541 #[test]
1546 fn test_phc_encode_decode() {
1547 let params = Params {
1548 iterations: 3,
1549 memory: 65536,
1550 parallelism: 4,
1551 };
1552 let salt = b"somesalt12345678";
1553 let tag = vec![0xAB; 32];
1554 let encoded = encode_phc(¶ms, salt, &tag);
1555 assert!(encoded.starts_with("$argon2id$v=19$m=65536,t=3,p=4$"));
1556 let (dp, ds, dt) = decode_phc(&encoded).unwrap();
1557 assert_eq!(dp.iterations, 3);
1558 assert_eq!(dp.memory, 65536);
1559 assert_eq!(dp.parallelism, 4);
1560 assert_eq!(ds, salt);
1561 assert_eq!(dt, tag);
1562 }
1563
1564 #[test]
1565 fn test_hash_and_verify() {
1566 let password = b"correct horse battery staple";
1567 let salt = b"randomsalt123456";
1568 let params = Params {
1569 iterations: 1,
1570 memory: 64,
1571 parallelism: 1,
1572 };
1573 let encoded = hash_password(password, salt, ¶ms).unwrap();
1574 assert!(verify_password(password, &encoded, None).is_ok());
1575 assert_eq!(
1576 verify_password(b"wrong password", &encoded, None),
1577 Err(Argon2Error::VerifyMismatch)
1578 );
1579 }
1580
1581 #[test]
1582 fn test_decode_phc_invalid() {
1583 assert!(decode_phc("").is_err());
1584 assert!(decode_phc("$argon2i$v=19$m=4096,t=3,p=1$salt$hash").is_err());
1585 assert!(decode_phc("$argon2id$v=16$m=4096,t=3,p=1$salt$hash").is_err());
1586 assert!(decode_phc("not a phc string").is_err());
1587 }
1588
1589 #[test]
1590 fn test_decode_phc_rejects_invalid_params() {
1591 let salt = b"salt12345678";
1592 let tag = [0u8; 32];
1593
1594 let enc = encode_phc(
1596 &Params {
1597 iterations: 0,
1598 memory: 64,
1599 parallelism: 1,
1600 },
1601 salt,
1602 &tag,
1603 );
1604 assert!(matches!(decode_phc(&enc), Err(Argon2Error::InvalidParams(_))));
1605
1606 let enc = encode_phc(
1608 &Params {
1609 iterations: 1,
1610 memory: 64,
1611 parallelism: 0,
1612 },
1613 salt,
1614 &tag,
1615 );
1616 assert!(matches!(decode_phc(&enc), Err(Argon2Error::InvalidParams(_))));
1617
1618 let enc = encode_phc(
1620 &Params {
1621 iterations: 1,
1622 memory: 64,
1623 parallelism: u32::MAX,
1624 },
1625 salt,
1626 &tag,
1627 );
1628 assert!(matches!(decode_phc(&enc), Err(Argon2Error::InvalidParams(_))));
1629
1630 let enc = encode_phc(
1632 &Params {
1633 iterations: 1,
1634 memory: 4,
1635 parallelism: 1,
1636 },
1637 salt,
1638 &tag,
1639 );
1640 assert!(matches!(decode_phc(&enc), Err(Argon2Error::InvalidParams(_))));
1641
1642 let enc = encode_phc(
1644 &Params {
1645 iterations: 1,
1646 memory: 64,
1647 parallelism: 1,
1648 },
1649 salt,
1650 &[0u8; 3],
1651 );
1652 assert!(matches!(decode_phc(&enc), Err(Argon2Error::InvalidParams(_))));
1653 }
1654
1655 #[test]
1656 fn test_decode_phc_huge_parallelism_does_not_panic() {
1657 let salt = b"salt12345678";
1658 let tag = [0u8; 32];
1659 let enc = encode_phc(
1662 &Params {
1663 iterations: 1,
1664 memory: u32::MAX,
1665 parallelism: u32::MAX,
1666 },
1667 salt,
1668 &tag,
1669 );
1670 assert!(decode_phc(&enc).is_err());
1671 assert!(verify_password(b"password", &enc, None).is_err());
1672 }
1673
1674 #[test]
1675 fn test_verify_password_limits() {
1676 let password = b"correct horse battery staple";
1677 let salt = b"randomsalt123456";
1678 let params = Params {
1679 iterations: 2,
1680 memory: 64,
1681 parallelism: 1,
1682 };
1683 let encoded = hash_password(password, salt, ¶ms).unwrap();
1684
1685 assert!(verify_password(password, &encoded, None).is_ok());
1687
1688 assert!(verify_password(password, &encoded, Some(¶ms)).is_ok());
1690
1691 for limits in [
1693 Params {
1694 iterations: 1,
1695 memory: 64,
1696 parallelism: 1,
1697 },
1698 Params {
1699 iterations: 2,
1700 memory: 32,
1701 parallelism: 1,
1702 },
1703 Params {
1704 iterations: 2,
1705 memory: 64,
1706 parallelism: 0,
1707 },
1708 ] {
1709 assert!(matches!(
1710 verify_password(password, &encoded, Some(&limits)),
1711 Err(Argon2Error::InvalidParams(_))
1712 ));
1713 }
1714 }
1715
1716 #[test]
1717 fn test_verify_password_limits_reject_before_allocating() {
1718 let salt = b"salt12345678";
1719 let tag = [0u8; 32];
1720 let hostile = encode_phc(
1723 &Params {
1724 iterations: 1,
1725 memory: u32::MAX,
1726 parallelism: 1,
1727 },
1728 salt,
1729 &tag,
1730 );
1731 let limits = Params {
1732 iterations: 1,
1733 memory: 64,
1734 parallelism: 1,
1735 };
1736 assert!(matches!(
1737 verify_password(b"password", &hostile, Some(&limits)),
1738 Err(Argon2Error::InvalidParams(_))
1739 ));
1740 }
1741
1742 #[cfg(target_pointer_width = "64")]
1743 #[test]
1744 fn test_check_u32_len() {
1745 assert!(check_u32_len(usize::MAX, "too long").is_err());
1746 assert!(check_u32_len(u32::MAX as usize, "ok").is_ok());
1747 assert!(check_u32_len(0, "ok").is_ok());
1748 }
1749
1750 #[test]
1751 fn test_invalid_params() {
1752 let mut out = [0u8; 32];
1753 assert!(
1754 derive_key(
1755 &mut out,
1756 b"password",
1757 b"salt",
1758 &[],
1759 &[],
1760 &Params {
1761 iterations: 0,
1762 memory: 64,
1763 parallelism: 1
1764 }
1765 )
1766 .is_err()
1767 );
1768 assert!(
1769 derive_key(
1770 &mut out,
1771 b"password",
1772 b"salt",
1773 &[],
1774 &[],
1775 &Params {
1776 iterations: 1,
1777 memory: 4,
1778 parallelism: 1
1779 }
1780 )
1781 .is_err()
1782 );
1783 let mut short = [0u8; 3];
1785 assert!(
1786 derive_key(
1787 &mut short,
1788 b"password",
1789 b"salt",
1790 &[],
1791 &[],
1792 &Params {
1793 iterations: 1,
1794 memory: 64,
1795 parallelism: 1
1796 }
1797 )
1798 .is_err()
1799 );
1800 }
1801
1802 #[test]
1803 fn test_variable_length_hash_short() {
1804 let input = b"test input";
1805 let r32 = variable_length_hash_vec(input, 32);
1806 assert_eq!(r32.len(), 32);
1807 assert_eq!(variable_length_hash_vec(input, 32), r32);
1808 let r48 = variable_length_hash_vec(input, 48);
1809 assert_eq!(r48.len(), 48);
1810 assert_ne!(&r32[..], &r48[..32]);
1811 }
1812
1813 #[test]
1814 fn test_variable_length_hash_long() {
1815 assert_eq!(variable_length_hash_vec(b"test input for long hash", 128).len(), 128);
1816 assert_eq!(variable_length_hash_vec(b"test input for long hash", 1024).len(), 1024);
1817 }
1818
1819 #[test]
1820 fn test_argon2id_min_memory() {
1821 let result = derive_key_vec(
1822 32,
1823 b"password",
1824 b"saltsalt",
1825 &[],
1826 &[],
1827 &Params {
1828 iterations: 1,
1829 memory: 8,
1830 parallelism: 1,
1831 },
1832 );
1833 assert_eq!(result.len(), 32);
1834 }
1835
1836 #[test]
1837 fn test_argon2id_multiple_lanes() {
1838 let result = derive_key_vec(
1839 32,
1840 b"password",
1841 b"saltsaltsaltsalt",
1842 &[],
1843 &[],
1844 &Params {
1845 iterations: 1,
1846 memory: 64,
1847 parallelism: 4,
1848 },
1849 );
1850 assert_eq!(result.len(), 32);
1851 }
1852
1853 #[test]
1854 fn test_different_passwords() {
1855 let p = Params {
1856 iterations: 1,
1857 memory: 64,
1858 parallelism: 1,
1859 };
1860 assert_ne!(
1861 derive_key_vec(32, b"password1", b"saltsaltsaltsalt", &[], &[], &p),
1862 derive_key_vec(32, b"password2", b"saltsaltsaltsalt", &[], &[], &p)
1863 );
1864 }
1865
1866 #[test]
1867 fn test_different_salts() {
1868 let p = Params {
1869 iterations: 1,
1870 memory: 64,
1871 parallelism: 1,
1872 };
1873 assert_ne!(
1874 derive_key_vec(32, b"password", b"salt1234salt1234", &[], &[], &p),
1875 derive_key_vec(32, b"password", b"salt5678salt5678", &[], &[], &p)
1876 );
1877 }
1878
1879 #[test]
1880 fn test_long_tag() {
1881 let result = derive_key_vec(
1882 64,
1883 b"password",
1884 b"saltsaltsaltsalt",
1885 &[],
1886 &[],
1887 &Params {
1888 iterations: 1,
1889 memory: 64,
1890 parallelism: 1,
1891 },
1892 );
1893 assert_eq!(result.len(), 64);
1894 }
1895
1896 #[test]
1897 fn test_phc_roundtrip() {
1898 let password = b"password";
1899 let salt = b"somesalt";
1900 let params = Params {
1901 iterations: 1,
1902 memory: 64,
1903 parallelism: 1,
1904 };
1905 let tag = derive_key_vec(24, password, salt, &[], &[], ¶ms);
1906 let encoded = encode_phc(¶ms, salt, &tag);
1907 let (dp, ds, dt) = decode_phc(&encoded).unwrap();
1908 assert_eq!(dp.memory, params.memory);
1909 assert_eq!(dp.iterations, params.iterations);
1910 assert_eq!(dp.parallelism, params.parallelism);
1911 assert_eq!(ds, salt);
1912 assert_eq!(dt, tag);
1913 }
1914
1915 fn argon2_core_with_passes(
1923 argon_type: u32,
1924 password: &[u8],
1925 salt: &[u8],
1926 secret: &[u8],
1927 ad: &[u8],
1928 params: &Params,
1929 ) -> Vec<Vec<Block>> {
1930 let p = params.parallelism;
1931 let t = params.iterations;
1932 let m = params.memory;
1933 let tag_length = 32u32;
1934
1935 let h0 = compute_h0(argon_type, password, salt, secret, ad, p, tag_length, m, t);
1936 let m_prime = 4 * p * (m / (4 * p));
1937 let q = m_prime / p;
1938
1939 let mem = Memory::uninit(m_prime as usize);
1940
1941 let mut input = [0u8; 72];
1942 input[..64].copy_from_slice(&h0);
1943 let mut block_bytes = [0u8; BLOCK_SIZE];
1944 for i in 0..p {
1945 input[68..72].copy_from_slice(&i.to_le_bytes());
1946
1947 input[64..68].copy_from_slice(&0u32.to_le_bytes());
1948 variable_length_hash_into(&input, &mut block_bytes);
1949 mem.write((i * q) as usize, Block::from_bytes(&block_bytes));
1950
1951 input[64..68].copy_from_slice(&1u32.to_le_bytes());
1952 variable_length_hash_into(&input, &mut block_bytes);
1953 mem.write((i * q + 1) as usize, Block::from_bytes(&block_bytes));
1954 }
1955
1956 let mut pass_snapshots = Vec::new();
1957 for pass in 0..t {
1958 for slice in 0..SYNC_POINTS {
1959 for lane in 0..p {
1960 fill_segment(Backend::Scalar, &mem, argon_type, pass, lane, slice, p, q, t, m_prime);
1961 }
1962 }
1963 pass_snapshots.push((0..mem.len()).map(|i| mem.get(i).clone()).collect());
1964 }
1965
1966 pass_snapshots
1967 }
1968
1969 fn block0_word(block: &Block, idx: usize) -> String {
1970 format!("{:016x}", block.v[idx])
1971 }
1972
1973 fn block_last_word(block: &Block, idx: usize) -> String {
1974 format!("{:016x}", block.v[idx])
1975 }
1976
1977 #[test]
1978 fn test_rfc9106_argon2d_intermediate_blocks() {
1979 let pwd = vec![0x01u8; 32];
1980 let salt = vec![0x02u8; 16];
1981 let secret = vec![0x03u8; 8];
1982 let ad = vec![0x04u8; 12];
1983 let params = Params {
1984 iterations: 3,
1985 memory: 32,
1986 parallelism: 4,
1987 };
1988 let passes = argon2_core_with_passes(ARGON2D, &pwd, &salt, &secret, &ad, ¶ms);
1989
1990 let p = params.parallelism;
1991 let q = (4 * p * (params.memory / (4 * p))) / p;
1992 let m_prime = p * q;
1993
1994 assert_eq!(block0_word(&passes[0][0], 0), "db2fea6b2c6f5c8a");
1995 assert_eq!(block_last_word(&passes[0][(m_prime - 1) as usize], 127), "6a6c49d2cb75d5b6");
1996
1997 assert_eq!(block0_word(&passes[1][0], 0), "d3801200410f8c0d");
1998 assert_eq!(block_last_word(&passes[1][(m_prime - 1) as usize], 127), "2dbfff23f31b5883");
1999
2000 assert_eq!(block0_word(&passes[2][0], 0), "5f047b575c5ff4d2");
2001 assert_eq!(block_last_word(&passes[2][(m_prime - 1) as usize], 127), "c341b3ca45c10da5");
2002 }
2003
2004 #[test]
2005 fn test_rfc9106_argon2i_intermediate_blocks() {
2006 let pwd = vec![0x01u8; 32];
2007 let salt = vec![0x02u8; 16];
2008 let secret = vec![0x03u8; 8];
2009 let ad = vec![0x04u8; 12];
2010 let params = Params {
2011 iterations: 3,
2012 memory: 32,
2013 parallelism: 4,
2014 };
2015 let passes = argon2_core_with_passes(ARGON2I, &pwd, &salt, &secret, &ad, ¶ms);
2016
2017 let p = params.parallelism;
2018 let q = (4 * p * (params.memory / (4 * p))) / p;
2019 let m_prime = p * q;
2020
2021 assert_eq!(block0_word(&passes[0][0], 0), "f8f9e84545db08f6");
2022 assert_eq!(block_last_word(&passes[0][(m_prime - 1) as usize], 127), "c570f2ab2a86cf00");
2023
2024 assert_eq!(block0_word(&passes[1][0], 0), "b2e4ddfcf76dc85a");
2025 assert_eq!(block_last_word(&passes[1][(m_prime - 1) as usize], 127), "421b3c6e9555b79d");
2026
2027 assert_eq!(block0_word(&passes[2][0], 0), "af2a8bd8482c2f11");
2028 assert_eq!(block_last_word(&passes[2][(m_prime - 1) as usize], 127), "71e436f035f30ed0");
2029 }
2030
2031 #[test]
2036 fn test_h0_argon2d() {
2037 let pwd = vec![0x01u8; 32];
2038 let salt = vec![0x02u8; 16];
2039 let secret = vec![0x03u8; 8];
2040 let ad = vec![0x04u8; 12];
2041 let h0 = compute_h0(ARGON2D, &pwd, &salt, &secret, &ad, 4, 32, 32, 3);
2042 let expected = "b8819791a0359660bb7709c85fa48f04d5d82c05c5f215ccdb885491717cf757082c28b951be381410b5fc2eb7274033b9fdc7ae672bcaac5d179097a4af3109";
2043 assert_eq!(hex::encode(h0), expected);
2044 }
2045
2046 #[test]
2047 fn test_h0_argon2i() {
2048 let pwd = vec![0x01u8; 32];
2049 let salt = vec![0x02u8; 16];
2050 let secret = vec![0x03u8; 8];
2051 let ad = vec![0x04u8; 12];
2052 let h0 = compute_h0(ARGON2I, &pwd, &salt, &secret, &ad, 4, 32, 32, 3);
2053 let expected = "c46065815276a0b3e731731c902f1fd80cf776907fbb7b6a5ca72e7b56011feeca446c86dd75b9469a5e6879dec4b72d0863fb939b982e5f397cc7d164fddaa9";
2054 assert_eq!(hex::encode(h0), expected);
2055 }
2056
2057 #[test]
2062 fn test_argon2id_empty_secret_and_ad() {
2063 let params = Params {
2064 iterations: 1,
2065 memory: 64,
2066 parallelism: 1,
2067 };
2068 let result = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], ¶ms);
2069 assert_eq!(result.len(), 32);
2070 let result2 = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], ¶ms);
2071 assert_eq!(result, result2);
2072 }
2073
2074 #[test]
2075 fn test_argon2id_with_secret() {
2076 let p = Params {
2077 iterations: 1,
2078 memory: 64,
2079 parallelism: 1,
2080 };
2081 let without_secret = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], &p);
2082 let with_secret = derive_key_vec(32, b"password", b"saltsaltsaltsalt", b"secret", &[], &p);
2083 assert_ne!(without_secret, with_secret);
2084 }
2085
2086 #[test]
2087 fn test_argon2id_with_ad() {
2088 let p = Params {
2089 iterations: 1,
2090 memory: 64,
2091 parallelism: 1,
2092 };
2093 let without_ad = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], &p);
2094 let with_ad = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], b"associated data", &p);
2095 assert_ne!(without_ad, with_ad);
2096 }
2097
2098 #[test]
2099 fn test_argon2id_tag_length_4() {
2100 let result = derive_key_vec(
2101 4,
2102 b"password",
2103 b"saltsaltsaltsalt",
2104 &[],
2105 &[],
2106 &Params {
2107 iterations: 1,
2108 memory: 64,
2109 parallelism: 1,
2110 },
2111 );
2112 assert_eq!(result.len(), 4);
2113 }
2114
2115 #[test]
2116 fn test_argon2id_tag_length_128() {
2117 let result = derive_key_vec(
2118 128,
2119 b"password",
2120 b"saltsaltsaltsalt",
2121 &[],
2122 &[],
2123 &Params {
2124 iterations: 1,
2125 memory: 64,
2126 parallelism: 1,
2127 },
2128 );
2129 assert_eq!(result.len(), 128);
2130 }
2131
2132 #[test]
2133 fn test_argon2id_tag_length_256() {
2134 let result = derive_key_vec(
2135 256,
2136 b"password",
2137 b"saltsaltsaltsalt",
2138 &[],
2139 &[],
2140 &Params {
2141 iterations: 1,
2142 memory: 64,
2143 parallelism: 1,
2144 },
2145 );
2146 assert_eq!(result.len(), 256);
2147 }
2148
2149 #[test]
2150 fn test_argon2id_long_tag_consistency() {
2151 let p = Params {
2152 iterations: 1,
2153 memory: 64,
2154 parallelism: 1,
2155 };
2156 let r1 = derive_key_vec(100, b"password", b"saltsaltsaltsalt", &[], &[], &p);
2157 let r2 = derive_key_vec(100, b"password", b"saltsaltsaltsalt", &[], &[], &p);
2158 assert_eq!(r1, r2);
2159 assert_eq!(r1.len(), 100);
2160 }
2161
2162 #[test]
2163 fn test_argon2i_long_tag_consistency() {
2164 let params = Params {
2165 iterations: 1,
2166 memory: 64,
2167 parallelism: 1,
2168 };
2169 let mut r1 = [0u8; 100];
2170 let mut r2 = [0u8; 100];
2171 argon2_core(ARGON2I, b"password", b"saltsaltsaltsalt", &[], &[], ¶ms, &mut r1).unwrap();
2172 argon2_core(ARGON2I, b"password", b"saltsaltsaltsalt", &[], &[], ¶ms, &mut r2).unwrap();
2173 assert_eq!(r1, r2);
2174 }
2175
2176 #[test]
2177 fn test_argon2d_long_tag_consistency() {
2178 let params = Params {
2179 iterations: 1,
2180 memory: 64,
2181 parallelism: 1,
2182 };
2183 let mut r1 = [0u8; 100];
2184 let mut r2 = [0u8; 100];
2185 argon2_core(ARGON2D, b"password", b"saltsaltsaltsalt", &[], &[], ¶ms, &mut r1).unwrap();
2186 argon2_core(ARGON2D, b"password", b"saltsaltsaltsalt", &[], &[], ¶ms, &mut r2).unwrap();
2187 assert_eq!(r1, r2);
2188 }
2189
2190 #[test]
2191 fn test_argon2id_single_pass() {
2192 let result = derive_key_vec(
2193 32,
2194 b"password",
2195 b"saltsalt",
2196 &[],
2197 &[],
2198 &Params {
2199 iterations: 1,
2200 memory: 32,
2201 parallelism: 1,
2202 },
2203 );
2204 assert_eq!(result.len(), 32);
2205 }
2206
2207 #[test]
2208 fn test_argon2id_high_parallelism() {
2209 let result = derive_key_vec(
2210 32,
2211 b"password",
2212 b"saltsaltsaltsalt",
2213 &[],
2214 &[],
2215 &Params {
2216 iterations: 1,
2217 memory: 64,
2218 parallelism: 8,
2219 },
2220 );
2221 assert_eq!(result.len(), 32);
2222 }
2223
2224 #[test]
2225 fn test_argon2d_rfc_h0() {
2226 let pwd = vec![0x01u8; 32];
2227 let salt = vec![0x02u8; 16];
2228 let secret = vec![0x03u8; 8];
2229 let ad = vec![0x04u8; 12];
2230 let h0 = compute_h0(ARGON2D, &pwd, &salt, &secret, &ad, 4, 32, 32, 3);
2231 assert_eq!(h0[0], 0xb8);
2232 assert_eq!(h0[1], 0x81);
2233 assert_eq!(h0[63], 0x09);
2234 }
2235
2236 #[test]
2237 fn test_variable_length_hash_exact_64() {
2238 let input = b"test";
2239 let result = variable_length_hash_vec(input, 64);
2240 assert_eq!(result.len(), 64);
2241 }
2242
2243 #[test]
2244 fn test_variable_length_hash_65_bytes() {
2245 let input = b"test";
2246 let result = variable_length_hash_vec(input, 65);
2247 assert_eq!(result.len(), 65);
2248 let result2 = variable_length_hash_vec(input, 65);
2249 assert_eq!(result, result2);
2250 }
2251
2252 #[test]
2253 fn test_variable_length_hash_deterministic() {
2254 for len in [4, 16, 32, 48, 64, 65, 96, 128, 256, 512, 1024] {
2255 let r1 = variable_length_hash_vec(b"determinism test", len);
2256 let r2 = variable_length_hash_vec(b"determinism test", len);
2257 assert_eq!(r1, r2, "variable_length_hash not deterministic for len={}", len);
2258 assert_eq!(r1.len(), len);
2259 }
2260 }
2261
2262 #[test]
2263 fn test_compress_deterministic() {
2264 let a = Block::from_bytes(&[0xAA; BLOCK_SIZE]);
2265 let b = Block::from_bytes(&[0xBB; BLOCK_SIZE]);
2266 let c1 = compress(&a, &b);
2267 let c2 = compress(&a, &b);
2268 assert_eq!(c1.v, c2.v);
2269 }
2270
2271 #[test]
2272 fn test_compress_xor_symmetry() {
2273 let a = Block::from_bytes(&[0x11; BLOCK_SIZE]);
2274 let b = Block::from_bytes(&[0x22; BLOCK_SIZE]);
2275 let c_ab = compress(&a, &b);
2276 let c_ba = compress(&b, &a);
2277 assert_eq!(c_ab.v, c_ba.v, "G(X,Y) should equal G(Y,X) since R = X XOR Y is symmetric");
2278 }
2279
2280 #[test]
2281 fn test_block_from_bytes_roundtrip() {
2282 let original = [0x42u8; BLOCK_SIZE];
2283 let block = Block::from_bytes(&original);
2284 let recovered = block.to_bytes();
2285 assert_eq!(original, recovered);
2286 }
2287
2288 #[test]
2289 fn test_argon2id_different_iterations() {
2290 let p1 = Params {
2291 iterations: 1,
2292 memory: 64,
2293 parallelism: 1,
2294 };
2295 let p2 = Params {
2296 iterations: 2,
2297 memory: 64,
2298 parallelism: 1,
2299 };
2300 let r1 = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], &p1);
2301 let r2 = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], &p2);
2302 assert_ne!(r1, r2);
2303 }
2304
2305 #[test]
2306 fn test_argon2id_different_memory() {
2307 let p1 = Params {
2308 iterations: 1,
2309 memory: 64,
2310 parallelism: 1,
2311 };
2312 let p2 = Params {
2313 iterations: 1,
2314 memory: 128,
2315 parallelism: 1,
2316 };
2317 let r1 = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], &p1);
2318 let r2 = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], &p2);
2319 assert_ne!(r1, r2);
2320 }
2321
2322 #[test]
2323 fn test_argon2id_different_parallelisms() {
2324 let p1 = Params {
2325 iterations: 1,
2326 memory: 64,
2327 parallelism: 1,
2328 };
2329 let p2 = Params {
2330 iterations: 1,
2331 memory: 64,
2332 parallelism: 2,
2333 };
2334 let r1 = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], &p1);
2335 let r2 = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], &p2);
2336 assert_ne!(r1, r2);
2337 }
2338
2339 #[test]
2340 fn test_argon2i_rfc9106_tag() {
2341 let pwd = vec![0x01u8; 32];
2342 let salt = vec![0x02u8; 16];
2343 let secret = vec![0x03u8; 8];
2344 let ad = vec![0x04u8; 12];
2345 let expected = hex::decode("c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8").unwrap();
2346 let result = derive_key_typed(ARGON2I, &pwd, &salt, &secret, &ad, 3, 32, 4, 32);
2347 assert_eq!(result, expected);
2348 }
2349
2350 #[test]
2351 fn test_argon2d_rfc9106_tag() {
2352 let pwd = vec![0x01u8; 32];
2353 let salt = vec![0x02u8; 16];
2354 let secret = vec![0x03u8; 8];
2355 let ad = vec![0x04u8; 12];
2356 let expected = hex::decode("512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb").unwrap();
2357 let result = derive_key_typed(ARGON2D, &pwd, &salt, &secret, &ad, 3, 32, 4, 32);
2358 assert_eq!(result, expected);
2359 }
2360
2361 #[test]
2362 fn test_phc_verify_known() {
2363 let password = b"password";
2364 let salt = b"randomsalt123456";
2365 let params = Params {
2366 iterations: 1,
2367 memory: 64,
2368 parallelism: 1,
2369 };
2370 let encoded = hash_password(password, salt, ¶ms).unwrap();
2371 assert!(verify_password(password, &encoded, None).is_ok());
2372 assert_eq!(verify_password(b"wrong", &encoded, None), Err(Argon2Error::VerifyMismatch));
2373 }
2374
2375 #[test]
2376 fn test_decode_phc_roundtrip_all_types() {
2377 for tag_len in [4, 16, 32, 64] {
2378 let params = Params {
2379 iterations: 1,
2380 memory: 64,
2381 parallelism: 1,
2382 };
2383 let salt = b"testsalt12345678";
2384 let tag = vec![0xAB; tag_len as usize];
2385 let encoded = encode_phc(¶ms, salt, &tag);
2386 let (dp, ds, dt) = decode_phc(&encoded).unwrap();
2387 assert_eq!(dp.memory, 64);
2388 assert_eq!(dp.iterations, 1);
2389 assert_eq!(dp.parallelism, 1);
2390 assert_eq!(ds, salt);
2391 assert_eq!(dt, tag);
2392 }
2393 }
2394
2395 #[test]
2396 fn test_index_alpha_pass0_slice0() {
2397 let result = index_alpha(0, 0, 4, 2, 2, 8, true, 0xFFFFFFFF);
2398 assert!(result < 8);
2399 }
2400
2401 #[test]
2402 fn test_index_alpha_reference_area_size_zero() {
2403 let result = index_alpha(0, 0, 4, 2, 0, 8, true, 0xFFFFFFFF);
2404 assert_eq!(result, 0);
2405 }
2406
2407 #[test]
2408 fn test_permutation_p_changes_values() {
2409 let mut v = [0u64; 16];
2410 v[0] = 1;
2411 v[1] = 2;
2412 v[2] = 3;
2413 v[3] = 4;
2414 permutation_p(&mut v);
2415 assert_ne!(v[0], 1);
2416 assert_ne!(v[1], 2);
2417 assert_ne!(v[2], 3);
2418 assert_ne!(v[3], 4);
2419 }
2420
2421 #[test]
2422 fn test_permutation_p_deterministic() {
2423 let mut v1 = [0u64; 16];
2424 for (i, word) in v1.iter_mut().enumerate() {
2425 *word = i as u64;
2426 }
2427 let mut v2 = v1;
2428 permutation_p(&mut v1);
2429 permutation_p(&mut v2);
2430 assert_eq!(v1, v2);
2431 }
2432}