Skip to main content

argon2/
lib.rs

1#![no_std]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![doc = include_str!("../README.md")]
4#![doc(
5    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
6    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg"
7)]
8#![warn(
9    clippy::cast_lossless,
10    clippy::cast_possible_truncation,
11    clippy::cast_possible_wrap,
12    clippy::cast_precision_loss,
13    clippy::cast_sign_loss,
14    clippy::checked_conversions,
15    clippy::implicit_saturating_sub,
16    clippy::missing_safety_doc,
17    clippy::panic,
18    clippy::panic_in_result_fn,
19    clippy::undocumented_unsafe_blocks,
20    clippy::unwrap_used,
21    missing_docs,
22    rust_2018_idioms,
23    unused_lifetimes,
24    unused_qualifications
25)]
26
27//! ## Usage
28//!
29//! ### Password Hashing
30//!
31//! This API hashes a password to a "PHC string" suitable for the purposes of
32//! password-based authentication. Do not use this API to derive cryptographic
33//! keys: see the "key derivation" usage example below.
34//!
35#![cfg_attr(all(feature = "alloc", feature = "getrandom"), doc = "```")]
36#![cfg_attr(not(all(feature = "alloc", feature = "getrandom")), doc = "```ignore")]
37//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
38//! // NOTE: example requires `getrandom` feature is enabled
39//!
40//! use argon2::{
41//!     password_hash::{PasswordHasher, PasswordVerifier, phc::PasswordHash},
42//!     Argon2
43//! };
44//!
45//! let password = b"hunter42"; // Bad password; don't actually use!
46//!
47//! // Argon2 with default params (Argon2id v19), generating a random salt
48//! let argon2 = Argon2::default();
49//!
50//! // Hash password to PHC string ($argon2id$v=19$...)
51//! let password_hash = argon2.hash_password(password)?.to_string();
52//!
53//! // Verify password against PHC string.
54//! //
55//! // NOTE: hash params from `parsed_hash` are used instead of what is configured in the
56//! // `Argon2` instance.
57//! let parsed_hash = PasswordHash::new(&password_hash)?;
58//! assert!(Argon2::default().verify_password(password, &parsed_hash).is_ok());
59//! # Ok(())
60//! # }
61//! ```
62//!
63//! To [pepper] as well as salt your passwords:
64//!
65//! [pepper]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#peppering
66//!
67#![cfg_attr(all(feature = "alloc", feature = "getrandom"), doc = "```")]
68#![cfg_attr(not(all(feature = "alloc", feature = "getrandom")), doc = "```ignore")]
69//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
70//! // NOTE: example requires `getrandom` feature is enabled
71//!
72//! use argon2::{
73//!     password_hash::{PasswordHasher, PasswordVerifier, phc::PasswordHash},
74//!     Algorithm, Argon2, Params, Version
75//! };
76//!
77//! let password = b"hunter42"; // Bad password; don't actually use!
78//!
79//! // Argon2 with default params (Argon2id v19) and pepper
80//! let argon2 = Argon2::new_with_secret(
81//!     b"secret pepper",
82//!     Algorithm::default(),
83//!     Version::default(),
84//!     Params::default()
85//! )?;
86//!
87//! // Hash password to PHC string ($argon2id$v=19$...), generating a random salt
88//! let password_hash = argon2.hash_password(password)?.to_string();
89//!
90//! // Verify password against PHC string.
91//! //
92//! // NOTE: hash params from `parsed_hash` are used instead of what is configured in the
93//! // `Argon2` instance.
94//! let parsed_hash = PasswordHash::new(&password_hash)?;
95//! let argon2 = Argon2::new_with_secret(
96//!     b"secret pepper",
97//!     Algorithm::default(),
98//!     Version::default(),
99//!     Params::default(),
100//! )
101//! .unwrap();
102//! let res = argon2.verify_password(password, &parsed_hash);
103//! assert!(res.is_ok());
104//! # Ok(())
105//! # }
106//! ```
107//!
108//! ### Key Derivation
109//!
110//! This API is useful for transforming a password into cryptographic keys for
111//! e.g. password-based encryption.
112//!
113#![cfg_attr(feature = "alloc", doc = "```")]
114#![cfg_attr(not(feature = "alloc"), doc = "```ignore")]
115//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
116//! use argon2::Argon2;
117//!
118//! let password = b"hunter42"; // Bad password; don't actually use!
119//! let salt = b"example salt"; // Salt should be unique per password
120//!
121//! let mut output_key_material = [0u8; 32]; // Can be any desired size
122//! Argon2::default().hash_password_into(password, salt, &mut output_key_material)?;
123//! # Ok(())
124//! # }
125//! ```
126
127// Call sites which cast `u32` to `usize` and are annotated with
128// allow(clippy::cast_possible_truncation) need this check to avoid truncation.
129#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
130compile_error!("this crate builds on 32-bit and 64-bit platforms only");
131
132#[cfg(feature = "alloc")]
133extern crate alloc;
134
135mod algorithm;
136mod blake2b_long;
137mod block;
138mod error;
139mod memory;
140mod params;
141mod version;
142
143pub use crate::{
144    algorithm::Algorithm,
145    block::Block,
146    error::{Error, Result},
147    params::{AssociatedData, KeyId, Params, ParamsBuilder},
148    version::Version,
149};
150
151#[cfg(feature = "kdf")]
152pub use kdf::{self, Kdf, Pbkdf};
153#[cfg(feature = "password-hash")]
154pub use {
155    crate::algorithm::{ARGON2D_IDENT, ARGON2I_IDENT, ARGON2ID_IDENT},
156    password_hash::{
157        self, CustomizedPasswordHasher, PasswordHasher, PasswordVerifier, phc::PasswordHash,
158    },
159};
160
161use crate::blake2b_long::blake2b_long;
162use blake2::{Blake2b512, Digest, digest};
163use core::fmt;
164use memory::Memory;
165
166#[cfg(all(feature = "alloc", feature = "password-hash"))]
167use password_hash::phc::{Output, ParamsString, Salt};
168
169#[cfg(feature = "zeroize")]
170use zeroize::Zeroize;
171
172/// Maximum password length in bytes.
173pub const MAX_PWD_LEN: usize = 0xFFFFFFFF;
174
175/// Minimum salt length in bytes.
176pub const MIN_SALT_LEN: usize = 8;
177
178/// Maximum salt length in bytes.
179pub const MAX_SALT_LEN: usize = 0xFFFFFFFF;
180
181/// Recommended salt length for password hashing in bytes.
182pub const RECOMMENDED_SALT_LEN: usize = 16;
183
184/// Maximum secret key length in bytes.
185pub const MAX_SECRET_LEN: usize = 0xFFFFFFFF;
186
187/// Number of synchronization points between lanes per pass
188pub(crate) const SYNC_POINTS: usize = 4;
189
190/// To generate reference block positions
191const ADDRESSES_IN_BLOCK: usize = 128;
192
193#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
194cpufeatures::new!(avx2_cpuid, "avx2");
195
196/// Argon2 context.
197///
198/// This is the primary type of this crate's API, and contains the following:
199///
200/// - Argon2 [`Algorithm`] variant to be used
201/// - Argon2 [`Version`] to be used
202/// - Default set of [`Params`] to be used
203/// - (Optional) Secret key a.k.a. "pepper" to be used
204#[derive(Clone)]
205pub struct Argon2<'key> {
206    /// Algorithm to use
207    algorithm: Algorithm,
208
209    /// Version number
210    version: Version,
211
212    /// Algorithm parameters
213    params: Params,
214
215    /// Key array
216    secret: Option<&'key [u8]>,
217
218    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
219    cpu_feat_avx2: avx2_cpuid::InitToken,
220}
221
222impl Default for Argon2<'_> {
223    fn default() -> Self {
224        Self::new(Algorithm::default(), Version::default(), Params::default())
225    }
226}
227
228impl fmt::Debug for Argon2<'_> {
229    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
230        fmt.debug_struct("Argon2")
231            .field("algorithm", &self.algorithm)
232            .field("version", &self.version)
233            .field("params", &self.params)
234            .finish_non_exhaustive()
235    }
236}
237
238impl<'key> Argon2<'key> {
239    /// Create a new Argon2 context.
240    #[must_use]
241    pub fn new(algorithm: Algorithm, version: Version, params: Params) -> Self {
242        Self {
243            algorithm,
244            version,
245            params,
246            secret: None,
247            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
248            cpu_feat_avx2: avx2_cpuid::init(),
249        }
250    }
251
252    /// Create a new Argon2 context.
253    ///
254    /// # Errors
255    /// Returns [`Error::SecretTooLong`] in the event `secret` is too long.
256    pub fn new_with_secret(
257        secret: &'key [u8],
258        algorithm: Algorithm,
259        version: Version,
260        params: Params,
261    ) -> Result<Self> {
262        if MAX_SECRET_LEN < secret.len() {
263            return Err(Error::SecretTooLong);
264        }
265
266        Ok(Self {
267            algorithm,
268            version,
269            params,
270            secret: Some(secret),
271            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
272            cpu_feat_avx2: avx2_cpuid::init(),
273        })
274    }
275
276    /// Hash a password and associated parameters into the provided output buffer.
277    ///
278    /// # Errors
279    /// - Returns [`Error::PwdTooLong`] if `pwd` is longer than `MAX_PWD_LEN`.
280    /// - Returns [`Error::SaltTooShort`] if `salt` is shorter than `MIN_SALT_LEN`.
281    /// - Returns [`Error::SaltTooLong`] if `salt` is longer than `MAX_SALT_LEN`.
282    /// - Returns [`Error::OutputTooShort`] if `out` is too short.
283    /// - Returns [`Error::OutputTooLong`] if `out` is too long.
284    #[cfg(feature = "alloc")]
285    pub fn hash_password_into(&self, pwd: &[u8], salt: &[u8], out: &mut [u8]) -> Result<()> {
286        let blocks_len = self.params.block_count();
287        let mut blocks = block::Blocks::new(blocks_len).ok_or(Error::OutOfMemory)?;
288        self.hash_password_into_with_memory(pwd, salt, out, blocks.as_slice())
289    }
290
291    /// Hash a password and associated parameters into the provided output buffer.
292    ///
293    /// This method takes an explicit `memory_blocks` parameter which allows
294    /// the caller to provide the backing storage for the algorithm's state:
295    ///
296    /// - Users with the `alloc` feature enabled can use [`Argon2::hash_password_into`]
297    ///   to have it allocated for them.
298    /// - `no_std` users on "heapless" targets can use an array of the [`Block`] type
299    ///   to stack allocate this buffer.
300    ///
301    /// # Errors
302    /// - Returns [`Error::PwdTooLong`] if `pwd` is longer than `MAX_PWD_LEN`.
303    /// - Returns [`Error::SaltTooShort`] if `salt` is shorter than `MIN_SALT_LEN`.
304    /// - Returns [`Error::SaltTooLong`] if `salt` is longer than `MAX_SALT_LEN`.
305    /// - Returns [`Error::OutputTooShort`] if `out` is too short.
306    /// - Returns [`Error::OutputTooLong`] if `out` is too long.
307    pub fn hash_password_into_with_memory(
308        &self,
309        pwd: &[u8],
310        salt: &[u8],
311        out: &mut [u8],
312        mut memory_blocks: impl AsMut<[Block]>,
313    ) -> Result<()> {
314        // Validate output length
315        if out.len() < self.params.output_len().unwrap_or(Params::MIN_OUTPUT_LEN) {
316            return Err(Error::OutputTooShort);
317        }
318
319        if out.len() > self.params.output_len().unwrap_or(Params::MAX_OUTPUT_LEN) {
320            return Err(Error::OutputTooLong);
321        }
322
323        Self::verify_inputs(pwd, salt)?;
324
325        // Hashing all inputs
326        let initial_hash = self.initial_hash(pwd, salt, out);
327        self.fill_blocks(memory_blocks.as_mut(), initial_hash)?;
328        self.finalize(memory_blocks.as_mut(), out)
329    }
330
331    /// Use a password and associated parameters only to fill the given memory blocks.
332    ///
333    /// This method omits the calculation of a hash and can be used when only the
334    /// filled memory is required. It is not necessary to call this method
335    /// before calling any of the hashing functions.
336    ///
337    /// # Errors
338    /// - Returns [`Error::PwdTooLong`] if `pwd` is longer than `MAX_PWD_LEN`.
339    /// - Returns [`Error::SaltTooShort`] if `salt` is shorter than `MIN_SALT_LEN`.
340    /// - Returns [`Error::SaltTooLong`] if `salt` is longer than `MAX_SALT_LEN`.
341    pub fn fill_memory(
342        &self,
343        pwd: &[u8],
344        salt: &[u8],
345        mut memory_blocks: impl AsMut<[Block]>,
346    ) -> Result<()> {
347        Self::verify_inputs(pwd, salt)?;
348
349        let initial_hash = self.initial_hash(pwd, salt, &[]);
350        self.fill_blocks(memory_blocks.as_mut(), initial_hash)
351    }
352
353    #[allow(clippy::cast_possible_truncation, unused_mut)]
354    fn fill_blocks(
355        &self,
356        memory_blocks: &mut [Block],
357        mut initial_hash: digest::Output<Blake2b512>,
358    ) -> Result<()> {
359        let block_count = self.params.block_count();
360        let mut memory_blocks = memory_blocks
361            .get_mut(..block_count)
362            .ok_or(Error::MemoryTooLittle)?;
363
364        let segment_length = self.params.segment_length();
365        let iterations = self.params.t_cost() as usize;
366        let lane_length = self.params.lane_length();
367        let lanes = self.params.lanes();
368
369        // Initialize the first two blocks in each lane
370        for (l, lane) in memory_blocks.chunks_exact_mut(lane_length).enumerate() {
371            for (i, block) in lane[..2].iter_mut().enumerate() {
372                let i = i as u32;
373                let l = l as u32;
374
375                // Make the first and second block in each lane as G(H0||0||i) or
376                // G(H0||1||i)
377                let inputs = &[
378                    initial_hash.as_ref(),
379                    &i.to_le_bytes()[..],
380                    &l.to_le_bytes()[..],
381                ];
382
383                let mut hash = [0u8; Block::SIZE];
384                blake2b_long(inputs, &mut hash)?;
385                block.load(&hash);
386            }
387        }
388
389        #[cfg(feature = "zeroize")]
390        initial_hash.zeroize();
391
392        // Run passes on blocks
393        for pass in 0..iterations {
394            memory_blocks.for_each_segment(lanes, |mut memory_view, slice, lane| {
395                let data_independent_addressing = self.algorithm == Algorithm::Argon2i
396                    || (self.algorithm == Algorithm::Argon2id
397                        && pass == 0
398                        && slice < SYNC_POINTS / 2);
399
400                let mut address_block = Block::default();
401                let mut input_block = Block::default();
402                let zero_block = Block::default();
403
404                if data_independent_addressing {
405                    input_block.as_mut()[..6].copy_from_slice(&[
406                        pass as u64,
407                        lane as u64,
408                        slice as u64,
409                        block_count as u64,
410                        iterations as u64,
411                        self.algorithm as u64,
412                    ]);
413                }
414
415                let first_block = if pass == 0 && slice == 0 {
416                    if data_independent_addressing {
417                        // Generate first set of addresses
418                        self.update_address_block(
419                            &mut address_block,
420                            &mut input_block,
421                            &zero_block,
422                        );
423                    }
424
425                    // The first two blocks of each lane are already initialized
426                    2
427                } else {
428                    0
429                };
430
431                let mut cur_index = lane * lane_length + slice * segment_length + first_block;
432                let mut prev_index = if slice == 0 && first_block == 0 {
433                    // Last block in current lane
434                    cur_index + lane_length - 1
435                } else {
436                    // Previous block
437                    cur_index - 1
438                };
439
440                // Fill blocks in the segment
441                for block in first_block..segment_length {
442                    // Extract entropy
443                    let rand = if data_independent_addressing {
444                        let address_index = block % ADDRESSES_IN_BLOCK;
445
446                        if address_index == 0 {
447                            self.update_address_block(
448                                &mut address_block,
449                                &mut input_block,
450                                &zero_block,
451                            );
452                        }
453
454                        address_block.as_ref()[address_index]
455                    } else {
456                        memory_view.get_block(prev_index).as_ref()[0]
457                    };
458
459                    // Calculate source block index for compress function
460                    let ref_lane = if pass == 0 && slice == 0 {
461                        // Cannot reference other lanes yet
462                        lane
463                    } else {
464                        (rand >> 32) as usize % lanes
465                    };
466
467                    let reference_area_size = if pass == 0 {
468                        // First pass
469                        if slice == 0 {
470                            // First slice
471                            block - 1 // all but the previous
472                        } else if ref_lane == lane {
473                            // The same lane => add current segment
474                            slice * segment_length + block - 1
475                        } else {
476                            slice * segment_length - if block == 0 { 1 } else { 0 }
477                        }
478                    } else {
479                        // Second pass
480                        if ref_lane == lane {
481                            lane_length - segment_length + block - 1
482                        } else {
483                            lane_length - segment_length - if block == 0 { 1 } else { 0 }
484                        }
485                    };
486
487                    // 1.2.4. Mapping rand to 0..<reference_area_size-1> and produce
488                    // relative position
489                    let mut map = rand & 0xFFFFFFFF;
490                    map = (map * map) >> 32;
491                    let relative_position = reference_area_size
492                        - 1
493                        - ((reference_area_size as u64 * map) >> 32) as usize;
494
495                    // 1.2.5 Computing starting position
496                    let start_position = if pass != 0 && slice != SYNC_POINTS - 1 {
497                        (slice + 1) * segment_length
498                    } else {
499                        0
500                    };
501
502                    let lane_index = (start_position + relative_position) % lane_length;
503                    let ref_index = ref_lane * lane_length + lane_index;
504
505                    // Calculate new block
506                    let result = self.compress(
507                        memory_view.get_block(prev_index),
508                        memory_view.get_block(ref_index),
509                    );
510
511                    if self.version == Version::V0x10 || pass == 0 {
512                        *memory_view.get_block_mut(cur_index) = result;
513                    } else {
514                        *memory_view.get_block_mut(cur_index) ^= &result;
515                    };
516
517                    prev_index = cur_index;
518                    cur_index += 1;
519                }
520            });
521        }
522
523        Ok(())
524    }
525
526    fn compress(&self, rhs: &Block, lhs: &Block) -> Block {
527        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
528        {
529            /// Enable AVX2 optimizations.
530            #[target_feature(enable = "avx2")]
531            unsafe fn compress_avx2(rhs: &Block, lhs: &Block) -> Block {
532                Block::compress(rhs, lhs)
533            }
534
535            if self.cpu_feat_avx2.get() {
536                // SAFETY: checked that AVX2 was detected.
537                return unsafe { compress_avx2(rhs, lhs) };
538            }
539        }
540
541        Block::compress(rhs, lhs)
542    }
543
544    /// Get default configured [`Params`].
545    #[must_use]
546    pub const fn params(&self) -> &Params {
547        &self.params
548    }
549
550    fn finalize(&self, memory_blocks: &[Block], out: &mut [u8]) -> Result<()> {
551        let lane_length = self.params.lane_length();
552
553        let mut blockhash = memory_blocks[lane_length - 1];
554
555        // XOR the last blocks
556        for l in 1..self.params.lanes() {
557            let last_block_in_lane = l * lane_length + (lane_length - 1);
558            blockhash ^= &memory_blocks[last_block_in_lane];
559        }
560
561        // Hash the result
562        let mut blockhash_bytes = [0u8; Block::SIZE];
563
564        for (chunk, v) in blockhash_bytes.chunks_mut(8).zip(blockhash.iter()) {
565            chunk.copy_from_slice(&v.to_le_bytes());
566        }
567
568        blake2b_long(&[&blockhash_bytes], out)?;
569
570        #[cfg(feature = "zeroize")]
571        {
572            blockhash.zeroize();
573            blockhash_bytes.zeroize();
574        }
575
576        Ok(())
577    }
578
579    fn update_address_block(
580        &self,
581        address_block: &mut Block,
582        input_block: &mut Block,
583        zero_block: &Block,
584    ) {
585        input_block.as_mut()[6] += 1;
586        *address_block = self.compress(zero_block, input_block);
587        *address_block = self.compress(zero_block, address_block);
588    }
589
590    /// Hashes all the inputs into `blockhash[PREHASH_DIGEST_LEN]`.
591    #[allow(clippy::cast_possible_truncation)]
592    fn initial_hash(&self, pwd: &[u8], salt: &[u8], out: &[u8]) -> digest::Output<Blake2b512> {
593        let mut digest = Blake2b512::new();
594        digest.update(self.params.p_cost().to_le_bytes());
595        digest.update((out.len() as u32).to_le_bytes());
596        digest.update(self.params.m_cost().to_le_bytes());
597        digest.update(self.params.t_cost().to_le_bytes());
598        digest.update(self.version.to_le_bytes());
599        digest.update(self.algorithm.to_le_bytes());
600        digest.update((pwd.len() as u32).to_le_bytes());
601        digest.update(pwd);
602        digest.update((salt.len() as u32).to_le_bytes());
603        digest.update(salt);
604
605        if let Some(secret) = &self.secret {
606            digest.update((secret.len() as u32).to_le_bytes());
607            digest.update(secret);
608        } else {
609            digest.update(0u32.to_le_bytes());
610        }
611
612        digest.update((self.params.data().len() as u32).to_le_bytes());
613        digest.update(self.params.data());
614        digest.finalize()
615    }
616
617    const fn verify_inputs(pwd: &[u8], salt: &[u8]) -> Result<()> {
618        if pwd.len() > MAX_PWD_LEN {
619            return Err(Error::PwdTooLong);
620        }
621
622        // Validate salt (required param)
623        if salt.len() < MIN_SALT_LEN {
624            return Err(Error::SaltTooShort);
625        }
626
627        if salt.len() > MAX_SALT_LEN {
628            return Err(Error::SaltTooLong);
629        }
630
631        Ok(())
632    }
633}
634
635#[cfg(feature = "kdf")]
636impl Kdf for Argon2<'_> {
637    fn derive_key(&self, password: &[u8], salt: &[u8], out: &mut [u8]) -> kdf::Result<()> {
638        self.hash_password_into(password, salt, out)?;
639        Ok(())
640    }
641}
642
643#[cfg(feature = "kdf")]
644impl Pbkdf for Argon2<'_> {}
645
646#[cfg(all(feature = "alloc", feature = "password-hash"))]
647impl CustomizedPasswordHasher<PasswordHash> for Argon2<'_> {
648    type Params = Params;
649
650    fn hash_password_customized(
651        &self,
652        password: &[u8],
653        salt: &[u8],
654        alg_id: Option<&str>,
655        version: Option<u32>,
656        params: Params,
657    ) -> password_hash::Result<PasswordHash> {
658        let algorithm = alg_id
659            .map(Algorithm::try_from)
660            .transpose()?
661            .unwrap_or_default();
662
663        let version = version
664            .map(Version::try_from)
665            .transpose()?
666            .unwrap_or_default();
667
668        Self {
669            secret: self.secret,
670            algorithm,
671            version,
672            params,
673            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
674            cpu_feat_avx2: self.cpu_feat_avx2,
675        }
676        .hash_password_with_salt(password, salt)
677    }
678}
679
680#[cfg(all(feature = "alloc", feature = "password-hash"))]
681impl PasswordHasher<PasswordHash> for Argon2<'_> {
682    fn hash_password_with_salt(
683        &self,
684        password: &[u8],
685        salt: &[u8],
686    ) -> password_hash::Result<PasswordHash> {
687        let salt = Salt::new(salt)?;
688
689        let output_len = self
690            .params
691            .output_len()
692            .unwrap_or(Params::DEFAULT_OUTPUT_LEN);
693
694        let mut buffer = [0u8; Output::MAX_LENGTH];
695        let out = buffer
696            .get_mut(..output_len)
697            .ok_or(password_hash::Error::OutputSize)?;
698
699        self.hash_password_into(password, &salt, out)?;
700        let output = Output::new(out)?;
701
702        Ok(PasswordHash {
703            algorithm: self.algorithm.ident(),
704            version: Some(self.version.into()),
705            params: ParamsString::try_from(&self.params)?,
706            salt: Some(salt),
707            hash: Some(output),
708        })
709    }
710}
711
712#[cfg(all(feature = "alloc", feature = "password-hash"))]
713impl PasswordVerifier<str> for Argon2<'_> {
714    fn verify_password(&self, password: &[u8], hash: &str) -> password_hash::Result<()> {
715        self.verify_password(password, &PasswordHash::new(hash)?)
716    }
717}
718
719impl From<Params> for Argon2<'_> {
720    fn from(params: Params) -> Self {
721        Self::new(Algorithm::default(), Version::default(), params)
722    }
723}
724
725impl From<&Params> for Argon2<'_> {
726    fn from(params: &Params) -> Self {
727        Self::from(params.clone())
728    }
729}
730
731#[cfg(all(test, feature = "alloc", feature = "password-hash"))]
732#[allow(clippy::unwrap_used)]
733mod tests {
734    use crate::{
735        Algorithm, Argon2, CustomizedPasswordHasher, Params, PasswordHasher, PasswordVerifier,
736        Version,
737    };
738
739    /// Example password only: don't use this as a real password!!!
740    const EXAMPLE_PASSWORD: &[u8] = b"hunter42";
741
742    /// Example salt value. Don't use a static salt value!!!
743    const EXAMPLE_SALT: &[u8] = b"example-salt";
744
745    #[test]
746    fn decoded_salt_too_short() {
747        let argon2 = Argon2::default();
748
749        // Too short: minimum size 8-bytes
750        let salt = b"weesalt";
751
752        let res =
753            argon2.hash_password_customized(EXAMPLE_PASSWORD, salt, None, None, Params::default());
754
755        assert_eq!(res, Err(password_hash::Error::SaltInvalid));
756    }
757
758    #[test]
759    fn password_hash_retains_configured_params() {
760        // Non-default but valid parameters
761        let t_cost = 4;
762        let m_cost = 2048;
763        let p_cost = 2;
764        let version = Version::V0x10;
765
766        let params = Params::new(m_cost, t_cost, p_cost, None).unwrap();
767        let hasher = Argon2::new(Algorithm::default(), version, params);
768        let hash = hasher
769            .hash_password_with_salt(EXAMPLE_PASSWORD, EXAMPLE_SALT)
770            .unwrap();
771
772        assert_eq!(hash.version.unwrap(), version.into());
773
774        for &(param, value) in &[("t", t_cost), ("m", m_cost), ("p", p_cost)] {
775            assert_eq!(
776                hash.params
777                    .get(param)
778                    .and_then(|p| p.decimal().ok())
779                    .unwrap(),
780                value,
781            );
782        }
783    }
784
785    #[test]
786    fn non_default_output_len_round_trip_should_verify() {
787        let params = Params::new(8, 1, 1, Some(16)).unwrap();
788        let hash = Argon2::new(Algorithm::Argon2id, Version::V0x13, params)
789            .hash_password_with_salt(EXAMPLE_PASSWORD, EXAMPLE_SALT)
790            .unwrap();
791
792        assert_eq!(
793            Argon2::default().verify_password(EXAMPLE_PASSWORD, &hash),
794            Ok(())
795        );
796    }
797}