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#![cfg_attr(all(feature = "alloc", feature = "getrandom"), doc = "```")]
36#![cfg_attr(not(all(feature = "alloc", feature = "getrandom")), doc = "```ignore")]
37#![cfg_attr(all(feature = "alloc", feature = "getrandom"), doc = "```")]
68#![cfg_attr(not(all(feature = "alloc", feature = "getrandom")), doc = "```ignore")]
69#![cfg_attr(feature = "alloc", doc = "```")]
114#![cfg_attr(not(feature = "alloc"), doc = "```ignore")]
115#[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
172pub const MAX_PWD_LEN: usize = 0xFFFFFFFF;
174
175pub const MIN_SALT_LEN: usize = 8;
177
178pub const MAX_SALT_LEN: usize = 0xFFFFFFFF;
180
181pub const RECOMMENDED_SALT_LEN: usize = 16;
183
184pub const MAX_SECRET_LEN: usize = 0xFFFFFFFF;
186
187pub(crate) const SYNC_POINTS: usize = 4;
189
190const ADDRESSES_IN_BLOCK: usize = 128;
192
193#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
194cpufeatures::new!(avx2_cpuid, "avx2");
195
196#[derive(Clone)]
205pub struct Argon2<'key> {
206 algorithm: Algorithm,
208
209 version: Version,
211
212 params: Params,
214
215 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 #[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 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 #[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 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 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 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 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 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 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 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 self.update_address_block(
419 &mut address_block,
420 &mut input_block,
421 &zero_block,
422 );
423 }
424
425 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 cur_index + lane_length - 1
435 } else {
436 cur_index - 1
438 };
439
440 for block in first_block..segment_length {
442 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 let ref_lane = if pass == 0 && slice == 0 {
461 lane
463 } else {
464 (rand >> 32) as usize % lanes
465 };
466
467 let reference_area_size = if pass == 0 {
468 if slice == 0 {
470 block - 1 } else if ref_lane == lane {
473 slice * segment_length + block - 1
475 } else {
476 slice * segment_length - if block == 0 { 1 } else { 0 }
477 }
478 } else {
479 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 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 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 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 #[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 return unsafe { compress_avx2(rhs, lhs) };
538 }
539 }
540
541 Block::compress(rhs, lhs)
542 }
543
544 #[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 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 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 #[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 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 const EXAMPLE_PASSWORD: &[u8] = b"hunter42";
741
742 const EXAMPLE_SALT: &[u8] = b"example-salt";
744
745 #[test]
746 fn decoded_salt_too_short() {
747 let argon2 = Argon2::default();
748
749 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 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}