1#[cfg(feature = "alloc")]
7extern crate alloc;
8
9#[cfg(feature = "alloc")]
10use alloc::{string::String, vec, vec::Vec};
11
12use crate::{Hasher, blake2::Blake2b};
13
14const VERSION: u32 = 0x13;
16
17const SYNC_POINTS: u32 = 4;
19
20const BLOCK_SIZE: usize = 1024;
22
23#[allow(dead_code)]
25const ARGON2D: u32 = 0;
26const ARGON2I: u32 = 1;
27const ARGON2ID: u32 = 2;
28
29#[derive(Debug, Clone)]
31pub struct Params {
32 pub t_cost: u32,
34 pub m_cost: u32,
36 pub p_cost: u32,
38 pub tag_length: u32,
40}
41
42impl Params {
43 pub fn new(t_cost: u32, m_cost: u32, p_cost: u32, tag_length: u32) -> Self {
45 Params {
46 t_cost,
47 m_cost,
48 p_cost,
49 tag_length,
50 }
51 }
52}
53
54impl Default for Params {
55 fn default() -> Self {
57 Params {
58 t_cost: 3,
59 m_cost: 65536,
60 p_cost: 4,
61 tag_length: 32,
62 }
63 }
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
68#[cfg(feature = "alloc")]
69pub enum Argon2Error {
70 InvalidParams(&'static str),
72 InvalidEncoding(&'static str),
74 VerifyMismatch,
76}
77
78#[cfg(feature = "alloc")]
79impl core::fmt::Display for Argon2Error {
80 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
81 match self {
82 Argon2Error::InvalidParams(msg) => write!(f, "argon2: invalid params: {}", msg),
83 Argon2Error::InvalidEncoding(msg) => write!(f, "argon2: invalid encoding: {}", msg),
84 Argon2Error::VerifyMismatch => write!(f, "argon2: verification failed"),
85 }
86 }
87}
88
89#[derive(Clone)]
91struct Block {
92 v: [u64; 128],
93}
94
95impl Block {
96 fn zero() -> Self {
97 Block {
98 v: [0u64; 128],
99 }
100 }
101
102 fn xor_with(&mut self, other: &Block) {
103 for i in 0..128 {
104 self.v[i] ^= other.v[i];
105 }
106 }
107
108 fn from_bytes(bytes: &[u8]) -> Self {
109 let mut block = Block::zero();
110 for i in 0..128 {
111 let offset = i * 8;
112 block.v[i] = u64::from_le_bytes([
113 bytes[offset],
114 bytes[offset + 1],
115 bytes[offset + 2],
116 bytes[offset + 3],
117 bytes[offset + 4],
118 bytes[offset + 5],
119 bytes[offset + 6],
120 bytes[offset + 7],
121 ]);
122 }
123 block
124 }
125
126 fn to_bytes(&self) -> [u8; BLOCK_SIZE] {
127 let mut out = [0u8; BLOCK_SIZE];
128 for i in 0..128 {
129 let bytes = self.v[i].to_le_bytes();
130 let offset = i * 8;
131 out[offset..offset + 8].copy_from_slice(&bytes);
132 }
133 out
134 }
135}
136
137#[cfg(feature = "alloc")]
155pub fn derive_key(
156 password: &[u8],
157 salt: &[u8],
158 secret: &[u8],
159 ad: &[u8],
160 params: &Params,
161) -> Result<Vec<u8>, Argon2Error> {
162 argon2_core(ARGON2ID, password, salt, secret, ad, params)
163}
164
165#[cfg(feature = "alloc")]
167fn argon2_core(
168 argon_type: u32,
169 password: &[u8],
170 salt: &[u8],
171 secret: &[u8],
172 ad: &[u8],
173 params: &Params,
174) -> Result<Vec<u8>, Argon2Error> {
175 if params.t_cost < 1 {
177 return Err(Argon2Error::InvalidParams("t_cost must be >= 1"));
178 }
179 if params.p_cost < 1 {
180 return Err(Argon2Error::InvalidParams("p_cost must be >= 1"));
181 }
182 if params.tag_length < 4 {
183 return Err(Argon2Error::InvalidParams("tag_length must be >= 4"));
184 }
185 if params.m_cost < 8 * params.p_cost {
186 return Err(Argon2Error::InvalidParams("m_cost must be >= 8*p_cost"));
187 }
188
189 let p = params.p_cost;
190 let t = params.t_cost;
191 let m = params.m_cost;
192 let tag_length = params.tag_length;
193
194 let h0 = compute_h0(argon_type, password, salt, secret, ad, p, tag_length, m, t);
196
197 let m_prime = 4 * p * (m / (4 * p));
199 let q = m_prime / p; let mut memory: Vec<Block> = vec![Block::zero(); m_prime as usize];
203
204 for i in 0..p {
206 let mut input = Vec::with_capacity(72);
208 input.extend_from_slice(&h0);
209 input.extend_from_slice(&0u32.to_le_bytes());
210 input.extend_from_slice(&i.to_le_bytes());
211 let block_bytes = variable_length_hash(&input, BLOCK_SIZE as u32);
212 memory[(i * q) as usize] = Block::from_bytes(&block_bytes);
213
214 let mut input = Vec::with_capacity(72);
216 input.extend_from_slice(&h0);
217 input.extend_from_slice(&1u32.to_le_bytes());
218 input.extend_from_slice(&i.to_le_bytes());
219 let block_bytes = variable_length_hash(&input, BLOCK_SIZE as u32);
220 memory[(i * q + 1) as usize] = Block::from_bytes(&block_bytes);
221 }
222
223 for pass in 0..t {
225 for slice in 0..SYNC_POINTS {
226 for lane in 0..p {
227 fill_segment(&mut memory, argon_type, pass, lane, slice, p, q, t, m_prime);
228 }
229 }
230 }
231
232 let mut final_block = memory[(q - 1) as usize].clone();
234 for i in 1..p {
235 let idx = (i * q + q - 1) as usize;
236 final_block.xor_with(&memory[idx]);
237 }
238
239 let final_bytes = final_block.to_bytes();
241 let tag = variable_length_hash(&final_bytes, tag_length);
242
243 Ok(tag)
244}
245
246#[cfg(feature = "alloc")]
248fn fill_segment(
249 memory: &mut [Block],
250 argon_type: u32,
251 pass: u32,
252 lane: u32,
253 slice: u32,
254 lanes: u32,
255 q: u32, t: u32, m_prime: u32, ) {
259 let segment_length = q / SYNC_POINTS;
260
261 let mut pseudo_rands: Vec<u64> = Vec::new();
263 let need_pseudo_rands = argon_type == ARGON2I || (argon_type == ARGON2ID && pass == 0 && slice < 2);
264
265 if need_pseudo_rands {
266 pseudo_rands = generate_addresses(pass, lane, slice, lanes, t, argon_type, m_prime, segment_length);
267 }
268
269 let start_index = if pass == 0 && slice == 0 { 2 } else { 0 };
270
271 for s in start_index..segment_length {
272 let j = slice * segment_length + s; let cur_index = (lane * q + j) as usize;
274
275 let prev_index = if j == 0 {
277 (lane * q + q - 1) as usize
278 } else {
279 (lane * q + j - 1) as usize
280 };
281
282 let (j1, j2) = if need_pseudo_rands {
284 let val = pseudo_rands[s as usize];
285 ((val & 0xFFFFFFFF) as u32, (val >> 32) as u32)
286 } else {
287 let prev = &memory[prev_index];
289 (prev.v[0] as u32, (prev.v[0] >> 32) as u32)
290 };
291
292 let ref_lane = if pass == 0 && slice == 0 { lane } else { j2 % lanes };
294
295 let ref_index = index_alpha(pass, slice, lanes, segment_length, s, q, ref_lane == lane, j1);
296 let ref_block_index = (ref_lane * q + ref_index) as usize;
297
298 let new_block = if pass == 0 {
300 compress(&memory[prev_index], &memory[ref_block_index])
301 } else {
302 let mut new = compress(&memory[prev_index], &memory[ref_block_index]);
303 new.xor_with(&memory[cur_index]);
304 new
305 };
306
307 memory[cur_index] = new_block;
308 }
309}
310
311#[cfg(feature = "alloc")]
313fn generate_addresses(
314 pass: u32,
315 lane: u32,
316 slice: u32,
317 _lanes: u32,
318 t: u32,
319 argon_type: u32,
320 m_prime: u32,
321 segment_length: u32,
322) -> Vec<u64> {
323 let mut pseudo_rands = Vec::with_capacity(segment_length as usize);
324
325 let mut input = Block::zero();
327 input.v[0] = pass as u64;
328 input.v[1] = lane as u64;
329 input.v[2] = slice as u64;
330 input.v[3] = m_prime as u64;
331 input.v[4] = t as u64;
332 input.v[5] = argon_type as u64;
333
334 let zero_block = Block::zero();
335 let mut counter = 1u64;
337 while pseudo_rands.len() < segment_length as usize {
338 input.v[6] = counter;
339 let tmp = compress(&zero_block, &input);
340 let addr_block = compress(&zero_block, &tmp);
341
342 for i in 0..128 {
343 if pseudo_rands.len() >= segment_length as usize {
344 break;
345 }
346 pseudo_rands.push(addr_block.v[i]);
347 }
348 counter += 1;
349 }
350
351 pseudo_rands
352}
353
354fn index_alpha(
356 pass: u32,
357 slice: u32,
358 _lanes: u32,
359 segment_length: u32,
360 index_in_segment: u32,
361 q: u32,
362 same_lane: bool,
363 j1: u32,
364) -> u32 {
365 let reference_area_size = if pass == 0 {
367 if slice == 0 {
369 index_in_segment.saturating_sub(1)
371 } else {
372 if same_lane {
373 slice * segment_length + index_in_segment - 1
374 } else {
375 slice * segment_length - if index_in_segment == 0 { 1 } else { 0 }
376 }
377 }
378 } else {
379 if same_lane {
381 q - segment_length + index_in_segment - 1
382 } else {
383 q - segment_length - if index_in_segment == 0 { 1 } else { 0 }
384 }
385 };
386
387 if reference_area_size == 0 {
388 return 0;
389 }
390
391 let j1_64 = j1 as u64;
393 let x = (j1_64 * j1_64) >> 32;
394 let y = (reference_area_size as u64 * x) >> 32;
395 let relative_position = (reference_area_size as u64 - 1 - y) as u32;
396
397 let start_position = if pass == 0 {
399 0
400 } else {
401 if slice == SYNC_POINTS - 1 {
402 0
403 } else {
404 (slice + 1) * segment_length
405 }
406 };
407
408 (start_position + relative_position) % q
409}
410
411#[cfg(feature = "alloc")]
413fn compute_h0(
414 argon_type: u32,
415 password: &[u8],
416 salt: &[u8],
417 secret: &[u8],
418 ad: &[u8],
419 p: u32,
420 tag_length: u32,
421 m: u32,
422 t: u32,
423) -> [u8; 64] {
424 let mut blake = Blake2b::new_keyed(&[], 64);
425
426 blake.update(&p.to_le_bytes());
427 blake.update(&tag_length.to_le_bytes());
428 blake.update(&m.to_le_bytes());
429 blake.update(&t.to_le_bytes());
430 blake.update(&VERSION.to_le_bytes());
431 blake.update(&argon_type.to_le_bytes());
432 blake.update(&(password.len() as u32).to_le_bytes());
433 blake.update(password);
434 blake.update(&(salt.len() as u32).to_le_bytes());
435 blake.update(salt);
436 blake.update(&(secret.len() as u32).to_le_bytes());
437 blake.update(secret);
438 blake.update(&(ad.len() as u32).to_le_bytes());
439 blake.update(ad);
440
441 let hash = blake.sum();
442 let mut result = [0u8; 64];
443 result.copy_from_slice(&hash.as_ref()[..64]);
444 result
445}
446
447#[cfg(feature = "alloc")]
451fn variable_length_hash(input: &[u8], tag_length: u32) -> Vec<u8> {
452 if tag_length <= 64 {
453 let mut blake = Blake2b::new_keyed(&[], tag_length as usize);
455 blake.update(&tag_length.to_le_bytes());
456 blake.update(input);
457 let hash = blake.sum();
458 hash.as_ref()[..tag_length as usize].to_vec()
459 } else {
460 let r = ((tag_length + 31) / 32) - 2;
463
464 let mut result = Vec::with_capacity(tag_length as usize);
465
466 let mut blake = Blake2b::new_keyed(&[], 64);
468 blake.update(&tag_length.to_le_bytes());
469 blake.update(input);
470 let hash = blake.sum();
471 let mut v_prev = hash.as_ref()[..64].to_vec();
472
473 result.extend_from_slice(&v_prev[..32]);
475
476 for _ in 2..=r {
478 let mut blake = Blake2b::new_keyed(&[], 64);
479 blake.update(&v_prev);
480 let hash = blake.sum();
481 v_prev = hash.as_ref()[..64].to_vec();
482 result.extend_from_slice(&v_prev[..32]);
483 }
484
485 let remaining = tag_length - 32 * r;
487 let mut blake = Blake2b::new_keyed(&[], remaining as usize);
488 blake.update(&v_prev);
489 let hash = blake.sum();
490 result.extend_from_slice(&hash.as_ref()[..remaining as usize]);
491
492 result
493 }
494}
495
496fn compress(x: &Block, y: &Block) -> Block {
504 let mut r = Block::zero();
506 for i in 0..128 {
507 r.v[i] = x.v[i] ^ y.v[i];
508 }
509
510 let mut q = r.clone();
511
512 for row in 0..8 {
515 let base = row * 16;
516 permutation_p(&mut q.v[base..base + 16]);
517 }
518
519 for col in 0..8 {
522 let mut buf = [0u64; 16];
523 for row in 0..8 {
524 let src = row * 16 + col * 2;
525 buf[row * 2] = q.v[src];
526 buf[row * 2 + 1] = q.v[src + 1];
527 }
528 permutation_p(&mut buf);
529 for row in 0..8 {
530 let dst = row * 16 + col * 2;
531 q.v[dst] = buf[row * 2];
532 q.v[dst + 1] = buf[row * 2 + 1];
533 }
534 }
535
536 for i in 0..128 {
538 q.v[i] ^= r.v[i];
539 }
540
541 q
542}
543
544fn permutation_p(v: &mut [u64]) {
548 gb(v, 0, 4, 8, 12);
550 gb(v, 1, 5, 9, 13);
551 gb(v, 2, 6, 10, 14);
552 gb(v, 3, 7, 11, 15);
553
554 gb(v, 0, 5, 10, 15);
556 gb(v, 1, 6, 11, 12);
557 gb(v, 2, 7, 8, 13);
558 gb(v, 3, 4, 9, 14);
559}
560
561#[inline(always)]
565fn gb(v: &mut [u64], a: usize, b: usize, c: usize, d: usize) {
566 v[a] = v[a]
567 .wrapping_add(v[b])
568 .wrapping_add(2u64.wrapping_mul((v[a] as u32 as u64).wrapping_mul(v[b] as u32 as u64)));
569 v[d] = (v[d] ^ v[a]).rotate_right(32);
570 v[c] = v[c]
571 .wrapping_add(v[d])
572 .wrapping_add(2u64.wrapping_mul((v[c] as u32 as u64).wrapping_mul(v[d] as u32 as u64)));
573 v[b] = (v[b] ^ v[c]).rotate_right(24);
574
575 v[a] = v[a]
576 .wrapping_add(v[b])
577 .wrapping_add(2u64.wrapping_mul((v[a] as u32 as u64).wrapping_mul(v[b] as u32 as u64)));
578 v[d] = (v[d] ^ v[a]).rotate_right(16);
579 v[c] = v[c]
580 .wrapping_add(v[d])
581 .wrapping_add(2u64.wrapping_mul((v[c] as u32 as u64).wrapping_mul(v[d] as u32 as u64)));
582 v[b] = (v[b] ^ v[c]).rotate_right(63);
583}
584
585#[cfg(feature = "alloc")]
595pub fn encode_phc(params: &Params, salt: &[u8], tag: &[u8]) -> String {
596 let salt_b64 = base64_encode_no_pad(salt);
597 let tag_b64 = base64_encode_no_pad(tag);
598 alloc::format!(
599 "$argon2id$v=19$m={},t={},p={}${}${}",
600 params.m_cost,
601 params.t_cost,
602 params.p_cost,
603 salt_b64,
604 tag_b64
605 )
606}
607
608#[cfg(feature = "alloc")]
612pub fn decode_phc(encoded: &str) -> Result<(Params, Vec<u8>, Vec<u8>), Argon2Error> {
613 let parts: Vec<&str> = encoded.split('$').collect();
614 if parts.len() != 6 {
616 return Err(Argon2Error::InvalidEncoding("invalid PHC string format"));
617 }
618 if parts[0] != "" {
619 return Err(Argon2Error::InvalidEncoding("must start with $"));
620 }
621 if parts[1] != "argon2id" {
622 return Err(Argon2Error::InvalidEncoding("unsupported algorithm"));
623 }
624 if parts[2] != "v=19" {
625 return Err(Argon2Error::InvalidEncoding("unsupported version"));
626 }
627
628 let param_parts: Vec<&str> = parts[3].split(',').collect();
630 if param_parts.len() != 3 {
631 return Err(Argon2Error::InvalidEncoding("invalid parameters"));
632 }
633
634 let m_cost = parse_param(param_parts[0], "m=")?;
635 let t_cost = parse_param(param_parts[1], "t=")?;
636 let p_cost = parse_param(param_parts[2], "p=")?;
637
638 let salt = base64_decode_no_pad(parts[4]).map_err(|_| Argon2Error::InvalidEncoding("invalid base64 in salt"))?;
639 let tag = base64_decode_no_pad(parts[5]).map_err(|_| Argon2Error::InvalidEncoding("invalid base64 in hash"))?;
640
641 let params = Params {
642 t_cost,
643 m_cost,
644 p_cost,
645 tag_length: tag.len() as u32,
646 };
647
648 Ok((params, salt, tag))
649}
650
651#[cfg(feature = "alloc")]
653pub fn hash_password(password: &[u8], salt: &[u8], params: &Params) -> Result<String, Argon2Error> {
654 let tag = derive_key(password, salt, &[], &[], params)?;
655 Ok(encode_phc(params, salt, &tag))
656}
657
658#[cfg(feature = "alloc")]
660pub fn verify_password(password: &[u8], encoded: &str) -> Result<(), Argon2Error> {
661 let (params, salt, expected_tag) = decode_phc(encoded)?;
662 let computed_tag = derive_key(password, &salt, &[], &[], ¶ms)?;
663 if constant_time_eq::constant_time_eq(&computed_tag, &expected_tag) {
664 Ok(())
665 } else {
666 Err(Argon2Error::VerifyMismatch)
667 }
668}
669
670#[cfg(feature = "alloc")]
675fn base64_encode_no_pad(input: &[u8]) -> String {
676 base64::encode(input, base64::Alphabet::StandardNoPadding)
677}
678
679#[cfg(feature = "alloc")]
680fn base64_decode_no_pad(input: &str) -> Result<Vec<u8>, ()> {
681 base64::decode(input.as_bytes(), base64::Alphabet::StandardNoPadding).map_err(|_| ())
682}
683
684#[cfg(feature = "alloc")]
685fn parse_param(s: &str, prefix: &str) -> Result<u32, Argon2Error> {
686 if !s.starts_with(prefix) {
687 return Err(Argon2Error::InvalidEncoding("invalid parameter prefix"));
688 }
689 s[prefix.len()..]
690 .parse::<u32>()
691 .map_err(|_| Argon2Error::InvalidEncoding("invalid parameter value"))
692}
693
694#[cfg(test)]
699mod tests {
700 use super::*;
701
702 fn derive_key_typed(
703 argon_type: u32,
704 password: &[u8],
705 salt: &[u8],
706 secret: &[u8],
707 ad: &[u8],
708 t_cost: u32,
709 m_cost: u32,
710 p_cost: u32,
711 tag_length: u32,
712 ) -> Vec<u8> {
713 let params = Params::new(t_cost, m_cost, p_cost, tag_length);
714 argon2_core(argon_type, password, salt, secret, ad, ¶ms).unwrap()
715 }
716
717 #[test]
724 fn test_rfc9106_argon2d() {
725 let pwd = vec![0x01u8; 32];
726 let salt = vec![0x02u8; 16];
727 let secret = vec![0x03u8; 8];
728 let ad = vec![0x04u8; 12];
729 let expected = hex::decode("512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb").unwrap();
730 let result = derive_key_typed(ARGON2D, &pwd, &salt, &secret, &ad, 3, 32, 4, 32);
731 assert_eq!(result, expected);
732 }
733
734 #[test]
735 fn test_rfc9106_argon2i() {
736 let pwd = vec![0x01u8; 32];
737 let salt = vec![0x02u8; 16];
738 let secret = vec![0x03u8; 8];
739 let ad = vec![0x04u8; 12];
740 let expected = hex::decode("c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8").unwrap();
741 let result = derive_key_typed(ARGON2I, &pwd, &salt, &secret, &ad, 3, 32, 4, 32);
742 assert_eq!(result, expected);
743 }
744
745 #[test]
752 fn test_h0() {
753 let pwd = vec![0x01u8; 32];
754 let salt = vec![0x02u8; 16];
755 let secret = vec![0x03u8; 8];
756 let ad = vec![0x04u8; 12];
757 let h0 = compute_h0(ARGON2ID, &pwd, &salt, &secret, &ad, 4, 32, 32, 3);
758 let expected = "2889de487eb42ae500c0007ed9252f1069eadec40d5765b485de6dc2437a67b8546a2f0acc1a0882db8fcf74714b472e94df421a5da1112ffa11434370a1e997";
759 assert_eq!(hex::encode(h0), expected);
760 }
761
762 struct Vec3 {
768 mode: u32,
769 time: u32,
770 memory: u32,
771 threads: u32,
772 hash: &'static str,
773 }
774
775 const GO_VECTORS: &[Vec3] = &[
776 Vec3 {
777 mode: ARGON2I,
778 time: 1,
779 memory: 64,
780 threads: 1,
781 hash: "b9c401d1844a67d50eae3967dc28870b22e508092e861a37",
782 },
783 Vec3 {
784 mode: ARGON2D,
785 time: 1,
786 memory: 64,
787 threads: 1,
788 hash: "8727405fd07c32c78d64f547f24150d3f2e703a89f981a19",
789 },
790 Vec3 {
791 mode: ARGON2ID,
792 time: 1,
793 memory: 64,
794 threads: 1,
795 hash: "655ad15eac652dc59f7170a7332bf49b8469be1fdb9c28bb",
796 },
797 Vec3 {
798 mode: ARGON2I,
799 time: 2,
800 memory: 64,
801 threads: 1,
802 hash: "8cf3d8f76a6617afe35fac48eb0b7433a9a670ca4a07ed64",
803 },
804 Vec3 {
805 mode: ARGON2D,
806 time: 2,
807 memory: 64,
808 threads: 1,
809 hash: "3be9ec79a69b75d3752acb59a1fbb8b295a46529c48fbb75",
810 },
811 Vec3 {
812 mode: ARGON2ID,
813 time: 2,
814 memory: 64,
815 threads: 1,
816 hash: "068d62b26455936aa6ebe60060b0a65870dbfa3ddf8d41f7",
817 },
818 Vec3 {
819 mode: ARGON2I,
820 time: 2,
821 memory: 64,
822 threads: 2,
823 hash: "2089f3e78a799720f80af806553128f29b132cafe40d059f",
824 },
825 Vec3 {
826 mode: ARGON2D,
827 time: 2,
828 memory: 64,
829 threads: 2,
830 hash: "68e2462c98b8bc6bb60ec68db418ae2c9ed24fc6748a40e9",
831 },
832 Vec3 {
833 mode: ARGON2ID,
834 time: 2,
835 memory: 64,
836 threads: 2,
837 hash: "350ac37222f436ccb5c0972f1ebd3bf6b958bf2071841362",
838 },
839 Vec3 {
840 mode: ARGON2I,
841 time: 3,
842 memory: 256,
843 threads: 2,
844 hash: "f5bbf5d4c3836af13193053155b73ec7476a6a2eb93fd5e6",
845 },
846 Vec3 {
847 mode: ARGON2D,
848 time: 3,
849 memory: 256,
850 threads: 2,
851 hash: "f4f0669218eaf3641f39cc97efb915721102f4b128211ef2",
852 },
853 Vec3 {
854 mode: ARGON2ID,
855 time: 3,
856 memory: 256,
857 threads: 2,
858 hash: "4668d30ac4187e6878eedeacf0fd83c5a0a30db2cc16ef0b",
859 },
860 Vec3 {
861 mode: ARGON2I,
862 time: 4,
863 memory: 4096,
864 threads: 4,
865 hash: "a11f7b7f3f93f02ad4bddb59ab62d121e278369288a0d0e7",
866 },
867 Vec3 {
868 mode: ARGON2D,
869 time: 4,
870 memory: 4096,
871 threads: 4,
872 hash: "935598181aa8dc2b720914aa6435ac8d3e3a4210c5b0fb2d",
873 },
874 Vec3 {
875 mode: ARGON2ID,
876 time: 4,
877 memory: 4096,
878 threads: 4,
879 hash: "145db9733a9f4ee43edf33c509be96b934d505a4efb33c5a",
880 },
881 Vec3 {
882 mode: ARGON2I,
883 time: 4,
884 memory: 1024,
885 threads: 8,
886 hash: "0cdd3956aa35e6b475a7b0c63488822f774f15b43f6e6e17",
887 },
888 Vec3 {
889 mode: ARGON2D,
890 time: 4,
891 memory: 1024,
892 threads: 8,
893 hash: "83604fc2ad0589b9d055578f4d3cc55bc616df3578a896e9",
894 },
895 Vec3 {
896 mode: ARGON2ID,
897 time: 4,
898 memory: 1024,
899 threads: 8,
900 hash: "8dafa8e004f8ea96bf7c0f93eecf67a6047476143d15577f",
901 },
902 Vec3 {
903 mode: ARGON2I,
904 time: 2,
905 memory: 64,
906 threads: 3,
907 hash: "5cab452fe6b8479c8661def8cd703b611a3905a6d5477fe6",
908 },
909 Vec3 {
910 mode: ARGON2D,
911 time: 2,
912 memory: 64,
913 threads: 3,
914 hash: "22474a423bda2ccd36ec9afd5119e5c8949798cadf659f51",
915 },
916 Vec3 {
917 mode: ARGON2ID,
918 time: 2,
919 memory: 64,
920 threads: 3,
921 hash: "4a15b31aec7c2590b87d1f520be7d96f56658172deaa3079",
922 },
923 Vec3 {
924 mode: ARGON2I,
925 time: 3,
926 memory: 1024,
927 threads: 6,
928 hash: "d236b29c2b2a09babee842b0dec6aa1e83ccbdea8023dced",
929 },
930 Vec3 {
931 mode: ARGON2D,
932 time: 3,
933 memory: 1024,
934 threads: 6,
935 hash: "a3351b0319a53229152023d9206902f4ef59661cdca89481",
936 },
937 Vec3 {
938 mode: ARGON2ID,
939 time: 3,
940 memory: 1024,
941 threads: 6,
942 hash: "1640b932f4b60e272f5d2207b9a9c626ffa1bd88d2349016",
943 },
944 ];
945
946 #[test]
947 fn test_go_vectors() {
948 let password = b"password";
949 let salt = b"somesalt";
950 for (i, v) in GO_VECTORS.iter().enumerate() {
951 let expected = hex::decode(v.hash).unwrap();
952 let result = derive_key_typed(
953 v.mode,
954 password,
955 salt,
956 &[],
957 &[],
958 v.time,
959 v.memory,
960 v.threads,
961 expected.len() as u32,
962 );
963 assert_eq!(
964 result, expected,
965 "Go vector {} failed (mode={}, t={}, m={}, p={})",
966 i, v.mode, v.time, v.memory, v.threads
967 );
968 }
969 }
970
971 struct CVector {
978 mode: u32,
979 time: u32,
980 memory: u32,
981 threads: u32,
982 hash: &'static str,
983 pwd: &'static str,
984 slt: &'static str,
985 }
986
987 const C_VECTORS: &[CVector] = &[
988 CVector {
989 mode: ARGON2I,
990 time: 2,
991 memory: 65536,
992 threads: 1,
993 hash: "c1628832147d9720c5bd1cfd61367078729f6dfb6f8fea9ff98158e0d7816ed0",
994 pwd: "password",
995 slt: "somesalt",
996 },
997 CVector {
998 mode: ARGON2I,
999 time: 2,
1000 memory: 262144,
1001 threads: 1,
1002 hash: "296dbae80b807cdceaad44ae741b506f14db0959267b183b118f9b24229bc7cb",
1003 pwd: "password",
1004 slt: "somesalt",
1005 },
1006 CVector {
1007 mode: ARGON2I,
1008 time: 2,
1009 memory: 256,
1010 threads: 1,
1011 hash: "89e9029f4637b295beb027056a7336c414fadd43f6b208645281cb214a56452f",
1012 pwd: "password",
1013 slt: "somesalt",
1014 },
1015 CVector {
1016 mode: ARGON2I,
1017 time: 2,
1018 memory: 256,
1019 threads: 2,
1020 hash: "4ff5ce2769a1d7f4c8a491df09d41a9fbe90e5eb02155a13e4c01e20cd4eab61",
1021 pwd: "password",
1022 slt: "somesalt",
1023 },
1024 CVector {
1025 mode: ARGON2I,
1026 time: 1,
1027 memory: 65536,
1028 threads: 1,
1029 hash: "d168075c4d985e13ebeae560cf8b94c3b5d8a16c51916b6f4ac2da3ac11bbecf",
1030 pwd: "password",
1031 slt: "somesalt",
1032 },
1033 CVector {
1034 mode: ARGON2I,
1035 time: 4,
1036 memory: 65536,
1037 threads: 1,
1038 hash: "aaa953d58af3706ce3df1aefd4a64a84e31d7f54175231f1285259f88174ce5b",
1039 pwd: "password",
1040 slt: "somesalt",
1041 },
1042 CVector {
1043 mode: ARGON2I,
1044 time: 2,
1045 memory: 65536,
1046 threads: 1,
1047 hash: "14ae8da01afea8700c2358dcef7c5358d9021282bd88663a4562f59fb74d22ee",
1048 pwd: "differentpassword",
1049 slt: "somesalt",
1050 },
1051 CVector {
1052 mode: ARGON2I,
1053 time: 2,
1054 memory: 65536,
1055 threads: 1,
1056 hash: "b0357cccfbef91f3860b0dba447b2348cbefecadaf990abfe9cc40726c521271",
1057 pwd: "password",
1058 slt: "diffsalt",
1059 },
1060 CVector {
1061 mode: ARGON2ID,
1062 time: 2,
1063 memory: 65536,
1064 threads: 1,
1065 hash: "09316115d5cf24ed5a15a31a3ba326e5cf32edc24702987c02b6566f61913cf7",
1066 pwd: "password",
1067 slt: "somesalt",
1068 },
1069 CVector {
1070 mode: ARGON2ID,
1071 time: 2,
1072 memory: 262144,
1073 threads: 1,
1074 hash: "78fe1ec91fb3aa5657d72e710854e4c3d9b9198c742f9616c2f085bed95b2e8c",
1075 pwd: "password",
1076 slt: "somesalt",
1077 },
1078 CVector {
1079 mode: ARGON2ID,
1080 time: 2,
1081 memory: 256,
1082 threads: 1,
1083 hash: "9dfeb910e80bad0311fee20f9c0e2b12c17987b4cac90c2ef54d5b3021c68bfe",
1084 pwd: "password",
1085 slt: "somesalt",
1086 },
1087 CVector {
1088 mode: ARGON2ID,
1089 time: 2,
1090 memory: 256,
1091 threads: 2,
1092 hash: "6d093c501fd5999645e0ea3bf620d7b8be7fd2db59c20d9fff9539da2bf57037",
1093 pwd: "password",
1094 slt: "somesalt",
1095 },
1096 CVector {
1097 mode: ARGON2ID,
1098 time: 1,
1099 memory: 65536,
1100 threads: 1,
1101 hash: "f6a5adc1ba723dddef9b5ac1d464e180fcd9dffc9d1cbf76cca2fed795d9ca98",
1102 pwd: "password",
1103 slt: "somesalt",
1104 },
1105 CVector {
1106 mode: ARGON2ID,
1107 time: 4,
1108 memory: 65536,
1109 threads: 1,
1110 hash: "9025d48e68ef7395cca9079da4c4ec3affb3c8911fe4f86d1a2520856f63172c",
1111 pwd: "password",
1112 slt: "somesalt",
1113 },
1114 CVector {
1115 mode: ARGON2ID,
1116 time: 2,
1117 memory: 65536,
1118 threads: 1,
1119 hash: "0b84d652cf6b0c4beaef0dfe278ba6a80df6696281d7e0d2891b817d8c458fde",
1120 pwd: "differentpassword",
1121 slt: "somesalt",
1122 },
1123 CVector {
1124 mode: ARGON2ID,
1125 time: 2,
1126 memory: 65536,
1127 threads: 1,
1128 hash: "bdf32b05ccc42eb15d58fd19b1f856b113da1e9a5874fdcc544308565aa8141c",
1129 pwd: "password",
1130 slt: "diffsalt",
1131 },
1132 ];
1133
1134 #[test]
1135 fn test_c_reference_vectors() {
1136 for (i, v) in C_VECTORS.iter().enumerate() {
1137 let expected = hex::decode(v.hash).unwrap();
1138 let result = derive_key_typed(
1139 v.mode,
1140 v.pwd.as_bytes(),
1141 v.slt.as_bytes(),
1142 &[],
1143 &[],
1144 v.time,
1145 v.memory,
1146 v.threads,
1147 expected.len() as u32,
1148 );
1149 assert_eq!(
1150 result, expected,
1151 "C ref vector {} failed (mode={}, t={}, m={}, p={})",
1152 i, v.mode, v.time, v.memory, v.threads
1153 );
1154 }
1155 }
1156
1157 #[test]
1162 fn test_phc_encode_decode() {
1163 let params = Params::new(3, 65536, 4, 32);
1164 let salt = b"somesalt12345678";
1165 let tag = vec![0xAB; 32];
1166 let encoded = encode_phc(¶ms, salt, &tag);
1167 assert!(encoded.starts_with("$argon2id$v=19$m=65536,t=3,p=4$"));
1168 let (dp, ds, dt) = decode_phc(&encoded).unwrap();
1169 assert_eq!(dp.t_cost, 3);
1170 assert_eq!(dp.m_cost, 65536);
1171 assert_eq!(dp.p_cost, 4);
1172 assert_eq!(dp.tag_length, 32);
1173 assert_eq!(ds, salt);
1174 assert_eq!(dt, tag);
1175 }
1176
1177 #[test]
1178 fn test_hash_and_verify() {
1179 let password = b"correct horse battery staple";
1180 let salt = b"randomsalt123456";
1181 let params = Params::new(1, 64, 1, 32);
1182 let encoded = hash_password(password, salt, ¶ms).unwrap();
1183 assert!(verify_password(password, &encoded).is_ok());
1184 assert_eq!(verify_password(b"wrong password", &encoded), Err(Argon2Error::VerifyMismatch));
1185 }
1186
1187 #[test]
1188 fn test_decode_phc_invalid() {
1189 assert!(decode_phc("").is_err());
1190 assert!(decode_phc("$argon2i$v=19$m=4096,t=3,p=1$salt$hash").is_err());
1191 assert!(decode_phc("$argon2id$v=16$m=4096,t=3,p=1$salt$hash").is_err());
1192 assert!(decode_phc("not a phc string").is_err());
1193 }
1194
1195 #[test]
1196 fn test_invalid_params() {
1197 assert!(derive_key(b"password", b"salt", &[], &[], &Params::new(0, 64, 1, 32)).is_err());
1198 assert!(derive_key(b"password", b"salt", &[], &[], &Params::new(1, 4, 1, 32)).is_err());
1199 assert!(derive_key(b"password", b"salt", &[], &[], &Params::new(1, 64, 1, 3)).is_err());
1200 }
1201
1202 #[test]
1203 fn test_variable_length_hash_short() {
1204 let input = b"test input";
1205 let r32 = variable_length_hash(input, 32);
1206 assert_eq!(r32.len(), 32);
1207 assert_eq!(variable_length_hash(input, 32), r32);
1208 let r48 = variable_length_hash(input, 48);
1209 assert_eq!(r48.len(), 48);
1210 assert_ne!(&r32[..], &r48[..32]);
1211 }
1212
1213 #[test]
1214 fn test_variable_length_hash_long() {
1215 assert_eq!(variable_length_hash(b"test input for long hash", 128).len(), 128);
1216 assert_eq!(variable_length_hash(b"test input for long hash", 1024).len(), 1024);
1217 }
1218
1219 #[test]
1220 fn test_argon2id_min_memory() {
1221 let result = derive_key(b"password", b"saltsalt", &[], &[], &Params::new(1, 8, 1, 32)).unwrap();
1222 assert_eq!(result.len(), 32);
1223 }
1224
1225 #[test]
1226 fn test_argon2id_multiple_lanes() {
1227 assert_eq!(
1228 derive_key(b"password", b"saltsaltsaltsalt", &[], &[], &Params::new(1, 64, 4, 32))
1229 .unwrap()
1230 .len(),
1231 32
1232 );
1233 }
1234
1235 #[test]
1236 fn test_different_passwords() {
1237 let p = Params::new(1, 64, 1, 32);
1238 assert_ne!(
1239 derive_key(b"password1", b"saltsaltsaltsalt", &[], &[], &p).unwrap(),
1240 derive_key(b"password2", b"saltsaltsaltsalt", &[], &[], &p).unwrap()
1241 );
1242 }
1243
1244 #[test]
1245 fn test_different_salts() {
1246 let p = Params::new(1, 64, 1, 32);
1247 assert_ne!(
1248 derive_key(b"password", b"salt1234salt1234", &[], &[], &p).unwrap(),
1249 derive_key(b"password", b"salt5678salt5678", &[], &[], &p).unwrap()
1250 );
1251 }
1252
1253 #[test]
1254 fn test_long_tag() {
1255 let result = derive_key(b"password", b"saltsaltsaltsalt", &[], &[], &Params::new(1, 64, 1, 64)).unwrap();
1256 assert_eq!(result.len(), 64);
1257 }
1258
1259 #[test]
1260 fn test_phc_roundtrip() {
1261 let password = b"password";
1262 let salt = b"somesalt";
1263 let params = Params::new(1, 64, 1, 24);
1264 let tag = derive_key(password, salt, &[], &[], ¶ms).unwrap();
1265 let encoded = encode_phc(¶ms, salt, &tag);
1266 let (dp, ds, dt) = decode_phc(&encoded).unwrap();
1267 assert_eq!(dp.m_cost, params.m_cost);
1268 assert_eq!(dp.t_cost, params.t_cost);
1269 assert_eq!(dp.p_cost, params.p_cost);
1270 assert_eq!(ds, salt);
1271 assert_eq!(dt, tag);
1272 }
1273
1274 fn argon2_core_with_passes(
1282 argon_type: u32,
1283 password: &[u8],
1284 salt: &[u8],
1285 secret: &[u8],
1286 ad: &[u8],
1287 params: &Params,
1288 ) -> Vec<Vec<Block>> {
1289 let p = params.p_cost;
1290 let t = params.t_cost;
1291 let m = params.m_cost;
1292 let tag_length = params.tag_length;
1293
1294 let h0 = compute_h0(argon_type, password, salt, secret, ad, p, tag_length, m, t);
1295 let m_prime = 4 * p * (m / (4 * p));
1296 let q = m_prime / p;
1297
1298 let mut memory: Vec<Block> = vec![Block::zero(); m_prime as usize];
1299
1300 for i in 0..p {
1301 let mut input = Vec::with_capacity(72);
1302 input.extend_from_slice(&h0);
1303 input.extend_from_slice(&0u32.to_le_bytes());
1304 input.extend_from_slice(&i.to_le_bytes());
1305 let block_bytes = variable_length_hash(&input, BLOCK_SIZE as u32);
1306 memory[(i * q) as usize] = Block::from_bytes(&block_bytes);
1307
1308 let mut input = Vec::with_capacity(72);
1309 input.extend_from_slice(&h0);
1310 input.extend_from_slice(&1u32.to_le_bytes());
1311 input.extend_from_slice(&i.to_le_bytes());
1312 let block_bytes = variable_length_hash(&input, BLOCK_SIZE as u32);
1313 memory[(i * q + 1) as usize] = Block::from_bytes(&block_bytes);
1314 }
1315
1316 let mut pass_snapshots = Vec::new();
1317
1318 for pass in 0..t {
1319 for slice in 0..SYNC_POINTS {
1320 for lane in 0..p {
1321 fill_segment(&mut memory, argon_type, pass, lane, slice, p, q, t, m_prime);
1322 }
1323 }
1324 pass_snapshots.push(memory.clone());
1325 }
1326
1327 pass_snapshots
1328 }
1329
1330 fn block0_word(block: &Block, idx: usize) -> String {
1331 format!("{:016x}", block.v[idx])
1332 }
1333
1334 fn block_last_word(block: &Block, idx: usize) -> String {
1335 format!("{:016x}", block.v[idx])
1336 }
1337
1338 #[test]
1339 fn test_rfc9106_argon2d_intermediate_blocks() {
1340 let pwd = vec![0x01u8; 32];
1341 let salt = vec![0x02u8; 16];
1342 let secret = vec![0x03u8; 8];
1343 let ad = vec![0x04u8; 12];
1344 let params = Params::new(3, 32, 4, 32);
1345 let passes = argon2_core_with_passes(ARGON2D, &pwd, &salt, &secret, &ad, ¶ms);
1346
1347 let p = params.p_cost;
1348 let q = (4 * p * (params.m_cost / (4 * p))) / p;
1349 let m_prime = p * q;
1350
1351 assert_eq!(block0_word(&passes[0][0], 0), "db2fea6b2c6f5c8a");
1352 assert_eq!(block_last_word(&passes[0][(m_prime - 1) as usize], 127), "6a6c49d2cb75d5b6");
1353
1354 assert_eq!(block0_word(&passes[1][0], 0), "d3801200410f8c0d");
1355 assert_eq!(block_last_word(&passes[1][(m_prime - 1) as usize], 127), "2dbfff23f31b5883");
1356
1357 assert_eq!(block0_word(&passes[2][0], 0), "5f047b575c5ff4d2");
1358 assert_eq!(block_last_word(&passes[2][(m_prime - 1) as usize], 127), "c341b3ca45c10da5");
1359 }
1360
1361 #[test]
1362 fn test_rfc9106_argon2i_intermediate_blocks() {
1363 let pwd = vec![0x01u8; 32];
1364 let salt = vec![0x02u8; 16];
1365 let secret = vec![0x03u8; 8];
1366 let ad = vec![0x04u8; 12];
1367 let params = Params::new(3, 32, 4, 32);
1368 let passes = argon2_core_with_passes(ARGON2I, &pwd, &salt, &secret, &ad, ¶ms);
1369
1370 let p = params.p_cost;
1371 let q = (4 * p * (params.m_cost / (4 * p))) / p;
1372 let m_prime = p * q;
1373
1374 assert_eq!(block0_word(&passes[0][0], 0), "f8f9e84545db08f6");
1375 assert_eq!(block_last_word(&passes[0][(m_prime - 1) as usize], 127), "c570f2ab2a86cf00");
1376
1377 assert_eq!(block0_word(&passes[1][0], 0), "b2e4ddfcf76dc85a");
1378 assert_eq!(block_last_word(&passes[1][(m_prime - 1) as usize], 127), "421b3c6e9555b79d");
1379
1380 assert_eq!(block0_word(&passes[2][0], 0), "af2a8bd8482c2f11");
1381 assert_eq!(block_last_word(&passes[2][(m_prime - 1) as usize], 127), "71e436f035f30ed0");
1382 }
1383
1384 #[test]
1389 fn test_h0_argon2d() {
1390 let pwd = vec![0x01u8; 32];
1391 let salt = vec![0x02u8; 16];
1392 let secret = vec![0x03u8; 8];
1393 let ad = vec![0x04u8; 12];
1394 let h0 = compute_h0(ARGON2D, &pwd, &salt, &secret, &ad, 4, 32, 32, 3);
1395 let expected = "b8819791a0359660bb7709c85fa48f04d5d82c05c5f215ccdb885491717cf757082c28b951be381410b5fc2eb7274033b9fdc7ae672bcaac5d179097a4af3109";
1396 assert_eq!(hex::encode(h0), expected);
1397 }
1398
1399 #[test]
1400 fn test_h0_argon2i() {
1401 let pwd = vec![0x01u8; 32];
1402 let salt = vec![0x02u8; 16];
1403 let secret = vec![0x03u8; 8];
1404 let ad = vec![0x04u8; 12];
1405 let h0 = compute_h0(ARGON2I, &pwd, &salt, &secret, &ad, 4, 32, 32, 3);
1406 let expected = "c46065815276a0b3e731731c902f1fd80cf776907fbb7b6a5ca72e7b56011feeca446c86dd75b9469a5e6879dec4b72d0863fb939b982e5f397cc7d164fddaa9";
1407 assert_eq!(hex::encode(h0), expected);
1408 }
1409
1410 #[test]
1415 fn test_argon2id_empty_secret_and_ad() {
1416 let result = derive_key(b"password", b"saltsaltsaltsalt", &[], &[], &Params::new(1, 64, 1, 32)).unwrap();
1417 assert_eq!(result.len(), 32);
1418 let result2 = derive_key(b"password", b"saltsaltsaltsalt", &[], &[], &Params::new(1, 64, 1, 32)).unwrap();
1419 assert_eq!(result, result2);
1420 }
1421
1422 #[test]
1423 fn test_argon2id_with_secret() {
1424 let p = Params::new(1, 64, 1, 32);
1425 let without_secret = derive_key(b"password", b"saltsaltsaltsalt", &[], &[], &p).unwrap();
1426 let with_secret = derive_key(b"password", b"saltsaltsaltsalt", b"secret", &[], &p).unwrap();
1427 assert_ne!(without_secret, with_secret);
1428 }
1429
1430 #[test]
1431 fn test_argon2id_with_ad() {
1432 let p = Params::new(1, 64, 1, 32);
1433 let without_ad = derive_key(b"password", b"saltsaltsaltsalt", &[], &[], &p).unwrap();
1434 let with_ad = derive_key(b"password", b"saltsaltsaltsalt", &[], b"associated data", &p).unwrap();
1435 assert_ne!(without_ad, with_ad);
1436 }
1437
1438 #[test]
1439 fn test_argon2id_tag_length_4() {
1440 let result = derive_key(b"password", b"saltsaltsaltsalt", &[], &[], &Params::new(1, 64, 1, 4)).unwrap();
1441 assert_eq!(result.len(), 4);
1442 }
1443
1444 #[test]
1445 fn test_argon2id_tag_length_128() {
1446 let result = derive_key(b"password", b"saltsaltsaltsalt", &[], &[], &Params::new(1, 64, 1, 128)).unwrap();
1447 assert_eq!(result.len(), 128);
1448 }
1449
1450 #[test]
1451 fn test_argon2id_tag_length_256() {
1452 let result = derive_key(b"password", b"saltsaltsaltsalt", &[], &[], &Params::new(1, 64, 1, 256)).unwrap();
1453 assert_eq!(result.len(), 256);
1454 }
1455
1456 #[test]
1457 fn test_argon2id_long_tag_consistency() {
1458 let p = Params::new(1, 64, 1, 100);
1459 let r1 = derive_key(b"password", b"saltsaltsaltsalt", &[], &[], &p).unwrap();
1460 let r2 = derive_key(b"password", b"saltsaltsaltsalt", &[], &[], &p).unwrap();
1461 assert_eq!(r1, r2);
1462 assert_eq!(r1.len(), 100);
1463 }
1464
1465 #[test]
1466 fn test_argon2i_long_tag_consistency() {
1467 let params = Params::new(1, 64, 1, 100);
1468 let r1 = argon2_core(ARGON2I, b"password", b"saltsaltsaltsalt", &[], &[], ¶ms).unwrap();
1469 let r2 = argon2_core(ARGON2I, b"password", b"saltsaltsaltsalt", &[], &[], ¶ms).unwrap();
1470 assert_eq!(r1, r2);
1471 assert_eq!(r1.len(), 100);
1472 }
1473
1474 #[test]
1475 fn test_argon2d_long_tag_consistency() {
1476 let params = Params::new(1, 64, 1, 100);
1477 let r1 = argon2_core(ARGON2D, b"password", b"saltsaltsaltsalt", &[], &[], ¶ms).unwrap();
1478 let r2 = argon2_core(ARGON2D, b"password", b"saltsaltsaltsalt", &[], &[], ¶ms).unwrap();
1479 assert_eq!(r1, r2);
1480 assert_eq!(r1.len(), 100);
1481 }
1482
1483 #[test]
1484 fn test_argon2id_single_pass() {
1485 let result = derive_key(b"password", b"saltsalt", &[], &[], &Params::new(1, 32, 1, 32)).unwrap();
1486 assert_eq!(result.len(), 32);
1487 }
1488
1489 #[test]
1490 fn test_argon2id_high_parallelism() {
1491 let result = derive_key(b"password", b"saltsaltsaltsalt", &[], &[], &Params::new(1, 64, 8, 32)).unwrap();
1492 assert_eq!(result.len(), 32);
1493 }
1494
1495 #[test]
1496 fn test_argon2d_rfc_h0() {
1497 let pwd = vec![0x01u8; 32];
1498 let salt = vec![0x02u8; 16];
1499 let secret = vec![0x03u8; 8];
1500 let ad = vec![0x04u8; 12];
1501 let h0 = compute_h0(ARGON2D, &pwd, &salt, &secret, &ad, 4, 32, 32, 3);
1502 assert_eq!(h0[0], 0xb8);
1503 assert_eq!(h0[1], 0x81);
1504 assert_eq!(h0[63], 0x09);
1505 }
1506
1507 #[test]
1508 fn test_variable_length_hash_exact_64() {
1509 let input = b"test";
1510 let result = variable_length_hash(input, 64);
1511 assert_eq!(result.len(), 64);
1512 }
1513
1514 #[test]
1515 fn test_variable_length_hash_65_bytes() {
1516 let input = b"test";
1517 let result = variable_length_hash(input, 65);
1518 assert_eq!(result.len(), 65);
1519 let result2 = variable_length_hash(input, 65);
1520 assert_eq!(result, result2);
1521 }
1522
1523 #[test]
1524 fn test_variable_length_hash_deterministic() {
1525 for len in [4, 16, 32, 48, 64, 65, 96, 128, 256, 512, 1024] {
1526 let r1 = variable_length_hash(b"determinism test", len);
1527 let r2 = variable_length_hash(b"determinism test", len);
1528 assert_eq!(r1, r2, "variable_length_hash not deterministic for len={}", len);
1529 assert_eq!(r1.len(), len as usize);
1530 }
1531 }
1532
1533 #[test]
1534 fn test_compress_deterministic() {
1535 let a = Block::from_bytes(&[0xAA; BLOCK_SIZE]);
1536 let b = Block::from_bytes(&[0xBB; BLOCK_SIZE]);
1537 let c1 = compress(&a, &b);
1538 let c2 = compress(&a, &b);
1539 assert_eq!(c1.v, c2.v);
1540 }
1541
1542 #[test]
1543 fn test_compress_xor_symmetry() {
1544 let a = Block::from_bytes(&[0x11; BLOCK_SIZE]);
1545 let b = Block::from_bytes(&[0x22; BLOCK_SIZE]);
1546 let c_ab = compress(&a, &b);
1547 let c_ba = compress(&b, &a);
1548 assert_eq!(c_ab.v, c_ba.v, "G(X,Y) should equal G(Y,X) since R = X XOR Y is symmetric");
1549 }
1550
1551 #[test]
1552 fn test_block_from_bytes_roundtrip() {
1553 let original = [0x42u8; BLOCK_SIZE];
1554 let block = Block::from_bytes(&original);
1555 let recovered = block.to_bytes();
1556 assert_eq!(original, recovered);
1557 }
1558
1559 #[test]
1560 fn test_argon2id_different_t_costs() {
1561 let p1 = Params::new(1, 64, 1, 32);
1562 let p2 = Params::new(2, 64, 1, 32);
1563 let r1 = derive_key(b"password", b"saltsaltsaltsalt", &[], &[], &p1).unwrap();
1564 let r2 = derive_key(b"password", b"saltsaltsaltsalt", &[], &[], &p2).unwrap();
1565 assert_ne!(r1, r2);
1566 }
1567
1568 #[test]
1569 fn test_argon2id_different_m_costs() {
1570 let p1 = Params::new(1, 64, 1, 32);
1571 let p2 = Params::new(1, 128, 1, 32);
1572 let r1 = derive_key(b"password", b"saltsaltsaltsalt", &[], &[], &p1).unwrap();
1573 let r2 = derive_key(b"password", b"saltsaltsaltsalt", &[], &[], &p2).unwrap();
1574 assert_ne!(r1, r2);
1575 }
1576
1577 #[test]
1578 fn test_argon2id_different_p_costs() {
1579 let p1 = Params::new(1, 64, 1, 32);
1580 let p2 = Params::new(1, 64, 2, 32);
1581 let r1 = derive_key(b"password", b"saltsaltsaltsalt", &[], &[], &p1).unwrap();
1582 let r2 = derive_key(b"password", b"saltsaltsaltsalt", &[], &[], &p2).unwrap();
1583 assert_ne!(r1, r2);
1584 }
1585
1586 #[test]
1587 fn test_argon2i_rfc9106_tag() {
1588 let pwd = vec![0x01u8; 32];
1589 let salt = vec![0x02u8; 16];
1590 let secret = vec![0x03u8; 8];
1591 let ad = vec![0x04u8; 12];
1592 let expected = hex::decode("c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8").unwrap();
1593 let result = derive_key_typed(ARGON2I, &pwd, &salt, &secret, &ad, 3, 32, 4, 32);
1594 assert_eq!(result, expected);
1595 }
1596
1597 #[test]
1598 fn test_argon2d_rfc9106_tag() {
1599 let pwd = vec![0x01u8; 32];
1600 let salt = vec![0x02u8; 16];
1601 let secret = vec![0x03u8; 8];
1602 let ad = vec![0x04u8; 12];
1603 let expected = hex::decode("512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb").unwrap();
1604 let result = derive_key_typed(ARGON2D, &pwd, &salt, &secret, &ad, 3, 32, 4, 32);
1605 assert_eq!(result, expected);
1606 }
1607
1608 #[test]
1609 fn test_phc_verify_known() {
1610 let password = b"password";
1611 let salt = b"randomsalt123456";
1612 let params = Params::new(1, 64, 1, 32);
1613 let encoded = hash_password(password, salt, ¶ms).unwrap();
1614 assert!(verify_password(password, &encoded).is_ok());
1615 assert_eq!(verify_password(b"wrong", &encoded), Err(Argon2Error::VerifyMismatch));
1616 }
1617
1618 #[test]
1619 fn test_decode_phc_roundtrip_all_types() {
1620 for tag_len in [4, 16, 32, 64] {
1621 let params = Params::new(1, 64, 1, tag_len);
1622 let salt = b"testsalt12345678";
1623 let tag = vec![0xAB; tag_len as usize];
1624 let encoded = encode_phc(¶ms, salt, &tag);
1625 let (dp, ds, dt) = decode_phc(&encoded).unwrap();
1626 assert_eq!(dp.m_cost, 64);
1627 assert_eq!(dp.t_cost, 1);
1628 assert_eq!(dp.p_cost, 1);
1629 assert_eq!(dp.tag_length, tag_len);
1630 assert_eq!(ds, salt);
1631 assert_eq!(dt, tag);
1632 }
1633 }
1634
1635 #[test]
1636 fn test_index_alpha_pass0_slice0() {
1637 let result = index_alpha(0, 0, 4, 2, 2, 8, true, 0xFFFFFFFF);
1638 assert!(result < 8);
1639 }
1640
1641 #[test]
1642 fn test_index_alpha_reference_area_size_zero() {
1643 let result = index_alpha(0, 0, 4, 2, 0, 8, true, 0xFFFFFFFF);
1644 assert_eq!(result, 0);
1645 }
1646
1647 #[test]
1648 fn test_gb_known_values() {
1649 let mut v = [0u64; 16];
1650 v[0] = 1;
1651 v[1] = 2;
1652 v[2] = 3;
1653 v[3] = 4;
1654 gb(&mut v, 0, 1, 2, 3);
1655 assert_ne!(v[0], 1);
1656 assert_ne!(v[1], 2);
1657 assert_ne!(v[2], 3);
1658 assert_ne!(v[3], 4);
1659 }
1660
1661 #[test]
1662 fn test_permutation_p_deterministic() {
1663 let mut v1: Vec<u64> = (0..16).collect();
1664 let mut v2: Vec<u64> = (0..16).collect();
1665 permutation_p(&mut v1);
1666 permutation_p(&mut v2);
1667 assert_eq!(v1, v2);
1668 }
1669}