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#![cfg_attr(feature = "std", doc = "```")]
34#![cfg_attr(not(feature = "std"), doc = "```ignore")]
35#![cfg_attr(feature = "std", doc = "```")]
69#![cfg_attr(not(feature = "std"), doc = "```ignore")]
70#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
85compile_error!("this crate builds on 32-bit and 64-bit platforms only");
86
87#[cfg(feature = "alloc")]
88#[macro_use]
89extern crate alloc;
90
91#[cfg(feature = "std")]
92extern crate std;
93
94mod algorithm;
95mod blake2b_long;
96mod block;
97mod error;
98mod params;
99mod version;
100
101pub 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")))]
111pub use {
112 crate::algorithm::{ARGON2D_IDENT, ARGON2ID_IDENT, ARGON2I_IDENT},
113 password_hash::{self, PasswordHash, PasswordHasher, PasswordVerifier},
114};
115
116use crate::blake2b_long::blake2b_long;
117use blake2::{digest, Blake2b512, Digest};
118use core::fmt;
119
120#[cfg(all(feature = "alloc", feature = "password-hash"))]
121use password_hash::{Decimal, Ident, ParamsString, Salt};
122
123#[cfg(feature = "zeroize")]
124use zeroize::Zeroize;
125
126pub const MAX_PWD_LEN: usize = 0xFFFFFFFF;
128
129pub const MIN_SALT_LEN: usize = 8;
131
132pub const MAX_SALT_LEN: usize = 0xFFFFFFFF;
134
135pub const RECOMMENDED_SALT_LEN: usize = 16;
137
138pub const MAX_SECRET_LEN: usize = 0xFFFFFFFF;
140
141pub(crate) const SYNC_POINTS: usize = 4;
143
144const ADDRESSES_IN_BLOCK: usize = 128;
146
147#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
148cpufeatures::new!(avx2_cpuid, "avx2");
149
150#[derive(Clone)]
159pub struct Argon2<'key> {
160 algorithm: Algorithm,
162
163 version: Version,
165
166 params: Params,
168
169 secret: Option<&'key [u8]>,
171
172 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
173 cpu_feat_avx2: avx2_cpuid::InitToken,
174}
175
176impl Default for Argon2<'_> {
177 fn default() -> Self {
178 Self::new(Algorithm::default(), Version::default(), Params::default())
179 }
180}
181
182impl fmt::Debug for Argon2<'_> {
183 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
184 fmt.debug_struct("Argon2")
185 .field("algorithm", &self.algorithm)
186 .field("version", &self.version)
187 .field("params", &self.params)
188 .finish_non_exhaustive()
189 }
190}
191
192impl<'key> Argon2<'key> {
193 pub fn new(algorithm: Algorithm, version: Version, params: Params) -> Self {
195 Self {
196 algorithm,
197 version,
198 params,
199 secret: None,
200 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
201 cpu_feat_avx2: avx2_cpuid::init(),
202 }
203 }
204
205 pub fn new_with_secret(
207 secret: &'key [u8],
208 algorithm: Algorithm,
209 version: Version,
210 params: Params,
211 ) -> Result<Self> {
212 if MAX_SECRET_LEN < secret.len() {
213 return Err(Error::SecretTooLong);
214 }
215
216 Ok(Self {
217 algorithm,
218 version,
219 params,
220 secret: Some(secret),
221 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
222 cpu_feat_avx2: avx2_cpuid::init(),
223 })
224 }
225
226 #[cfg(feature = "alloc")]
228 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
229 pub fn hash_password_into(&self, pwd: &[u8], salt: &[u8], out: &mut [u8]) -> Result<()> {
230 let mut blocks = vec![Block::default(); self.params.block_count()];
231 self.hash_password_into_with_memory(pwd, salt, out, &mut blocks)
232 }
233
234 pub fn hash_password_into_with_memory(
244 &self,
245 pwd: &[u8],
246 salt: &[u8],
247 out: &mut [u8],
248 mut memory_blocks: impl AsMut<[Block]>,
249 ) -> Result<()> {
250 if out.len() < self.params.output_len().unwrap_or(Params::MIN_OUTPUT_LEN) {
252 return Err(Error::OutputTooShort);
253 }
254
255 if out.len() > self.params.output_len().unwrap_or(Params::MAX_OUTPUT_LEN) {
256 return Err(Error::OutputTooLong);
257 }
258
259 Self::verify_inputs(pwd, salt)?;
260
261 let initial_hash = self.initial_hash(pwd, salt, out);
263
264 self.fill_blocks(memory_blocks.as_mut(), initial_hash)?;
265 self.finalize(memory_blocks.as_mut(), out)
266 }
267
268 pub fn fill_memory(
274 &self,
275 pwd: &[u8],
276 salt: &[u8],
277 mut memory_blocks: impl AsMut<[Block]>,
278 ) -> Result<()> {
279 Self::verify_inputs(pwd, salt)?;
280
281 let initial_hash = self.initial_hash(pwd, salt, &[]);
282
283 self.fill_blocks(memory_blocks.as_mut(), initial_hash)
284 }
285
286 #[allow(clippy::cast_possible_truncation, unused_mut)]
287 fn fill_blocks(
288 &self,
289 memory_blocks: &mut [Block],
290 mut initial_hash: digest::Output<Blake2b512>,
291 ) -> Result<()> {
292 let block_count = self.params.block_count();
293 let memory_blocks = memory_blocks
294 .get_mut(..block_count)
295 .ok_or(Error::MemoryTooLittle)?;
296
297 let segment_length = self.params.segment_length();
298 let iterations = self.params.t_cost() as usize;
299 let lane_length = self.params.lane_length();
300 let lanes = self.params.lanes();
301
302 for (l, lane) in memory_blocks.chunks_exact_mut(lane_length).enumerate() {
304 for (i, block) in lane[..2].iter_mut().enumerate() {
305 let i = i as u32;
306 let l = l as u32;
307
308 let inputs = &[
311 initial_hash.as_ref(),
312 &i.to_le_bytes()[..],
313 &l.to_le_bytes()[..],
314 ];
315
316 let mut hash = [0u8; Block::SIZE];
317 blake2b_long(inputs, &mut hash)?;
318 block.load(&hash);
319 }
320 }
321
322 #[cfg(feature = "zeroize")]
323 initial_hash.zeroize();
324
325 for pass in 0..iterations {
327 for slice in 0..SYNC_POINTS {
328 let data_independent_addressing = self.algorithm == Algorithm::Argon2i
329 || (self.algorithm == Algorithm::Argon2id
330 && pass == 0
331 && slice < SYNC_POINTS / 2);
332
333 for lane in 0..lanes {
334 let mut address_block = Block::default();
335 let mut input_block = Block::default();
336 let zero_block = Block::default();
337
338 if data_independent_addressing {
339 input_block.as_mut()[..6].copy_from_slice(&[
340 pass as u64,
341 lane as u64,
342 slice as u64,
343 memory_blocks.len() as u64,
344 iterations as u64,
345 self.algorithm as u64,
346 ]);
347 }
348
349 let first_block = if pass == 0 && slice == 0 {
350 if data_independent_addressing {
351 self.update_address_block(
353 &mut address_block,
354 &mut input_block,
355 &zero_block,
356 );
357 }
358
359 2
361 } else {
362 0
363 };
364
365 let mut cur_index = lane * lane_length + slice * segment_length + first_block;
366 let mut prev_index = if slice == 0 && first_block == 0 {
367 cur_index + lane_length - 1
369 } else {
370 cur_index - 1
372 };
373
374 for block in first_block..segment_length {
376 let rand = if data_independent_addressing {
378 let addres_index = block % ADDRESSES_IN_BLOCK;
379
380 if addres_index == 0 {
381 self.update_address_block(
382 &mut address_block,
383 &mut input_block,
384 &zero_block,
385 );
386 }
387
388 address_block.as_ref()[addres_index]
389 } else {
390 memory_blocks[prev_index].as_ref()[0]
391 };
392
393 let ref_lane = if pass == 0 && slice == 0 {
395 lane
397 } else {
398 (rand >> 32) as usize % lanes
399 };
400
401 let reference_area_size = if pass == 0 {
402 if slice == 0 {
404 block - 1 } else if ref_lane == lane {
407 slice * segment_length + block - 1
409 } else {
410 slice * segment_length - if block == 0 { 1 } else { 0 }
411 }
412 } else {
413 if ref_lane == lane {
415 lane_length - segment_length + block - 1
416 } else {
417 lane_length - segment_length - if block == 0 { 1 } else { 0 }
418 }
419 };
420
421 let mut map = rand & 0xFFFFFFFF;
424 map = (map * map) >> 32;
425 let relative_position = reference_area_size
426 - 1
427 - ((reference_area_size as u64 * map) >> 32) as usize;
428
429 let start_position = if pass != 0 && slice != SYNC_POINTS - 1 {
431 (slice + 1) * segment_length
432 } else {
433 0
434 };
435
436 let lane_index = (start_position + relative_position) % lane_length;
437 let ref_index = ref_lane * lane_length + lane_index;
438
439 let result =
441 self.compress(&memory_blocks[prev_index], &memory_blocks[ref_index]);
442
443 if self.version == Version::V0x10 || pass == 0 {
444 memory_blocks[cur_index] = result;
445 } else {
446 memory_blocks[cur_index] ^= &result;
447 };
448
449 prev_index = cur_index;
450 cur_index += 1;
451 }
452 }
453 }
454 }
455
456 Ok(())
457 }
458
459 fn compress(&self, rhs: &Block, lhs: &Block) -> Block {
460 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
461 {
462 #[target_feature(enable = "avx2")]
464 unsafe fn compress_avx2(rhs: &Block, lhs: &Block) -> Block {
465 Block::compress(rhs, lhs)
466 }
467
468 if self.cpu_feat_avx2.get() {
469 return unsafe { compress_avx2(rhs, lhs) };
470 }
471 }
472
473 Block::compress(rhs, lhs)
474 }
475
476 pub const fn params(&self) -> &Params {
478 &self.params
479 }
480
481 fn finalize(&self, memory_blocks: &[Block], out: &mut [u8]) -> Result<()> {
482 let lane_length = self.params.lane_length();
483
484 let mut blockhash = memory_blocks[lane_length - 1];
485
486 for l in 1..self.params.lanes() {
488 let last_block_in_lane = l * lane_length + (lane_length - 1);
489 blockhash ^= &memory_blocks[last_block_in_lane];
490 }
491
492 let mut blockhash_bytes = [0u8; Block::SIZE];
494
495 for (chunk, v) in blockhash_bytes.chunks_mut(8).zip(blockhash.iter()) {
496 chunk.copy_from_slice(&v.to_le_bytes())
497 }
498
499 blake2b_long(&[&blockhash_bytes], out)?;
500
501 #[cfg(feature = "zeroize")]
502 {
503 blockhash.zeroize();
504 blockhash_bytes.zeroize();
505 }
506
507 Ok(())
508 }
509
510 fn update_address_block(
511 &self,
512 address_block: &mut Block,
513 input_block: &mut Block,
514 zero_block: &Block,
515 ) {
516 input_block.as_mut()[6] += 1;
517 *address_block = self.compress(zero_block, input_block);
518 *address_block = self.compress(zero_block, address_block);
519 }
520
521 #[allow(clippy::cast_possible_truncation)]
523 fn initial_hash(&self, pwd: &[u8], salt: &[u8], out: &[u8]) -> digest::Output<Blake2b512> {
524 let mut digest = Blake2b512::new();
525 digest.update(self.params.p_cost().to_le_bytes());
526 digest.update((out.len() as u32).to_le_bytes());
527 digest.update(self.params.m_cost().to_le_bytes());
528 digest.update(self.params.t_cost().to_le_bytes());
529 digest.update(self.version.to_le_bytes());
530 digest.update(self.algorithm.to_le_bytes());
531 digest.update((pwd.len() as u32).to_le_bytes());
532 digest.update(pwd);
533 digest.update((salt.len() as u32).to_le_bytes());
534 digest.update(salt);
535
536 if let Some(secret) = &self.secret {
537 digest.update((secret.len() as u32).to_le_bytes());
538 digest.update(secret);
539 } else {
540 digest.update(0u32.to_le_bytes());
541 }
542
543 digest.update((self.params.data().len() as u32).to_le_bytes());
544 digest.update(self.params.data());
545 digest.finalize()
546 }
547
548 const fn verify_inputs(pwd: &[u8], salt: &[u8]) -> Result<()> {
549 if pwd.len() > MAX_PWD_LEN {
550 return Err(Error::PwdTooLong);
551 }
552
553 if salt.len() < MIN_SALT_LEN {
555 return Err(Error::SaltTooShort);
556 }
557
558 if salt.len() > MAX_SALT_LEN {
559 return Err(Error::SaltTooLong);
560 }
561
562 Ok(())
563 }
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")))]
569impl PasswordHasher for Argon2<'_> {
570 type Params = Params;
571
572 fn hash_password<'a>(
573 &self,
574 password: &[u8],
575 salt: impl Into<Salt<'a>>,
576 ) -> password_hash::Result<PasswordHash<'a>> {
577 let salt = salt.into();
578 let mut salt_arr = [0u8; 64];
579 let salt_bytes = salt.decode_b64(&mut salt_arr)?;
580
581 let output_len = self
582 .params
583 .output_len()
584 .unwrap_or(Params::DEFAULT_OUTPUT_LEN);
585
586 let output = password_hash::Output::init_with(output_len, |out| {
587 Ok(self.hash_password_into(password, salt_bytes, out)?)
588 })?;
589
590 Ok(PasswordHash {
591 algorithm: self.algorithm.ident(),
592 version: Some(self.version.into()),
593 params: ParamsString::try_from(&self.params)?,
594 salt: Some(salt),
595 hash: Some(output),
596 })
597 }
598
599 fn hash_password_customized<'a>(
600 &self,
601 password: &[u8],
602 alg_id: Option<Ident<'a>>,
603 version: Option<Decimal>,
604 params: Params,
605 salt: impl Into<Salt<'a>>,
606 ) -> password_hash::Result<PasswordHash<'a>> {
607 let algorithm = alg_id
608 .map(Algorithm::try_from)
609 .transpose()?
610 .unwrap_or_default();
611
612 let version = version
613 .map(Version::try_from)
614 .transpose()?
615 .unwrap_or_default();
616
617 let salt = salt.into();
618
619 Self {
620 secret: self.secret,
621 algorithm,
622 version,
623 params,
624 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
625 cpu_feat_avx2: self.cpu_feat_avx2,
626 }
627 .hash_password(password, salt)
628 }
629}
630
631impl<'key> From<Params> for Argon2<'key> {
632 fn from(params: Params) -> Self {
633 Self::new(Algorithm::default(), Version::default(), params)
634 }
635}
636
637impl<'key> From<&Params> for Argon2<'key> {
638 fn from(params: &Params) -> Self {
639 Self::from(params.clone())
640 }
641}
642
643#[cfg(all(test, feature = "alloc", feature = "password-hash"))]
644#[allow(clippy::unwrap_used)]
645mod tests {
646 use crate::{Algorithm, Argon2, Params, PasswordHasher, Salt, Version};
647
648 const EXAMPLE_PASSWORD: &[u8] = b"hunter42";
650
651 const EXAMPLE_SALT: &str = "examplesaltvalue";
653
654 #[test]
655 fn decoded_salt_too_short() {
656 let argon2 = Argon2::default();
657
658 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 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}