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