argon2_kdf/
hasher.rs

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/// The Argon2 spec consist of 3 different algorithms: one that aims to be resistant to GPU
18/// cracking attacks (argon2d), one that aims to be resistant to side-channel attacks
19/// (argon2i), and a hybrid algorithm that aims to be resistant to both types of attacks.
20/// See <https://en.wikipedia.org/wiki/Argon2> for more information.
21///
22/// Argon2id is a good default. The other algorithms should only be used in rare cases,
23/// preferably only when a cryptography expert can validate that using one of the other two
24/// algorithms is safe.
25#[derive(Clone, Copy, Debug)]
26pub enum Algorithm {
27    /// GPU-cracking attack resistant/memory-hard
28    Argon2d,
29
30    /// Side-channel attack resistant
31    Argon2i,
32
33    /// GPU-cracking attack resistant/memory-hard and side-channel attack resistant
34    Argon2id,
35}
36
37/// A secret that mixes with a password (and a salt) to create a hash. This is sometimes
38/// referred to as a "[pepper](https://en.wikipedia.org/wiki/Pepper_(cryptography))."
39///
40/// A 32-byte key is recommended. Do not use an alphanumeric password or passphrase; the
41/// entrophy of a 32-character password is much lower than the entrophy of a 32-byte key. This
42/// key should be generated with a cryptographically-secure random number generator and stored
43/// securely.
44#[derive(Clone, Copy, Debug)]
45pub struct Secret<'a>(&'a [u8]);
46
47impl<'a> Secret<'a> {
48    /// Wraps a reference to a slice containing a secret key
49    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/// A builder for a hash. Parameters for hashing, such as
85#[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    /// Create a new `Hasher` with default values.
99    ///
100    /// This provides some reasonable defaults, but it is recommended that you tinker with
101    /// these parameters to find the best settings for your application. The more resources the
102    /// hashing requires, the stronger the hash. Increase the memory cost (and perhaps the
103    /// parallelization factor) as high as your application can afford, then likewise raise the
104    /// iteration count.
105    ///
106    /// Unless you are _absolutely positive_ you want to use a different algorithm, use
107    /// the default argon2id algorithm for password hashing and key derivation.
108    ///
109    /// The defaults are as follows:
110    ///
111    /// * Algorithm: Argon2id
112    /// * Salt Length: 16 bytes
113    /// * Hash Length: 32 bytes
114    /// * Iterations: 18
115    /// * Memory Cost: 62500 kibibytes (equal to 64 megabytes)
116    /// * Parallelization Factor: 1 thread
117    ///
118    /// `Hasher` allows for a secret, sometimes called a
119    /// "[pepper](https://en.wikipedia.org/wiki/Pepper_(cryptography))," to be mixed with the
120    /// password before hashing. `Hasher` can be used securely without a secret, though
121    /// high-security applications might consider using one.
122    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    /// Create a new `Hasher` with default values.
138    ///
139    /// This provides some reasonable defaults, but it is recommended that you tinker with
140    /// these parameters to find the best settings for your application. The more resources the
141    /// hashing requires, the stronger the hash. Increase the memory cost (and perhaps the
142    /// parallelization factor) as high as your application can afford, then likewise raise the
143    /// iteration count.
144    ///
145    /// Unless you are _absolutely positive_ you want to use a different algorithm, use
146    /// the default argon2id algorithm for password hashing and key derivation.
147    ///
148    /// The defaults are as follows:
149    ///
150    /// * Algorithm: Argon2id
151    /// * Salt Length: 16 bytes
152    /// * Hash Length: 32 bytes
153    /// * Iterations: 18
154    /// * Memory Cost: 62500 kibibytes (equal to 64 megabytes)
155    /// * Parallelization Factor: 1 thread
156    ///
157    /// `Hasher` allows for a secret, sometimes called a
158    /// "[pepper](https://en.wikipedia.org/wiki/Pepper_(cryptography))," to be mixed with the
159    /// password before hashing. `Hasher` can be used securely without a secret, though
160    /// high-security applications might consider using one.
161    pub fn new() -> Self {
162        Self::default()
163    }
164
165    /// Specifies the hashing algorithm to use.
166    ///
167    /// The Argon2 spec consist of 3 different algorithms: one that aims to be resistant to GPU
168    /// cracking attacks (argon2d), one that aims to be resistant to side-channel attacks
169    /// (argon2i), and a hybrid algorithm that aims to be resistant to both types of attacks.
170    /// See <https://en.wikipedia.org/wiki/Argon2> for more information.
171    ///
172    /// Argon2id is a good default. The other algorithms should only be used in rare cases,
173    /// preferably only when a cryptography expert can validate that using one of the other two
174    /// algorithms is safe.
175    pub fn algorithm(mut self, alg: Algorithm) -> Self {
176        self.alg = alg;
177        self
178    }
179
180    /// When left unspecified, a salt is generated using a cryptographically-secure random
181    /// number generator. In most cases, this function should not be used. Only use this
182    /// function if you are trying to generate a hash deterministically with a known salt and
183    /// a randomly generated salt will not suffice.
184    pub fn custom_salt(mut self, salt: &'a [u8]) -> Self {
185        self.custom_salt = Some(salt);
186        self
187    }
188
189    /// The length of the salt for the hash, in bytes. Using salt that is too short can lower
190    /// the strength of the generated hash. 16 bytes is a reasonable default salt length.
191    ///
192    /// If a salt is specified manually using [`custom_salt()`], the length of the provided
193    /// salt will override the length specified here.
194    pub fn salt_length(mut self, salt_len: u32) -> Self {
195        self.salt_len = salt_len;
196        self
197    }
198
199    /// The length of the resulting hash, in bytes.
200    ///
201    /// Short hashes can be insecure. The shorter the hash, the greater the chance of
202    /// collisions. A 32-byte hash should be plenty for any application.
203    ///
204    /// Note that the length of the hash _string_ will be different; the hash string specifies
205    /// parameters and the salt used to generate the hash. The hash is base64-encoded in the
206    /// hash string, so even the hash itself is longer in the hash string than the specified
207    /// number of bytes.
208    ///
209    /// A hash is just an array of bytes, whereas a hash string looks something like this:
210    ///
211    /// _$argon2id$v=19$m=62500,t=18,p=2$AQIDBAUGBwg$ypJ3pKxN4aWGkwMv0TOb08OIzwrfK1SZWy64vyTLKo8_
212    pub fn hash_length(mut self, hash_len: u32) -> Self {
213        self.hash_len = hash_len;
214        self
215    }
216
217    /// The number of times the hashing algorithm is repeated in order to slow down the hashing
218    /// and thwart those pesky hackers.
219    pub fn iterations(mut self, iterations: u32) -> Self {
220        self.iterations = iterations;
221        self
222    }
223
224    /// The amount of memory required to compute a hash. This is where a lot of the magic of
225    /// Argon2 happens. By setting a hard memory requirement for generating a hash,
226    /// brute-forcing a password becomes infeasable even for well-funded adversaries with
227    /// access to a lot of processing power.
228    ///
229    /// Set this parameter as high as you can afford to. Be cautious setting this lower than
230    /// 62500 KiB (64 MB). If reasonable, increase this to 125000 KiB (128 MB) or 250000 KiB
231    /// (256 MB) (or even higher were security is critical).
232    pub fn memory_cost_kib(mut self, cost: u32) -> Self {
233        self.mem_cost_kib = cost;
234        self
235    }
236
237    /// The number of CPU threads required to generate a hash. If this is set higher than the
238    /// total number of logical CPU cores on a given machine, hashing may fail or take an
239    /// astronomically long time to generate on said machine.
240    ///
241    /// While increasing the thread count does strengthen the hash, it is impractical to raise
242    /// this parameter for some applications. Aim to increase the memory cost before increasing
243    /// the thread count. With a high memory cost, just 1 thread can still provide excellent
244    /// security.
245    pub fn threads(mut self, threads: u32) -> Self {
246        self.threads = threads;
247        self
248    }
249
250    /// A secret that mixes with a password (and a salt) to create a hash. This is sometimes
251    /// referred to as a "[pepper](https://en.wikipedia.org/wiki/Pepper_(cryptography))."
252    ///
253    /// This secret is not necessary to generate strong hashes, though high-security
254    /// applications might consider using a secret. Many argon2 libraries don't expose this
255    /// parameter (because it isn't necessary), so using a secret can limit interoperability
256    /// with other languages/libraries.
257    ///
258    /// A 32-byte key is recommended. Do not use an alphanumeric password or passphrase; the
259    /// entrophy of a 32-character password is much lower than the entrophy of a 32-byte key.
260    /// This key should be generated with a cryptographically-secure random number generator
261    /// and stored securely.
262    pub fn secret(mut self, secret: Secret<'a>) -> Self {
263        self.secret = Some(secret);
264        self
265    }
266
267    /// Consumes the `Hasher` and returns a hash.
268    ///
269    /// This is an expensive operation. For some appliations, it might make sense to move this
270    /// operation to a separate thread using `std::thread` or something like
271    /// [the Rayon crate](https://docs.rs/rayon/latest/rayon/) to avoid blocking main threads.
272    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        // Some buffers here are cast to *mut to pass to C. The C code does not modify these
332        // buffers so this is safe
333        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/// A container for an Argon2 hash, the corresponding salt, and the parameters used for hashing
385#[derive(Clone, Debug)]
386pub struct Hash {
387    /// The algorithm used for hashing (Argon2d, Argon2i, or Argon2id)
388    pub alg: Algorithm,
389    /// The memory cost in kibibytes
390    pub mem_cost_kib: u32,
391    /// The number of iterations used for hashing
392    pub iterations: u32,
393    /// The number of threads used for hashing
394    pub threads: u32,
395    /// The salt used to generate the hash
396    pub salt: Vec<u8>,
397    /// The hash in bytes
398    pub hash: Vec<u8>,
399}
400
401#[allow(clippy::to_string_trait_impl)]
402impl ToString for Hash {
403    /// Generates a hash string. Aside from the hash, the hash string also includes the salt
404    /// and paramters used to generate the hash, making it easy to store in a database or a
405    /// cache. This string is formatted to a standard shared by most implementations of argon2,
406    /// so other argon2 libraries should be able to use this hash string.
407    ///
408    /// A hash string looks something like this:
409    ///
410    /// _$argon2id$v=19$m=62500,t=18,p=2$AQIDBAUGBwg$ypJ3pKxN4aWGkwMv0TOb08OIzwrfK1SZWy64vyTLKo8_
411    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    /// Deserializes a hash string into parts (e.g. the hash, the salt, parameters) that can
438    /// be used for purposes such as verification or encryption.
439    ///
440    /// A hash string looks something like this:
441    ///
442    /// _$argon2id$v=19$m=62500,t=18,p=2$AQIDBAUGBwg$ypJ3pKxN4aWGkwMv0TOb08OIzwrfK1SZWy64vyTLKo8_
443    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    /// Returns a reference to a byte slice of the computed hash/key.
481    pub fn as_bytes(&self) -> &[u8] {
482        &self.hash
483    }
484
485    /// Returns a reference to a byte slice of the salt used to generate the hash.
486    pub fn salt_bytes(&self) -> &[u8] {
487        &self.salt
488    }
489
490    /// Returns the algorithm used to generate the hash.
491    pub fn algorithm(&self) -> Algorithm {
492        self.alg
493    }
494
495    /// Returns the memory cost used to generate the hash.
496    pub fn memory_cost_kib(&self) -> u32 {
497        self.mem_cost_kib
498    }
499
500    /// Returns the number of iterations used to generate the hash.
501    pub fn iterations(&self) -> u32 {
502        self.iterations
503    }
504
505    /// Returns the number of threads used to generate the hash.
506    pub fn threads(&self) -> u32 {
507        self.threads
508    }
509
510    /// Checks if the hash matches the provided password.
511    ///
512    /// Because verification requires re-hashing the password, this is an expensive operation.
513    /// For some appliations, it might make sense to move this operation to a separate thread
514    /// using `std::thread` or something like
515    /// [the Rayon crate](https://docs.rs/rayon/latest/rayon/) to avoid blocking main threads.
516    pub fn verify(&self, password: &[u8]) -> bool {
517        self.verify_with_or_without_secret(password, None)
518    }
519
520    /// Checks if the hash matches the provided password using the provided secret.
521    ///
522    /// Because verification requires re-hashing the password, this is an expensive operation.
523    /// For some appliations, it might make sense to move this operation to a separate thread
524    /// using `std::thread` or something like
525    /// [the Rayon crate](https://docs.rs/rayon/latest/rayon/) to avoid blocking main threads.
526    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        // Do bitwise comparison to prevent timing attacks (entire length of string must be
561        // compared)
562        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}