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 as _,
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 as _ {
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 from_parts_owned(
498 hash: Vec<u8>,
499 salt: Vec<u8>,
500 alg: Algorithm,
501 mem_cost_kib: u32,
502 iterations: u32,
503 threads: u32,
504 ) -> Self {
505 Self {
506 alg,
507 mem_cost_kib,
508 iterations,
509 threads,
510 salt: Cow::Owned(salt),
511 hash: Cow::Owned(hash),
512 }
513 }
514
515 pub fn as_bytes(&self) -> &[u8] {
517 self.hash.as_ref()
518 }
519
520 pub fn salt_bytes(&self) -> &[u8] {
522 self.salt.as_ref()
523 }
524
525 pub fn algorithm(&self) -> Algorithm {
527 self.alg
528 }
529
530 pub fn memory_cost_kib(&self) -> u32 {
532 self.mem_cost_kib
533 }
534
535 pub fn iterations(&self) -> u32 {
537 self.iterations
538 }
539
540 pub fn threads(&self) -> u32 {
542 self.threads
543 }
544
545 pub fn verify(&self, password: &[u8]) -> bool {
552 self.verify_with_or_without_secret(password, None)
553 }
554
555 pub fn verify_with_secret(&self, password: &[u8], secret: Secret) -> bool {
562 self.verify_with_or_without_secret(password, Some(secret))
563 }
564
565 #[inline]
566 fn verify_with_or_without_secret(&self, password: &[u8], secret: Option<Secret>) -> bool {
567 let hash_length: u32 = match self.hash.len().try_into() {
568 Ok(l) => l,
569 Err(_) => return false,
570 };
571
572 let mut hash_builder = Hasher::default()
573 .algorithm(self.alg)
574 .custom_salt(&self.salt)
575 .hash_length(hash_length)
576 .iterations(self.iterations)
577 .memory_cost_kib(self.mem_cost_kib)
578 .threads(self.threads);
579
580 if let Some(s) = secret {
581 hash_builder = hash_builder.secret(s);
582 }
583
584 let hashed_password = match hash_builder.hash(password) {
585 Ok(h) => h,
586 Err(_) => return false,
587 };
588
589 let mut hashes_dont_match = 0u8;
590
591 if self.hash.len() != hashed_password.hash.len() || self.hash.is_empty() {
592 return false;
593 }
594
595 for (i, hash_byte) in hashed_password.hash.iter().enumerate() {
598 unsafe {
599 hashes_dont_match |= hash_byte ^ self.hash.get_unchecked(i);
600 }
601 }
602
603 hashes_dont_match == 0
604 }
605}
606
607#[cfg(test)]
608mod tests {
609 use super::*;
610
611 #[test]
612 fn test_byte_hash_into_hash_string() {
613 let hash = Hash {
614 alg: Algorithm::Argon2id,
615 mem_cost_kib: 128,
616 iterations: 3,
617 threads: 2,
618 salt: Cow::Borrowed(&[1, 2, 3, 4, 5, 6, 7, 8]),
619 hash: Cow::Owned(
620 b64_stdnopad
621 .decode("ypJ3pKxN4aWGkwMv0TOb08OIzwrfK1SZWy64vyTLKo8")
622 .unwrap()
623 .to_vec(),
624 ),
625 };
626
627 assert_eq!(
628 hash.to_string(),
629 String::from(
630 "$argon2id$v=19$m=128,t=3,p=2$AQIDBAUGBwg$ypJ3pKxN4aWGkwMv0TOb08OIzwrfK1SZWy64vyTLKo8"
631 )
632 );
633 }
634
635 #[test]
636 fn test_hash_from_str() {
637 let hash = Hash::from_str(
638 "$argon2id$v=19$m=128,t=3,p=2$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
639 )
640 .unwrap();
641
642 assert_eq!(hash.mem_cost_kib, 128);
643 assert_eq!(hash.iterations, 3);
644 assert_eq!(hash.threads, 2);
645 assert_eq!(hash.salt, b64_stdnopad.decode("AQIDBAUGBwg").unwrap());
646 assert_eq!(
647 hash.hash,
648 b64_stdnopad
649 .decode("7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",)
650 .unwrap()
651 );
652
653 let hash = Hash::from_str(
654 "$argon2id$v=19$t=3,m=128,p=2$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
655 )
656 .unwrap();
657
658 assert_eq!(hash.mem_cost_kib, 128);
659 assert_eq!(hash.iterations, 3);
660 assert_eq!(hash.threads, 2);
661 assert_eq!(hash.salt, b64_stdnopad.decode("AQIDBAUGBwg").unwrap());
662 assert_eq!(
663 hash.hash,
664 b64_stdnopad
665 .decode("7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",)
666 .unwrap()
667 );
668
669 let hash = Hash::from_str(
670 "$argon2id$v=19$p=2,m=128,t=3$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
671 )
672 .unwrap();
673
674 assert_eq!(hash.mem_cost_kib, 128);
675 assert_eq!(hash.iterations, 3);
676 assert_eq!(hash.threads, 2);
677 assert_eq!(hash.salt, b64_stdnopad.decode("AQIDBAUGBwg").unwrap());
678 assert_eq!(
679 hash.hash,
680 b64_stdnopad
681 .decode("7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",)
682 .unwrap()
683 );
684
685 let hash = Hash::from_str(
686 "$argon2id$v=19$t=3,p=2,m=128$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
687 )
688 .unwrap();
689
690 assert_eq!(hash.mem_cost_kib, 128);
691 assert_eq!(hash.iterations, 3);
692 assert_eq!(hash.threads, 2);
693 assert_eq!(hash.salt, b64_stdnopad.decode("AQIDBAUGBwg").unwrap());
694 assert_eq!(
695 hash.hash,
696 b64_stdnopad
697 .decode("7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",)
698 .unwrap()
699 );
700 }
701
702 #[test]
703 fn test_invalid_hash_from_str() {
704 let hash = Hash::from_str(
705 "$argon2id$v=19$m=128,t=3,p=2,$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
706 );
707
708 assert!(hash.is_err());
709
710 let hash = Hash::from_str(
711 "$argon2id$v=19$t=3,m=128,p=2,m=128$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc"
712 );
713
714 assert!(hash.is_err());
715
716 let hash = Hash::from_str(
717 "$argon2i$v=19$p=2m=128,t=3$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
718 );
719
720 assert!(hash.is_err());
721
722 let hash = Hash::from_str(
723 "$argon2id$v=19$p=2m=128,t=3$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
724 );
725
726 assert!(hash.is_err());
727
728 let hash = Hash::from_str(
729 "$argon2id$t=3,p=2,m=128$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
730 );
731
732 assert!(hash.is_err());
733
734 let hash = Hash::from_str(
735 "$argon2$v=19$m=128,t=3,p=2$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
736 );
737
738 assert!(hash.is_err());
739
740 let hash = Hash::from_str(
741 "$argon2id$v=19$m=128,t=3,p=2AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
742 );
743
744 assert!(hash.is_err());
745
746 let hash = Hash::from_str(
747 "$argon2id$v=18$m=128,t=3,p=2$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
748 );
749
750 assert!(hash.is_err());
751
752 let hash = Hash::from_str(
753 "argon2id$v=19$m=128,t=3,p=2$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
754 );
755
756 assert!(hash.is_err());
757
758 let hash = Hash::from_str(
759 "$argon2id$v=19$m=128,t3,p=2$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
760 );
761
762 assert!(hash.is_err());
763
764 let hash = Hash::from_str(
765 "$argon2id$v=19$m=128,t=3,p=2$AQIDBAUGBwg7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
766 );
767
768 assert!(hash.is_err());
769
770 let hash = Hash::from_str(
771 "$argon2id$v=19$m=128,t=3,p=2$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc$",
772 );
773
774 assert!(hash.is_err());
775
776 let hash = Hash::from_str("$argon2id$v=19$m=128,t=3,p=2$AQIDBAUGBwg$$");
777
778 assert!(hash.is_err());
779
780 let hash = Hash::from_str(
781 "$argon2id$v=19$m=128,p=2$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
782 );
783
784 assert!(hash.is_err());
785
786 let hash = Hash::from_str(
787 "$argon2id$v=19$t=2,p=2$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
788 );
789
790 assert!(hash.is_err());
791
792 let hash = Hash::from_str(
793 "$argon2id$v=19$t=2,m=128$AQIDBAUGBwg$7OU7S/azjYpnXXySR52cFWeisxk1VVjNeXqtQ8ZM/Oc",
794 );
795
796 assert!(hash.is_err());
797 }
798
799 #[test]
800 fn test_hash_auth_string_argon2d() {
801 let auth_string = b"@Pa$$20rd-Test";
802
803 let key = [1u8; 32];
804 let hash_builder = Hasher::default()
805 .algorithm(Algorithm::Argon2d)
806 .salt_length(16)
807 .hash_length(32)
808 .iterations(1)
809 .memory_cost_kib(16)
810 .threads(1)
811 .secret((&key).into());
812
813 let hash = hash_builder.hash(auth_string).unwrap().to_string();
814
815 assert!(!Hash::from_str(&hash).unwrap().verify(auth_string));
816
817 assert!(Hash::from_str(&hash)
818 .unwrap()
819 .verify_with_secret(auth_string, (&key).into()));
820 }
821
822 #[test]
823 fn test_hash_auth_string_no_secret() {
824 let auth_string = b"@Pa$$20rd-Test";
825
826 let hash = Hasher::default()
827 .salt_length(16)
828 .hash_length(32)
829 .iterations(1)
830 .memory_cost_kib(16)
831 .threads(1)
832 .hash(auth_string)
833 .unwrap()
834 .to_string();
835
836 assert!(!Hash::from_str(&hash)
837 .unwrap()
838 .verify_with_secret(auth_string, (&[0, 1, 2, 3]).into()));
839
840 assert!(Hash::from_str(&hash).unwrap().verify(auth_string));
841 }
842
843 #[test]
844 fn test_hash_auth_string_argon2i() {
845 let auth_string = b"@Pa$$20rd-Test";
846
847 let key = [1u8; 32];
848 let hash_builder = Hasher::default()
849 .algorithm(Algorithm::Argon2i)
850 .salt_length(16)
851 .hash_length(32)
852 .iterations(1)
853 .memory_cost_kib(16)
854 .threads(1)
855 .secret((&key).into());
856
857 let hash = hash_builder.hash(auth_string).unwrap().to_string();
858
859 assert!(!Hash::from_str(&hash).unwrap().verify(auth_string));
860
861 assert!(Hash::from_str(&hash)
862 .unwrap()
863 .verify_with_secret(auth_string, (&key).into()));
864 }
865
866 #[test]
867 fn test_hash_auth_string_argon2id() {
868 let auth_string = b"@Pa$$20rd-Test";
869
870 let key = [1u8; 32];
871 let hash_builder = Hasher::new()
872 .algorithm(Algorithm::Argon2id)
873 .salt_length(16)
874 .hash_length(32)
875 .iterations(1)
876 .memory_cost_kib(16)
877 .threads(1)
878 .secret((&key).into());
879
880 let hash = hash_builder.hash(auth_string).unwrap().to_string();
881
882 assert!(!Hash::from_str(&hash).unwrap().verify(auth_string));
883
884 assert!(Hash::from_str(&hash)
885 .unwrap()
886 .verify_with_secret(auth_string, (&key).into()));
887 }
888
889 #[test]
890 fn test_get_fields() {
891 let auth_string = b"@Pa$$20rd-Test";
892 let salt = b"seasalts";
893
894 let hash_builder = Hasher::new()
895 .algorithm(Algorithm::Argon2d)
896 .custom_salt(salt)
897 .hash_length(32)
898 .iterations(1)
899 .memory_cost_kib(16)
900 .threads(1);
901
902 let hash = hash_builder.hash(auth_string).unwrap().to_string();
903 let hash = Hash::from_str(&hash).unwrap();
904
905 assert!(hash.verify(auth_string));
906
907 assert!(matches!(hash.algorithm(), Algorithm::Argon2d));
908 assert_eq!(hash.salt_bytes(), salt);
909 assert_eq!(hash.as_bytes().len(), 32);
910 assert_eq!(hash.iterations(), 1);
911 assert_eq!(hash.memory_cost_kib(), 16);
912 assert_eq!(hash.threads(), 1);
913 }
914
915 #[test]
916 fn test_custom_salt() {
917 let auth_string = b"@Pa$$20rd-Test";
918 let salt = b"seasalts";
919
920 let hash = Hasher::default()
921 .custom_salt(salt)
922 .hash(auth_string)
923 .unwrap();
924
925 assert_eq!(hash.salt.as_ref(), salt);
926
927 let hash_string = hash.to_string();
928
929 assert!(!Hash::from_str(&hash_string)
930 .unwrap()
931 .verify_with_secret(auth_string, (&[0, 1, 2, 3]).into()));
932
933 assert!(Hash::from_str(&hash_string).unwrap().verify(auth_string));
934 }
935
936 #[test]
937 fn test_verify_hash() {
938 let auth_string = b"@Pa$$20rd-Test";
939
940 let key = [0u8; 32];
941 let hash_builder = Hasher::default()
942 .salt_length(16)
943 .hash_length(32)
944 .iterations(1)
945 .memory_cost_kib(16)
946 .threads(1)
947 .secret((&key).into());
948
949 let hash = hash_builder.hash(auth_string).unwrap().to_string();
950
951 assert!(!Hash::from_str(&hash).unwrap().verify(auth_string));
952
953 assert!(Hash::from_str(&hash)
954 .unwrap()
955 .verify_with_secret(auth_string, (&key).into()));
956 }
957
958 #[test]
959 fn test_verify_incorrect_auth_string() {
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(b"@Pa$$20rd-Tests", (&key).into()));
976 }
977
978 #[test]
979 fn test_verify_incorrect_key() {
980 let auth_string = b"@Pa$$20rd-Test";
981
982 let key = [0u8; 32];
983 let hash_builder = Hasher::default()
984 .salt_length(16)
985 .hash_length(32)
986 .iterations(1)
987 .memory_cost_kib(16)
988 .threads(1)
989 .secret((&key).into());
990
991 let hash = hash_builder.hash(auth_string).unwrap().to_string();
992
993 assert!(!Hash::from_str(&hash)
994 .unwrap()
995 .verify_with_secret(auth_string, (&[0u8; 33]).into()));
996 }
997
998 #[test]
999 fn test_from_parts() {
1000 let hash = Hash::from_parts(
1001 &[155, 147, 76, 205, 220, 49, 114, 102],
1002 b"testsalt",
1003 Algorithm::Argon2id,
1004 16,
1005 1,
1006 1,
1007 );
1008
1009 assert!(matches!(hash.algorithm(), Algorithm::Argon2id));
1010 assert_eq!(hash.mem_cost_kib, 16);
1011 assert_eq!(hash.iterations, 1);
1012 assert_eq!(hash.threads, 1);
1013 assert_eq!(hash.salt.as_ref(), b"testsalt");
1014 assert_eq!(hash.hash.as_ref(), &[155, 147, 76, 205, 220, 49, 114, 102]);
1015
1016 assert!(hash.verify(b"password"));
1017 }
1018
1019 #[test]
1020 fn test_from_parts_owned() {
1021 let hash = Hash::from_parts_owned(
1022 vec![155, 147, 76, 205, 220, 49, 114, 102],
1023 Vec::from(b"testsalt"),
1024 Algorithm::Argon2id,
1025 16,
1026 1,
1027 1,
1028 );
1029
1030 assert!(matches!(hash.algorithm(), Algorithm::Argon2id));
1031 assert_eq!(hash.mem_cost_kib, 16);
1032 assert_eq!(hash.iterations, 1);
1033 assert_eq!(hash.threads, 1);
1034 assert_eq!(hash.salt.as_ref(), b"testsalt");
1035 assert_eq!(hash.hash.as_ref(), &[155, 147, 76, 205, 220, 49, 114, 102]);
1036
1037 assert!(hash.verify(b"password"));
1038 }
1039}