1use crate::error::Argon2Error;
2use crate::lexer::TokenizedHash;
3
4use base64::engine::general_purpose::STANDARD_NO_PAD as b64_stdnopad;
5use base64::Engine;
6use rand::rngs::OsRng;
7use rand::TryRngCore;
8use std::ffi::CStr;
9use std::mem::MaybeUninit;
10use std::str::FromStr;
11use std::{borrow::Cow, default::Default};
12
13use crate::bindings::{
14 argon2_error_message, argon2d_ctx, argon2i_ctx, argon2id_ctx, Argon2_Context,
15 Argon2_ErrorCodes_ARGON2_OK, Argon2_version_ARGON2_VERSION_13,
16};
17
18#[derive(Clone, Copy, Debug)]
27pub enum Algorithm {
28 Argon2d,
30
31 Argon2i,
33
34 Argon2id,
36}
37
38#[derive(Clone, Copy, Debug)]
46pub struct Secret<'a>(&'a [u8]);
47
48impl<'a> Secret<'a> {
49 pub fn using(secret: &'a [u8]) -> Self {
51 Self(secret)
52 }
53}
54
55impl<'a> From<&'a [u8]> for Secret<'a> {
56 fn from(secret: &'a [u8]) -> Self {
57 Self(secret)
58 }
59}
60
61impl<'a> From<&'a Vec<u8>> for Secret<'a> {
62 fn from(secret: &'a Vec<u8>) -> Self {
63 Self(secret)
64 }
65}
66
67impl<'a, const SIZE: usize> From<&'a [u8; SIZE]> for Secret<'a> {
68 fn from(secret: &'a [u8; SIZE]) -> Self {
69 Self(secret)
70 }
71}
72
73impl<'a> From<&'a str> for Secret<'a> {
74 fn from(secret: &'a str) -> Self {
75 Self(secret.as_bytes())
76 }
77}
78
79impl<'a> From<&'a String> for Secret<'a> {
80 fn from(secret: &'a String) -> Self {
81 Self(secret.as_bytes())
82 }
83}
84
85#[derive(Clone, Debug)]
87pub struct Hasher<'a> {
88 alg: Algorithm,
89 custom_salt: Option<&'a [u8]>,
90 salt_len: u32,
91 hash_len: u32,
92 iterations: u32,
93 mem_cost_kib: u32,
94 threads: u32,
95 secret: Option<Secret<'a>>,
96}
97
98impl Default for Hasher<'_> {
99 fn default() -> Self {
124 Self {
125 alg: Algorithm::Argon2id,
126 custom_salt: None,
127 salt_len: 16,
128 hash_len: 32,
129 iterations: 18,
130 mem_cost_kib: 62500,
131 threads: 1,
132 secret: None,
133 }
134 }
135}
136
137impl<'a> Hasher<'a> {
138 pub fn new() -> Self {
163 Self::default()
164 }
165
166 pub fn algorithm(mut self, alg: Algorithm) -> Self {
177 self.alg = alg;
178 self
179 }
180
181 pub fn custom_salt(mut self, salt: &'a [u8]) -> Self {
186 self.custom_salt = Some(salt);
187 self
188 }
189
190 pub fn salt_length(mut self, salt_len: u32) -> Self {
196 self.salt_len = salt_len;
197 self
198 }
199
200 pub fn hash_length(mut self, hash_len: u32) -> Self {
214 self.hash_len = hash_len;
215 self
216 }
217
218 pub fn iterations(mut self, iterations: u32) -> Self {
221 self.iterations = iterations;
222 self
223 }
224
225 pub fn memory_cost_kib(mut self, cost: u32) -> Self {
234 self.mem_cost_kib = cost;
235 self
236 }
237
238 pub fn threads(mut self, threads: u32) -> Self {
247 self.threads = threads;
248 self
249 }
250
251 pub fn secret(mut self, secret: Secret<'a>) -> Self {
264 self.secret = Some(secret);
265 self
266 }
267
268 pub fn hash(self, password: &[u8]) -> Result<Hash, Argon2Error> {
274 let hash_len_usize = match usize::try_from(self.hash_len) {
275 Ok(l) => l,
276 Err(_) => return Err(Argon2Error::InvalidParameter("Hash length is too big")),
277 };
278
279 let mut hash_buffer = MaybeUninit::new(Vec::with_capacity(hash_len_usize));
280 let mut hash_buffer = unsafe {
281 (*hash_buffer.as_mut_ptr()).set_len(hash_len_usize);
282
283 OsRng
284 .try_fill_bytes(&mut *hash_buffer.as_mut_ptr())
285 .expect("Failed to fill buffer with random bytes");
286
287 hash_buffer.assume_init()
288 };
289
290 let (salt_len_u32, salt_len_usize) = if let Some(s) = self.custom_salt {
291 let salt_len_u32 = match u32::try_from(s.len()) {
292 Ok(l) => l,
293 Err(_) => return Err(Argon2Error::InvalidParameter("Salt length is too big")),
294 };
295
296 (salt_len_u32, s.len())
297 } else {
298 let salt_len_usize = match usize::try_from(self.salt_len) {
299 Ok(l) => l,
300 Err(_) => return Err(Argon2Error::InvalidParameter("Salt length is too big")),
301 };
302
303 (self.salt_len, salt_len_usize)
304 };
305
306 let mut salt = if let Some(s) = self.custom_salt {
307 Vec::from(s)
308 } else {
309 let mut rand_salt = MaybeUninit::new(Vec::with_capacity(salt_len_usize));
310 unsafe {
311 (*rand_salt.as_mut_ptr()).set_len(salt_len_usize);
312 OsRng
313 .try_fill_bytes(&mut *rand_salt.as_mut_ptr())
314 .expect("Failed to fill buffer with random bytes");
315
316 rand_salt.assume_init()
317 }
318 };
319
320 let (secret_ptr, secret_len) = {
321 if let Some(s) = self.secret {
322 let length = match s.0.len().try_into() {
323 Ok(l) => l,
324 Err(_) => return Err(Argon2Error::InvalidParameter("Secret is too long")),
325 };
326
327 (s.0.as_ptr() as *mut _, length)
328 } else {
329 (std::ptr::null_mut(), 0)
330 }
331 };
332
333 let mut ctx = Argon2_Context {
336 out: hash_buffer.as_mut_ptr(),
337 outlen: self.hash_len,
338 pwd: password as *const _ as *mut _,
339 pwdlen: match password.len().try_into() {
340 Ok(l) => l,
341 Err(_) => return Err(Argon2Error::InvalidParameter("Password is too long")),
342 },
343 salt: salt.as_mut_ptr(),
344 saltlen: salt_len_u32,
345 secret: secret_ptr,
346 secretlen: secret_len,
347 ad: std::ptr::null_mut(),
348 adlen: 0,
349 t_cost: self.iterations,
350 m_cost: self.mem_cost_kib,
351 lanes: self.threads,
352 threads: self.threads,
353 version: Argon2_version_ARGON2_VERSION_13,
354 allocate_cbk: None,
355 free_cbk: None,
356 flags: 0,
357 };
358
359 let result = unsafe {
360 match self.alg {
361 Algorithm::Argon2d => argon2d_ctx(&mut ctx as *mut _),
362 Algorithm::Argon2i => argon2i_ctx(&mut ctx as *mut _),
363 Algorithm::Argon2id => argon2id_ctx(&mut ctx as *mut _),
364 }
365 };
366
367 if result != Argon2_ErrorCodes_ARGON2_OK {
368 let err_msg = String::from_utf8_lossy(unsafe {
369 CStr::from_ptr(argon2_error_message(result)).to_bytes()
370 });
371
372 return Err(Argon2Error::CLibError(err_msg.into_owned()));
373 }
374
375 Ok(Hash {
376 alg: self.alg,
377 mem_cost_kib: self.mem_cost_kib,
378 iterations: self.iterations,
379 threads: self.threads,
380 salt: Cow::Owned(salt),
381 hash: Cow::Owned(hash_buffer),
382 })
383 }
384}
385
386#[derive(Clone, Debug)]
388pub struct Hash<'a> {
389 alg: Algorithm,
390 mem_cost_kib: u32,
391 iterations: u32,
392 threads: u32,
393 salt: Cow<'a, [u8]>,
394 hash: Cow<'a, [u8]>,
395}
396
397#[allow(clippy::to_string_trait_impl)]
398impl ToString for Hash<'_> {
399 fn to_string(&self) -> String {
408 let b64_salt = b64_stdnopad.encode(self.salt.as_ref());
409 let b64_hash = b64_stdnopad.encode(self.hash.as_ref());
410
411 let alg = match self.alg {
412 Algorithm::Argon2d => "d",
413 Algorithm::Argon2i => "i",
414 Algorithm::Argon2id => "id",
415 };
416
417 format!(
418 "$argon2{}$v={}$m={},t={},p={}${}${}",
419 alg,
420 Argon2_version_ARGON2_VERSION_13,
421 self.mem_cost_kib,
422 self.iterations,
423 self.threads,
424 b64_salt,
425 b64_hash,
426 )
427 }
428}
429
430impl FromStr for Hash<'_> {
431 type Err = Argon2Error;
432
433 fn from_str(s: &str) -> Result<Self, Self::Err> {
440 let tokenized_hash = TokenizedHash::from_str(s)?;
441
442 if tokenized_hash.v != Argon2_version_ARGON2_VERSION_13 {
443 return Err(Argon2Error::InvalidHash("Hash version is unsupported"));
444 }
445
446 let decoded_salt = match b64_stdnopad.decode(tokenized_hash.b64_salt) {
447 Ok(s) => s,
448 Err(_) => {
449 return Err(Argon2Error::InvalidHash(
450 "Invalid character in base64-encoded salt",
451 ))
452 }
453 };
454
455 let decoded_hash = match b64_stdnopad.decode(tokenized_hash.b64_hash) {
456 Ok(h) => h,
457 Err(_) => {
458 return Err(Argon2Error::InvalidHash(
459 "Invalid character in base64-encoded hash",
460 ))
461 }
462 };
463
464 Ok(Self {
465 alg: tokenized_hash.alg,
466 mem_cost_kib: tokenized_hash.mem_cost_kib,
467 iterations: tokenized_hash.iterations,
468 threads: tokenized_hash.threads,
469 salt: Cow::Owned(decoded_salt),
470 hash: Cow::Owned(decoded_hash),
471 })
472 }
473}
474
475impl<'a> Hash<'a> {
476 pub fn from_parts(
478 hash: &'a [u8],
479 salt: &'a [u8],
480 alg: Algorithm,
481 mem_cost_kib: u32,
482 iterations: u32,
483 threads: u32,
484 ) -> Self {
485 Self {
486 alg,
487 mem_cost_kib,
488 iterations,
489 threads,
490 salt: Cow::Borrowed(salt),
491 hash: Cow::Borrowed(hash),
492 }
493 }
494
495 pub fn as_bytes(&self) -> &[u8] {
497 self.hash.as_ref()
498 }
499
500 pub fn salt_bytes(&self) -> &[u8] {
502 self.salt.as_ref()
503 }
504
505 pub fn algorithm(&self) -> Algorithm {
507 self.alg
508 }
509
510 pub fn memory_cost_kib(&self) -> u32 {
512 self.mem_cost_kib
513 }
514
515 pub fn iterations(&self) -> u32 {
517 self.iterations
518 }
519
520 pub fn threads(&self) -> u32 {
522 self.threads
523 }
524
525 pub fn verify(&self, password: &[u8]) -> bool {
532 self.verify_with_or_without_secret(password, None)
533 }
534
535 pub fn verify_with_secret(&self, password: &[u8], secret: Secret) -> bool {
542 self.verify_with_or_without_secret(password, Some(secret))
543 }
544
545 #[inline]
546 fn verify_with_or_without_secret(&self, password: &[u8], secret: Option<Secret>) -> bool {
547 let hash_length: u32 = match self.hash.len().try_into() {
548 Ok(l) => l,
549 Err(_) => return false,
550 };
551
552 let mut hash_builder = Hasher::default()
553 .algorithm(self.alg)
554 .custom_salt(&self.salt)
555 .hash_length(hash_length)
556 .iterations(self.iterations)
557 .memory_cost_kib(self.mem_cost_kib)
558 .threads(self.threads);
559
560 if let Some(s) = secret {
561 hash_builder = hash_builder.secret(s);
562 }
563
564 let hashed_password = match hash_builder.hash(password) {
565 Ok(h) => h,
566 Err(_) => return false,
567 };
568
569 let mut hashes_dont_match = 0u8;
570
571 if self.hash.len() != hashed_password.hash.len() || self.hash.is_empty() {
572 return false;
573 }
574
575 for (i, hash_byte) in hashed_password.hash.iter().enumerate() {
578 unsafe {
579 hashes_dont_match |= hash_byte ^ self.hash.get_unchecked(i);
580 }
581 }
582
583 hashes_dont_match == 0
584 }
585}
586
587#[cfg(test)]
588mod tests {
589 use super::*;
590
591 #[test]
592 fn test_byte_hash_into_hash_string() {
593 let hash = Hash {
594 alg: Algorithm::Argon2id,
595 mem_cost_kib: 128,
596 iterations: 3,
597 threads: 2,
598 salt: Cow::Borrowed(&[1, 2, 3, 4, 5, 6, 7, 8]),
599 hash: Cow::Owned(
600 b64_stdnopad
601 .decode("ypJ3pKxN4aWGkwMv0TOb08OIzwrfK1SZWy64vyTLKo8")
602 .unwrap()
603 .to_vec(),
604 ),
605 };
606
607 assert_eq!(
608 hash.to_string(),
609 String::from(
610 "$argon2id$v=19$m=128,t=3,p=2$AQIDBAUGBwg$ypJ3pKxN4aWGkwMv0TOb08OIzwrfK1SZWy64vyTLKo8"
611 )
612 );
613 }
614
615 #[test]
616 fn test_hash_from_str() {
617 let hash = Hash::from_str(
618 "$argon2id$v=19$m=128,t=3,p=2$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
619 )
620 .unwrap();
621
622 assert_eq!(hash.mem_cost_kib, 128);
623 assert_eq!(hash.iterations, 3);
624 assert_eq!(hash.threads, 2);
625 assert_eq!(hash.salt, b64_stdnopad.decode("AQIDBAUGBwg").unwrap());
626 assert_eq!(
627 hash.hash,
628 b64_stdnopad
629 .decode("7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",)
630 .unwrap()
631 );
632
633 let hash = Hash::from_str(
634 "$argon2id$v=19$t=3,m=128,p=2$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
635 )
636 .unwrap();
637
638 assert_eq!(hash.mem_cost_kib, 128);
639 assert_eq!(hash.iterations, 3);
640 assert_eq!(hash.threads, 2);
641 assert_eq!(hash.salt, b64_stdnopad.decode("AQIDBAUGBwg").unwrap());
642 assert_eq!(
643 hash.hash,
644 b64_stdnopad
645 .decode("7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",)
646 .unwrap()
647 );
648
649 let hash = Hash::from_str(
650 "$argon2id$v=19$p=2,m=128,t=3$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
651 )
652 .unwrap();
653
654 assert_eq!(hash.mem_cost_kib, 128);
655 assert_eq!(hash.iterations, 3);
656 assert_eq!(hash.threads, 2);
657 assert_eq!(hash.salt, b64_stdnopad.decode("AQIDBAUGBwg").unwrap());
658 assert_eq!(
659 hash.hash,
660 b64_stdnopad
661 .decode("7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",)
662 .unwrap()
663 );
664
665 let hash = Hash::from_str(
666 "$argon2id$v=19$t=3,p=2,m=128$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
667 )
668 .unwrap();
669
670 assert_eq!(hash.mem_cost_kib, 128);
671 assert_eq!(hash.iterations, 3);
672 assert_eq!(hash.threads, 2);
673 assert_eq!(hash.salt, b64_stdnopad.decode("AQIDBAUGBwg").unwrap());
674 assert_eq!(
675 hash.hash,
676 b64_stdnopad
677 .decode("7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",)
678 .unwrap()
679 );
680 }
681
682 #[test]
683 fn test_invalid_hash_from_str() {
684 let hash = Hash::from_str(
685 "$argon2id$v=19$m=128,t=3,p=2,$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
686 );
687
688 assert!(hash.is_err());
689
690 let hash = Hash::from_str(
691 "$argon2id$v=19$t=3,m=128,p=2,m=128$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc"
692 );
693
694 assert!(hash.is_err());
695
696 let hash = Hash::from_str(
697 "$argon2i$v=19$p=2m=128,t=3$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
698 );
699
700 assert!(hash.is_err());
701
702 let hash = Hash::from_str(
703 "$argon2id$v=19$p=2m=128,t=3$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
704 );
705
706 assert!(hash.is_err());
707
708 let hash = Hash::from_str(
709 "$argon2id$t=3,p=2,m=128$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
710 );
711
712 assert!(hash.is_err());
713
714 let hash = Hash::from_str(
715 "$argon2$v=19$m=128,t=3,p=2$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
716 );
717
718 assert!(hash.is_err());
719
720 let hash = Hash::from_str(
721 "$argon2id$v=19$m=128,t=3,p=2AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
722 );
723
724 assert!(hash.is_err());
725
726 let hash = Hash::from_str(
727 "$argon2id$v=18$m=128,t=3,p=2$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
728 );
729
730 assert!(hash.is_err());
731
732 let hash = Hash::from_str(
733 "argon2id$v=19$m=128,t=3,p=2$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
734 );
735
736 assert!(hash.is_err());
737
738 let hash = Hash::from_str(
739 "$argon2id$v=19$m=128,t3,p=2$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
740 );
741
742 assert!(hash.is_err());
743
744 let hash = Hash::from_str(
745 "$argon2id$v=19$m=128,t=3,p=2$AQIDBAUGBwg7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
746 );
747
748 assert!(hash.is_err());
749
750 let hash = Hash::from_str(
751 "$argon2id$v=19$m=128,t=3,p=2$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc$",
752 );
753
754 assert!(hash.is_err());
755
756 let hash = Hash::from_str("$argon2id$v=19$m=128,t=3,p=2$AQIDBAUGBwg$$");
757
758 assert!(hash.is_err());
759
760 let hash = Hash::from_str(
761 "$argon2id$v=19$m=128,p=2$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
762 );
763
764 assert!(hash.is_err());
765
766 let hash = Hash::from_str(
767 "$argon2id$v=19$t=2,p=2$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
768 );
769
770 assert!(hash.is_err());
771
772 let hash = Hash::from_str(
773 "$argon2id$v=19$t=2,m=128$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
774 );
775
776 assert!(hash.is_err());
777 }
778
779 #[test]
780 fn test_hash_auth_string_argon2d() {
781 let auth_string = b"@Pa$$20rd-Test";
782
783 let key = [1u8; 32];
784 let hash_builder = Hasher::default()
785 .algorithm(Algorithm::Argon2d)
786 .salt_length(16)
787 .hash_length(32)
788 .iterations(1)
789 .memory_cost_kib(16)
790 .threads(1)
791 .secret((&key).into());
792
793 let hash = hash_builder.hash(auth_string).unwrap().to_string();
794
795 assert!(!Hash::from_str(&hash).unwrap().verify(auth_string));
796
797 assert!(Hash::from_str(&hash)
798 .unwrap()
799 .verify_with_secret(auth_string, (&key).into()));
800 }
801
802 #[test]
803 fn test_hash_auth_string_no_secret() {
804 let auth_string = b"@Pa$$20rd-Test";
805
806 let hash = Hasher::default()
807 .salt_length(16)
808 .hash_length(32)
809 .iterations(1)
810 .memory_cost_kib(16)
811 .threads(1)
812 .hash(auth_string)
813 .unwrap()
814 .to_string();
815
816 assert!(!Hash::from_str(&hash)
817 .unwrap()
818 .verify_with_secret(auth_string, (&[0, 1, 2, 3]).into()));
819
820 assert!(Hash::from_str(&hash).unwrap().verify(auth_string));
821 }
822
823 #[test]
824 fn test_hash_auth_string_argon2i() {
825 let auth_string = b"@Pa$$20rd-Test";
826
827 let key = [1u8; 32];
828 let hash_builder = Hasher::default()
829 .algorithm(Algorithm::Argon2i)
830 .salt_length(16)
831 .hash_length(32)
832 .iterations(1)
833 .memory_cost_kib(16)
834 .threads(1)
835 .secret((&key).into());
836
837 let hash = hash_builder.hash(auth_string).unwrap().to_string();
838
839 assert!(!Hash::from_str(&hash).unwrap().verify(auth_string));
840
841 assert!(Hash::from_str(&hash)
842 .unwrap()
843 .verify_with_secret(auth_string, (&key).into()));
844 }
845
846 #[test]
847 fn test_hash_auth_string_argon2id() {
848 let auth_string = b"@Pa$$20rd-Test";
849
850 let key = [1u8; 32];
851 let hash_builder = Hasher::new()
852 .algorithm(Algorithm::Argon2id)
853 .salt_length(16)
854 .hash_length(32)
855 .iterations(1)
856 .memory_cost_kib(16)
857 .threads(1)
858 .secret((&key).into());
859
860 let hash = hash_builder.hash(auth_string).unwrap().to_string();
861
862 assert!(!Hash::from_str(&hash).unwrap().verify(auth_string));
863
864 assert!(Hash::from_str(&hash)
865 .unwrap()
866 .verify_with_secret(auth_string, (&key).into()));
867 }
868
869 #[test]
870 fn test_get_fields() {
871 let auth_string = b"@Pa$$20rd-Test";
872 let salt = b"seasalts";
873
874 let hash_builder = Hasher::new()
875 .algorithm(Algorithm::Argon2d)
876 .custom_salt(salt)
877 .hash_length(32)
878 .iterations(1)
879 .memory_cost_kib(16)
880 .threads(1);
881
882 let hash = hash_builder.hash(auth_string).unwrap().to_string();
883 let hash = Hash::from_str(&hash).unwrap();
884
885 assert!(hash.verify(auth_string));
886
887 assert!(matches!(hash.algorithm(), Algorithm::Argon2d));
888 assert_eq!(hash.salt_bytes(), salt);
889 assert_eq!(hash.as_bytes().len(), 32);
890 assert_eq!(hash.iterations(), 1);
891 assert_eq!(hash.memory_cost_kib(), 16);
892 assert_eq!(hash.threads(), 1);
893 }
894
895 #[test]
896 fn test_custom_salt() {
897 let auth_string = b"@Pa$$20rd-Test";
898 let salt = b"seasalts";
899
900 let hash = Hasher::default()
901 .custom_salt(salt)
902 .hash(auth_string)
903 .unwrap();
904
905 assert_eq!(hash.salt.as_ref(), salt);
906
907 let hash_string = hash.to_string();
908
909 assert!(!Hash::from_str(&hash_string)
910 .unwrap()
911 .verify_with_secret(auth_string, (&[0, 1, 2, 3]).into()));
912
913 assert!(Hash::from_str(&hash_string).unwrap().verify(auth_string));
914 }
915
916 #[test]
917 fn test_verify_hash() {
918 let auth_string = b"@Pa$$20rd-Test";
919
920 let key = [0u8; 32];
921 let hash_builder = Hasher::default()
922 .salt_length(16)
923 .hash_length(32)
924 .iterations(1)
925 .memory_cost_kib(16)
926 .threads(1)
927 .secret((&key).into());
928
929 let hash = hash_builder.hash(auth_string).unwrap().to_string();
930
931 assert!(!Hash::from_str(&hash).unwrap().verify(auth_string));
932
933 assert!(Hash::from_str(&hash)
934 .unwrap()
935 .verify_with_secret(auth_string, (&key).into()));
936 }
937
938 #[test]
939 fn test_verify_incorrect_auth_string() {
940 let auth_string = b"@Pa$$20rd-Test";
941
942 let key = [0u8; 32];
943 let hash_builder = Hasher::default()
944 .salt_length(16)
945 .hash_length(32)
946 .iterations(1)
947 .memory_cost_kib(16)
948 .threads(1)
949 .secret((&key).into());
950
951 let hash = hash_builder.hash(auth_string).unwrap().to_string();
952
953 assert!(!Hash::from_str(&hash)
954 .unwrap()
955 .verify_with_secret(b"@Pa$$20rd-Tests", (&key).into()));
956 }
957
958 #[test]
959 fn test_verify_incorrect_key() {
960 let auth_string = b"@Pa$$20rd-Test";
961
962 let key = [0u8; 32];
963 let hash_builder = Hasher::default()
964 .salt_length(16)
965 .hash_length(32)
966 .iterations(1)
967 .memory_cost_kib(16)
968 .threads(1)
969 .secret((&key).into());
970
971 let hash = hash_builder.hash(auth_string).unwrap().to_string();
972
973 assert!(!Hash::from_str(&hash)
974 .unwrap()
975 .verify_with_secret(auth_string, (&[0u8; 33]).into()));
976 }
977
978 #[test]
979 fn test_from_parts() {
980 let hash = Hash::from_parts(
981 &[155, 147, 76, 205, 220, 49, 114, 102],
982 b"testsalt",
983 Algorithm::Argon2id,
984 16,
985 1,
986 1,
987 );
988
989 assert!(matches!(hash.algorithm(), Algorithm::Argon2id));
990 assert_eq!(hash.mem_cost_kib, 16);
991 assert_eq!(hash.iterations, 1);
992 assert_eq!(hash.threads, 1);
993 assert_eq!(hash.salt.as_ref(), b"testsalt");
994 assert_eq!(hash.hash.as_ref(), &[155, 147, 76, 205, 220, 49, 114, 102]);
995
996 assert!(hash.verify(b"password"));
997 }
998}