/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 | | } |