Skip to main content

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