Coverage Report

Created: 2026-03-11 07:34

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