1use core::fmt;
37
38use super::hmac::hmac_prefix_state;
39use crate::{
40 hashes::crypto::{
41 Sha256, Sha512,
42 sha256::{H0 as SHA256_H0, dispatch as sha256_dispatch, kernels::CompressBlocksFn as Sha256CompressBlocksFn},
43 sha512::{H0 as SHA512_H0, dispatch as sha512_dispatch, kernels::CompressBlocksFn as Sha512CompressBlocksFn},
44 },
45 traits::{VerificationError, ct},
46};
47
48const SHA256_OUTPUT_SIZE: usize = 32;
49const SHA256_BLOCK_SIZE: usize = 64;
50const SHA256_INLINE_SALT_MAX: usize = SHA256_BLOCK_SIZE - 4 - 1 - 8;
54
55const SHA512_OUTPUT_SIZE: usize = 64;
56const SHA512_BLOCK_SIZE: usize = 128;
57const SHA512_INLINE_SALT_MAX: usize = SHA512_BLOCK_SIZE - 4 - 1 - 16;
60
61#[inline(always)]
62#[allow(clippy::indexing_slicing)]
63fn write_u32x8_be(dst: &mut [u8], words: &[u32; 8]) {
64 dst[0..4].copy_from_slice(&words[0].to_be_bytes());
65 dst[4..8].copy_from_slice(&words[1].to_be_bytes());
66 dst[8..12].copy_from_slice(&words[2].to_be_bytes());
67 dst[12..16].copy_from_slice(&words[3].to_be_bytes());
68 dst[16..20].copy_from_slice(&words[4].to_be_bytes());
69 dst[20..24].copy_from_slice(&words[5].to_be_bytes());
70 dst[24..28].copy_from_slice(&words[6].to_be_bytes());
71 dst[28..32].copy_from_slice(&words[7].to_be_bytes());
72}
73
74#[inline(always)]
75#[allow(clippy::indexing_slicing)]
76fn write_u64x8_be(dst: &mut [u8], words: &[u64; 8]) {
77 dst[0..8].copy_from_slice(&words[0].to_be_bytes());
78 dst[8..16].copy_from_slice(&words[1].to_be_bytes());
79 dst[16..24].copy_from_slice(&words[2].to_be_bytes());
80 dst[24..32].copy_from_slice(&words[3].to_be_bytes());
81 dst[32..40].copy_from_slice(&words[4].to_be_bytes());
82 dst[40..48].copy_from_slice(&words[5].to_be_bytes());
83 dst[48..56].copy_from_slice(&words[6].to_be_bytes());
84 dst[56..64].copy_from_slice(&words[7].to_be_bytes());
85}
86
87#[inline(always)]
88fn zeroize_u32x8_no_fence(words: &mut [u32; 8]) {
89 ct::zeroize_words_no_fence(words);
90}
91
92#[inline(always)]
93fn zeroize_u64x8_no_fence(words: &mut [u64; 8]) {
94 ct::zeroize_words_no_fence(words);
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
113#[non_exhaustive]
114pub enum Pbkdf2Error {
115 InvalidIterations,
119 OutputTooLong,
121}
122
123impl fmt::Display for Pbkdf2Error {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 match self {
126 Self::InvalidIterations => f.write_str("PBKDF2 iteration count must be at least 1"),
127 Self::OutputTooLong => f.write_str("PBKDF2 output length exceeds algorithm maximum"),
128 }
129 }
130}
131
132impl core::error::Error for Pbkdf2Error {}
133
134macro_rules! define_pbkdf2_sha2 {
135 (
136 $(#[$struct_meta:meta])*
137 $name:ident {
138 output_size_const: $output_size_const:ident,
139 block_size_const: $block_size_const:ident,
140 compress_ty: $compress_ty:ty,
141 digest_ty: $digest_ty:ty,
142 h0: $h0:path,
143 dispatch: $dispatch:ident,
144 f_fn: $f_fn:path,
145 iter1_fn: $iter1_fn:path,
146 test_oneshot: $test_oneshot:path,
147 word_ty: $word_ty:ty,
148 recommended_iterations: $recommended_iterations:expr,
149 }
150 ) => {
151 $(#[$struct_meta])*
152 #[derive(Clone)]
153 pub struct $name {
154 inner_init: [$word_ty; 8],
155 outer_init: [$word_ty; 8],
156 compress: $compress_ty,
157 }
158
159 impl fmt::Debug for $name {
160 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161 f.debug_struct(stringify!($name)).finish_non_exhaustive()
162 }
163 }
164
165 impl $name {
166 pub const OUTPUT_SIZE: usize = $output_size_const;
168 pub const MIN_RECOMMENDED_ITERATIONS: u32 = $recommended_iterations;
170 pub const MIN_SALT_LEN: usize = 16;
172
173 #[must_use]
175 #[allow(clippy::indexing_slicing)] pub fn new(password: &[u8]) -> Self {
177 let compress = $dispatch::compress_dispatch().select(0);
178
179 let mut key_block = [0u8; $block_size_const];
180 if password.len() > $block_size_const {
181 let digest = <$digest_ty>::digest(password);
182 key_block[..$output_size_const].copy_from_slice(&digest);
183 } else {
184 key_block[..password.len()].copy_from_slice(password);
185 }
186
187 let (inner_init, outer_init) = hmac_prefix_state(&mut key_block, |ipad, opad| {
188 let mut inner_init = $h0;
189 compress(&mut inner_init, ipad);
190
191 let mut outer_init = $h0;
192 compress(&mut outer_init, opad);
193
194 (inner_init, outer_init)
195 });
196
197 Self {
198 inner_init,
199 outer_init,
200 compress,
201 }
202 }
203
204 #[inline]
206 #[allow(clippy::indexing_slicing)]
207 pub fn derive(&self, salt: &[u8], iterations: u32, okm: &mut [u8]) -> Result<(), Pbkdf2Error> {
208 Self::derive_with_prefixes(self.compress, &self.inner_init, &self.outer_init, salt, iterations, okm)
209 }
210
211 #[inline]
212 #[allow(clippy::indexing_slicing)]
213 fn derive_with_prefixes(
214 compress: $compress_ty,
215 inner_init: &[$word_ty; 8],
216 outer_init: &[$word_ty; 8],
217 salt: &[u8],
218 iterations: u32,
219 okm: &mut [u8],
220 ) -> Result<(), Pbkdf2Error> {
221 if iterations == 0 {
222 return Err(Pbkdf2Error::InvalidIterations);
223 }
224 if okm.is_empty() {
225 return Ok(());
226 }
227 let num_blocks = okm.len().div_ceil($output_size_const);
228 if num_blocks as u64 > u32::MAX as u64 {
229 return Err(Pbkdf2Error::OutputTooLong);
230 }
231
232 if iterations == 1 {
233 $iter1_fn(compress, inner_init, outer_init, salt, okm);
234 return Ok(());
235 }
236
237 let mut block_index = 1u32;
238 let mut chunks = okm.chunks_exact_mut($output_size_const);
239
240 for chunk in chunks.by_ref() {
241 let full_chunk = unsafe { &mut *(chunk.as_mut_ptr().cast::<[u8; $output_size_const]>()) };
244 $f_fn(
245 compress,
246 inner_init,
247 outer_init,
248 salt,
249 iterations,
250 block_index,
251 full_chunk,
252 );
253 block_index = block_index.strict_add(1);
254 }
255
256 let tail = chunks.into_remainder();
257 if !tail.is_empty() {
258 let mut block_out = [0u8; $output_size_const];
259 $f_fn(
260 compress,
261 inner_init,
262 outer_init,
263 salt,
264 iterations,
265 block_index,
266 &mut block_out,
267 );
268 tail.copy_from_slice(&block_out[..tail.len()]);
269 ct::zeroize(&mut block_out);
270 }
271 Ok(())
272 }
273
274 pub fn derive_array<const N: usize>(&self, salt: &[u8], iterations: u32) -> Result<[u8; N], Pbkdf2Error> {
276 let mut out = [0u8; N];
277 self.derive(salt, iterations, &mut out)?;
278 Ok(out)
279 }
280
281 #[allow(clippy::indexing_slicing)]
283 #[must_use = "password verification must be checked; a dropped Result silently accepts the wrong password"]
284 pub fn verify(&self, salt: &[u8], iterations: u32, expected: &[u8]) -> Result<(), VerificationError> {
285 if iterations == 0 || expected.is_empty() {
286 return Err(VerificationError::new());
287 }
288 let num_blocks = expected.len().div_ceil($output_size_const);
289 if num_blocks as u64 > u32::MAX as u64 {
290 return Err(VerificationError::new());
291 }
292
293 let compress = self.compress;
294
295 let mut block_out = [0u8; $output_size_const];
296 let mut acc = 0u8;
297
298 for (i, chunk) in expected.chunks($output_size_const).enumerate() {
299 let block_index = (i as u32).strict_add(1);
300 $f_fn(
301 compress,
302 &self.inner_init,
303 &self.outer_init,
304 salt,
305 iterations,
306 block_index,
307 &mut block_out,
308 );
309 for (&a, &b) in block_out[..chunk.len()].iter().zip(chunk.iter()) {
310 acc |= a ^ b;
311 }
312 }
313
314 ct::zeroize(&mut block_out);
315
316 if core::hint::black_box(acc) == 0 {
317 Ok(())
318 } else {
319 Err(VerificationError::new())
320 }
321 }
322
323 #[inline]
325 #[allow(clippy::indexing_slicing)]
326 pub fn derive_key(password: &[u8], salt: &[u8], iterations: u32, okm: &mut [u8]) -> Result<(), Pbkdf2Error> {
327 let compress = $dispatch::compress_dispatch().select(0);
328
329 let mut key_block = [0u8; $block_size_const];
330 if password.len() > $block_size_const {
331 let digest = <$digest_ty>::digest(password);
332 key_block[..$output_size_const].copy_from_slice(&digest);
333 } else {
334 key_block[..password.len()].copy_from_slice(password);
335 }
336
337 let mut ipad = [0x36u8; $block_size_const];
338 let mut opad = [0x5Cu8; $block_size_const];
339 for ((ipad_byte, opad_byte), key_byte) in ipad.iter_mut().zip(opad.iter_mut()).zip(key_block.iter().copied()) {
340 *ipad_byte ^= key_byte;
341 *opad_byte ^= key_byte;
342 }
343
344 let mut inner_init = $h0;
345 compress(&mut inner_init, &ipad);
346
347 let mut outer_init = $h0;
348 compress(&mut outer_init, &opad);
349
350 let result = Self::derive_with_prefixes(compress, &inner_init, &outer_init, salt, iterations, okm);
351
352 ct::zeroize_no_fence(&mut key_block);
353 ct::zeroize_no_fence(&mut ipad);
354 ct::zeroize_no_fence(&mut opad);
355 for word in inner_init.iter_mut().chain(outer_init.iter_mut()) {
356 unsafe { core::ptr::write_volatile(word, 0) };
358 }
359 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
360
361 result
362 }
363
364 #[inline]
366 pub fn derive_key_array<const N: usize>(
367 password: &[u8],
368 salt: &[u8],
369 iterations: u32,
370 ) -> Result<[u8; N], Pbkdf2Error> {
371 Self::new(password).derive_array(salt, iterations)
372 }
373
374 #[inline]
376 #[must_use = "password verification must be checked; a dropped Result silently accepts the wrong password"]
377 pub fn verify_password(
378 password: &[u8],
379 salt: &[u8],
380 iterations: u32,
381 expected: &[u8],
382 ) -> Result<(), VerificationError> {
383 Self::new(password).verify(salt, iterations, expected)
384 }
385
386 #[cfg(test)]
388 #[allow(clippy::indexing_slicing)]
389 pub(crate) fn new_with_compress_for_test(password: &[u8], compress: $compress_ty) -> Self {
390 let mut key_block = [0u8; $block_size_const];
391 if password.len() > $block_size_const {
392 key_block[..$output_size_const].copy_from_slice(&$test_oneshot(password, compress));
393 } else {
394 key_block[..password.len()].copy_from_slice(password);
395 }
396
397 let (inner_init, outer_init) = hmac_prefix_state(&mut key_block, |ipad, opad| {
398 let mut inner_init = $h0;
399 compress(&mut inner_init, ipad);
400
401 let mut outer_init = $h0;
402 compress(&mut outer_init, opad);
403
404 (inner_init, outer_init)
405 });
406
407 Self {
408 inner_init,
409 outer_init,
410 compress,
411 }
412 }
413 }
414
415 impl Drop for $name {
416 fn drop(&mut self) {
417 for word in self.inner_init.iter_mut().chain(self.outer_init.iter_mut()) {
418 unsafe { core::ptr::write_volatile(word, 0) };
420 }
421 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
422 }
423 }
424 };
425}
426
427define_pbkdf2_sha2! {
430 Pbkdf2Sha256 {
454 output_size_const: SHA256_OUTPUT_SIZE,
455 block_size_const: SHA256_BLOCK_SIZE,
456 compress_ty: Sha256CompressBlocksFn,
457 digest_ty: Sha256,
458 h0: SHA256_H0,
459 dispatch: sha256_dispatch,
460 f_fn: pbkdf2_sha256_f,
461 iter1_fn: pbkdf2_sha256_iter1,
462 test_oneshot: sha256_oneshot_with_compress,
463 word_ty: u32,
464 recommended_iterations: 600_000,
465 }
466}
467
468#[cfg(test)]
470#[allow(clippy::indexing_slicing)]
471fn sha256_oneshot_with_compress(data: &[u8], compress: Sha256CompressBlocksFn) -> [u8; SHA256_OUTPUT_SIZE] {
472 let mut state = SHA256_H0;
473 let mut pos = 0usize;
474 while pos.strict_add(SHA256_BLOCK_SIZE) <= data.len() {
475 compress(&mut state, &data[pos..pos.strict_add(SHA256_BLOCK_SIZE)]);
476 pos = pos.strict_add(SHA256_BLOCK_SIZE);
477 }
478 let mut block = [0u8; SHA256_BLOCK_SIZE];
479 let tail = data.len().strict_sub(pos);
480 block[..tail].copy_from_slice(&data[pos..]);
481 block[tail] = 0x80;
482 if tail >= 56 {
483 compress(&mut state, &block);
484 block = [0u8; SHA256_BLOCK_SIZE];
485 }
486 block[56..64].copy_from_slice(&(data.len() as u64).strict_mul(8).to_be_bytes());
487 compress(&mut state, &block);
488 let mut out = [0u8; SHA256_OUTPUT_SIZE];
489 for (chunk, &word) in out.chunks_exact_mut(4).zip(state.iter()) {
490 chunk.copy_from_slice(&word.to_be_bytes());
491 }
492 out
493}
494
495#[allow(clippy::indexing_slicing)]
501#[inline(always)]
502fn pbkdf2_sha256_f(
503 compress: Sha256CompressBlocksFn,
504 inner_init: &[u32; 8],
505 outer_init: &[u32; 8],
506 salt: &[u8],
507 iterations: u32,
508 block_index: u32,
509 output: &mut [u8; SHA256_OUTPUT_SIZE],
510) {
511 let mut state: [u32; 8];
512 let mut u_words: [u32; 8];
513 let mut result_words: [u32; 8];
514
515 state = *inner_init;
517 let msg_len = salt.len().strict_add(4);
518 let total_inner = (SHA256_BLOCK_SIZE as u64).strict_add(msg_len as u64);
519
520 let mut block = [0u8; SHA256_BLOCK_SIZE];
521 if salt.len() <= SHA256_INLINE_SALT_MAX {
522 block[..salt.len()].copy_from_slice(salt);
523 let pos = salt.len().strict_add(4);
524 block[salt.len()..pos].copy_from_slice(&block_index.to_be_bytes());
525 block[pos] = 0x80;
526 block[56..SHA256_BLOCK_SIZE].copy_from_slice(&total_inner.strict_mul(8).to_be_bytes());
527 compress(&mut state, &block);
528 } else {
529 let mut pos = 0usize;
530
531 let mut salt_off = 0usize;
533 while salt_off < salt.len() {
534 let space = SHA256_BLOCK_SIZE.strict_sub(pos);
535 let remaining = salt.len().strict_sub(salt_off);
536 let take = if space < remaining { space } else { remaining };
537 block[pos..pos.strict_add(take)].copy_from_slice(&salt[salt_off..salt_off.strict_add(take)]);
538 pos = pos.strict_add(take);
539 salt_off = salt_off.strict_add(take);
540 if pos == SHA256_BLOCK_SIZE {
541 compress(&mut state, &block);
542 block = [0u8; SHA256_BLOCK_SIZE];
543 pos = 0;
544 }
545 }
546
547 for &b in &block_index.to_be_bytes() {
549 block[pos] = b;
550 pos = pos.strict_add(1);
551 if pos == SHA256_BLOCK_SIZE {
552 compress(&mut state, &block);
553 block = [0u8; SHA256_BLOCK_SIZE];
554 pos = 0;
555 }
556 }
557
558 block[pos] = 0x80;
560 if pos.strict_add(1) > 56 {
561 compress(&mut state, &block);
562 block = [0u8; SHA256_BLOCK_SIZE];
563 }
564 block[56..SHA256_BLOCK_SIZE].copy_from_slice(&total_inner.strict_mul(8).to_be_bytes());
565 compress(&mut state, &block);
566 }
567
568 let mut outer_block = [0u8; SHA256_BLOCK_SIZE];
570 write_u32x8_be(&mut outer_block[..SHA256_OUTPUT_SIZE], &state);
571 outer_block[SHA256_OUTPUT_SIZE] = 0x80;
572 outer_block[56..SHA256_BLOCK_SIZE].copy_from_slice(&768u64.to_be_bytes());
574
575 state = *outer_init;
576 compress(&mut state, &outer_block);
577 u_words = state;
578 result_words = u_words;
579
580 if iterations == 1 {
581 write_u32x8_be(output, &result_words);
582 ct::zeroize_no_fence(&mut outer_block);
583 ct::zeroize_no_fence(&mut block);
584 zeroize_u32x8_no_fence(&mut state);
585 zeroize_u32x8_no_fence(&mut u_words);
586 zeroize_u32x8_no_fence(&mut result_words);
587 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
588 return;
589 }
590
591 let mut inner_block = [0u8; SHA256_BLOCK_SIZE];
594 inner_block[SHA256_OUTPUT_SIZE] = 0x80;
595 inner_block[56..SHA256_BLOCK_SIZE].copy_from_slice(&768u64.to_be_bytes());
596
597 for _ in 1..iterations {
598 write_u32x8_be(&mut inner_block[..SHA256_OUTPUT_SIZE], &u_words);
600 state = *inner_init;
601 compress(&mut state, &inner_block);
602
603 write_u32x8_be(&mut outer_block[..SHA256_OUTPUT_SIZE], &state);
605
606 state = *outer_init;
608 compress(&mut state, &outer_block);
609
610 u_words = state;
611
612 for (dst, &word) in result_words.iter_mut().zip(u_words.iter()) {
614 *dst ^= word;
615 }
616 }
617
618 write_u32x8_be(output, &result_words);
619
620 ct::zeroize_no_fence(&mut inner_block);
622 ct::zeroize_no_fence(&mut outer_block);
623 ct::zeroize_no_fence(&mut block);
624 zeroize_u32x8_no_fence(&mut state);
625 zeroize_u32x8_no_fence(&mut u_words);
626 zeroize_u32x8_no_fence(&mut result_words);
627 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
628}
629
630#[allow(clippy::indexing_slicing)]
631#[inline(always)]
632fn pbkdf2_sha256_iter1(
633 compress: Sha256CompressBlocksFn,
634 inner_init: &[u32; 8],
635 outer_init: &[u32; 8],
636 salt: &[u8],
637 okm: &mut [u8],
638) {
639 if salt.len() > SHA256_INLINE_SALT_MAX {
640 let mut block_index = 1u32;
641 let mut chunks = okm.chunks_exact_mut(SHA256_OUTPUT_SIZE);
642 for chunk in chunks.by_ref() {
643 let full_chunk = unsafe { &mut *(chunk.as_mut_ptr().cast::<[u8; SHA256_OUTPUT_SIZE]>()) };
645 pbkdf2_sha256_f(compress, inner_init, outer_init, salt, 1, block_index, full_chunk);
646 block_index = block_index.strict_add(1);
647 }
648 let tail = chunks.into_remainder();
649 if !tail.is_empty() {
650 let mut block_out = [0u8; SHA256_OUTPUT_SIZE];
651 pbkdf2_sha256_f(compress, inner_init, outer_init, salt, 1, block_index, &mut block_out);
652 tail.copy_from_slice(&block_out[..tail.len()]);
653 ct::zeroize(&mut block_out);
654 }
655 return;
656 }
657
658 let msg_len = salt.len().strict_add(4);
659 let total_inner = (SHA256_BLOCK_SIZE as u64).strict_add(msg_len as u64);
660 let index_pos = salt.len();
661 let pad_pos = index_pos.strict_add(4);
662
663 let mut block = [0u8; SHA256_BLOCK_SIZE];
664 block[..salt.len()].copy_from_slice(salt);
665 block[pad_pos] = 0x80;
666 block[56..SHA256_BLOCK_SIZE].copy_from_slice(&total_inner.strict_mul(8).to_be_bytes());
667
668 let mut outer_block = [0u8; SHA256_BLOCK_SIZE];
669 outer_block[SHA256_OUTPUT_SIZE] = 0x80;
670 outer_block[56..SHA256_BLOCK_SIZE].copy_from_slice(&768u64.to_be_bytes());
671
672 let mut state = [0u32; 8];
673 let mut block_index = 1u32;
674
675 let mut chunks = okm.chunks_exact_mut(SHA256_OUTPUT_SIZE);
676 for chunk in chunks.by_ref() {
677 block[index_pos..pad_pos].copy_from_slice(&block_index.to_be_bytes());
678
679 state = *inner_init;
680 compress(&mut state, &block);
681
682 write_u32x8_be(&mut outer_block[..SHA256_OUTPUT_SIZE], &state);
683 state = *outer_init;
684 compress(&mut state, &outer_block);
685
686 write_u32x8_be(chunk, &state);
687 block_index = block_index.strict_add(1);
688 }
689
690 let tail = chunks.into_remainder();
691 if !tail.is_empty() {
692 block[index_pos..pad_pos].copy_from_slice(&block_index.to_be_bytes());
693
694 state = *inner_init;
695 compress(&mut state, &block);
696
697 write_u32x8_be(&mut outer_block[..SHA256_OUTPUT_SIZE], &state);
698 state = *outer_init;
699 compress(&mut state, &outer_block);
700
701 let mut block_out = [0u8; SHA256_OUTPUT_SIZE];
702 write_u32x8_be(&mut block_out, &state);
703 tail.copy_from_slice(&block_out[..tail.len()]);
704 ct::zeroize_no_fence(&mut block_out);
705 }
706
707 ct::zeroize_no_fence(&mut block);
708 ct::zeroize_no_fence(&mut outer_block);
709 zeroize_u32x8_no_fence(&mut state);
710 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
711}
712
713define_pbkdf2_sha2! {
716 Pbkdf2Sha512 {
732 output_size_const: SHA512_OUTPUT_SIZE,
733 block_size_const: SHA512_BLOCK_SIZE,
734 compress_ty: Sha512CompressBlocksFn,
735 digest_ty: Sha512,
736 h0: SHA512_H0,
737 dispatch: sha512_dispatch,
738 f_fn: pbkdf2_sha512_f,
739 iter1_fn: pbkdf2_sha512_iter1,
740 test_oneshot: sha512_oneshot_with_compress,
741 word_ty: u64,
742 recommended_iterations: 210_000,
743 }
744}
745
746#[cfg(test)]
748#[allow(clippy::indexing_slicing)]
749fn sha512_oneshot_with_compress(data: &[u8], compress: Sha512CompressBlocksFn) -> [u8; SHA512_OUTPUT_SIZE] {
750 let mut state = SHA512_H0;
751 let mut pos = 0usize;
752 while pos.strict_add(SHA512_BLOCK_SIZE) <= data.len() {
753 compress(&mut state, &data[pos..pos.strict_add(SHA512_BLOCK_SIZE)]);
754 pos = pos.strict_add(SHA512_BLOCK_SIZE);
755 }
756 let mut block = [0u8; SHA512_BLOCK_SIZE];
757 let tail = data.len().strict_sub(pos);
758 block[..tail].copy_from_slice(&data[pos..]);
759 block[tail] = 0x80;
760 if tail >= 112 {
761 compress(&mut state, &block);
762 block = [0u8; SHA512_BLOCK_SIZE];
763 }
764 block[112..128].copy_from_slice(&(data.len() as u128).strict_mul(8).to_be_bytes());
765 compress(&mut state, &block);
766 let mut out = [0u8; SHA512_OUTPUT_SIZE];
767 for (chunk, &word) in out.chunks_exact_mut(8).zip(state.iter()) {
768 chunk.copy_from_slice(&word.to_be_bytes());
769 }
770 out
771}
772
773#[allow(clippy::indexing_slicing)]
775#[inline(always)]
776fn pbkdf2_sha512_f(
777 compress: Sha512CompressBlocksFn,
778 inner_init: &[u64; 8],
779 outer_init: &[u64; 8],
780 salt: &[u8],
781 iterations: u32,
782 block_index: u32,
783 output: &mut [u8; SHA512_OUTPUT_SIZE],
784) {
785 let mut state: [u64; 8];
786 let mut u_words: [u64; 8];
787 let mut result_words: [u64; 8];
788
789 state = *inner_init;
791 let msg_len = salt.len().strict_add(4);
792 let total_inner = (SHA512_BLOCK_SIZE as u128).strict_add(msg_len as u128);
793
794 let mut block = [0u8; SHA512_BLOCK_SIZE];
795 if salt.len() <= SHA512_INLINE_SALT_MAX {
796 block[..salt.len()].copy_from_slice(salt);
797 let pos = salt.len().strict_add(4);
798 block[salt.len()..pos].copy_from_slice(&block_index.to_be_bytes());
799 block[pos] = 0x80;
800 block[112..SHA512_BLOCK_SIZE].copy_from_slice(&total_inner.strict_mul(8).to_be_bytes());
801 compress(&mut state, &block);
802 } else {
803 let mut pos = 0usize;
804
805 let mut salt_off = 0usize;
807 while salt_off < salt.len() {
808 let space = SHA512_BLOCK_SIZE.strict_sub(pos);
809 let remaining = salt.len().strict_sub(salt_off);
810 let take = if space < remaining { space } else { remaining };
811 block[pos..pos.strict_add(take)].copy_from_slice(&salt[salt_off..salt_off.strict_add(take)]);
812 pos = pos.strict_add(take);
813 salt_off = salt_off.strict_add(take);
814 if pos == SHA512_BLOCK_SIZE {
815 compress(&mut state, &block);
816 block = [0u8; SHA512_BLOCK_SIZE];
817 pos = 0;
818 }
819 }
820
821 for &b in &block_index.to_be_bytes() {
823 block[pos] = b;
824 pos = pos.strict_add(1);
825 if pos == SHA512_BLOCK_SIZE {
826 compress(&mut state, &block);
827 block = [0u8; SHA512_BLOCK_SIZE];
828 pos = 0;
829 }
830 }
831
832 block[pos] = 0x80;
834 if pos.strict_add(1) > 112 {
835 compress(&mut state, &block);
836 block = [0u8; SHA512_BLOCK_SIZE];
837 }
838 block[112..SHA512_BLOCK_SIZE].copy_from_slice(&total_inner.strict_mul(8).to_be_bytes());
839 compress(&mut state, &block);
840 }
841
842 let mut outer_block = [0u8; SHA512_BLOCK_SIZE];
844 write_u64x8_be(&mut outer_block[..SHA512_OUTPUT_SIZE], &state);
845 outer_block[SHA512_OUTPUT_SIZE] = 0x80;
846 outer_block[112..SHA512_BLOCK_SIZE].copy_from_slice(&1536u128.to_be_bytes());
848
849 state = *outer_init;
850 compress(&mut state, &outer_block);
851 u_words = state;
852 result_words = u_words;
853
854 let mut inner_block = [0u8; SHA512_BLOCK_SIZE];
856 inner_block[SHA512_OUTPUT_SIZE] = 0x80;
857 inner_block[112..SHA512_BLOCK_SIZE].copy_from_slice(&1536u128.to_be_bytes());
858
859 for _ in 1..iterations {
860 write_u64x8_be(&mut inner_block[..SHA512_OUTPUT_SIZE], &u_words);
861 state = *inner_init;
862 compress(&mut state, &inner_block);
863
864 write_u64x8_be(&mut outer_block[..SHA512_OUTPUT_SIZE], &state);
865
866 state = *outer_init;
867 compress(&mut state, &outer_block);
868
869 u_words = state;
870
871 for (dst, &word) in result_words.iter_mut().zip(u_words.iter()) {
872 *dst ^= word;
873 }
874 }
875
876 write_u64x8_be(output, &result_words);
877
878 ct::zeroize_no_fence(&mut inner_block);
879 ct::zeroize_no_fence(&mut outer_block);
880 ct::zeroize_no_fence(&mut block);
881 zeroize_u64x8_no_fence(&mut state);
882 zeroize_u64x8_no_fence(&mut u_words);
883 zeroize_u64x8_no_fence(&mut result_words);
884 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
885}
886
887#[allow(clippy::indexing_slicing)]
888#[inline(always)]
889fn pbkdf2_sha512_iter1(
890 compress: Sha512CompressBlocksFn,
891 inner_init: &[u64; 8],
892 outer_init: &[u64; 8],
893 salt: &[u8],
894 okm: &mut [u8],
895) {
896 if salt.len() > SHA512_INLINE_SALT_MAX {
897 let mut block_index = 1u32;
898 let mut chunks = okm.chunks_exact_mut(SHA512_OUTPUT_SIZE);
899 for chunk in chunks.by_ref() {
900 let full_chunk = unsafe { &mut *(chunk.as_mut_ptr().cast::<[u8; SHA512_OUTPUT_SIZE]>()) };
902 pbkdf2_sha512_f(compress, inner_init, outer_init, salt, 1, block_index, full_chunk);
903 block_index = block_index.strict_add(1);
904 }
905 let tail = chunks.into_remainder();
906 if !tail.is_empty() {
907 let mut block_out = [0u8; SHA512_OUTPUT_SIZE];
908 pbkdf2_sha512_f(compress, inner_init, outer_init, salt, 1, block_index, &mut block_out);
909 tail.copy_from_slice(&block_out[..tail.len()]);
910 ct::zeroize(&mut block_out);
911 }
912 return;
913 }
914
915 let msg_len = salt.len().strict_add(4);
916 let total_inner = (SHA512_BLOCK_SIZE as u128).strict_add(msg_len as u128);
917 let index_pos = salt.len();
918 let pad_pos = index_pos.strict_add(4);
919
920 let mut block = [0u8; SHA512_BLOCK_SIZE];
921 block[..salt.len()].copy_from_slice(salt);
922 block[pad_pos] = 0x80;
923 block[112..SHA512_BLOCK_SIZE].copy_from_slice(&total_inner.strict_mul(8).to_be_bytes());
924
925 let mut outer_block = [0u8; SHA512_BLOCK_SIZE];
926 outer_block[SHA512_OUTPUT_SIZE] = 0x80;
927 outer_block[112..SHA512_BLOCK_SIZE].copy_from_slice(&1536u128.to_be_bytes());
928
929 let mut state = [0u64; 8];
930 let mut block_index = 1u32;
931
932 let mut chunks = okm.chunks_exact_mut(SHA512_OUTPUT_SIZE);
933 for chunk in chunks.by_ref() {
934 block[index_pos..pad_pos].copy_from_slice(&block_index.to_be_bytes());
935
936 state = *inner_init;
937 compress(&mut state, &block);
938
939 write_u64x8_be(&mut outer_block[..SHA512_OUTPUT_SIZE], &state);
940 state = *outer_init;
941 compress(&mut state, &outer_block);
942
943 write_u64x8_be(chunk, &state);
944 block_index = block_index.strict_add(1);
945 }
946
947 let tail = chunks.into_remainder();
948 if !tail.is_empty() {
949 block[index_pos..pad_pos].copy_from_slice(&block_index.to_be_bytes());
950
951 state = *inner_init;
952 compress(&mut state, &block);
953
954 write_u64x8_be(&mut outer_block[..SHA512_OUTPUT_SIZE], &state);
955 state = *outer_init;
956 compress(&mut state, &outer_block);
957
958 let mut block_out = [0u8; SHA512_OUTPUT_SIZE];
959 write_u64x8_be(&mut block_out, &state);
960 tail.copy_from_slice(&block_out[..tail.len()]);
961 ct::zeroize_no_fence(&mut block_out);
962 }
963
964 ct::zeroize_no_fence(&mut block);
965 ct::zeroize_no_fence(&mut outer_block);
966 zeroize_u64x8_no_fence(&mut state);
967 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
968}
969
970#[cfg(test)]
971mod tests {
972 use alloc::vec;
973 use core::sync::atomic::{AtomicUsize, Ordering};
974
975 use super::*;
976
977 #[test]
980 fn rfc7914_sha256_vector_1() {
981 let mut dk = [0u8; 64];
982 Pbkdf2Sha256::derive_key(b"passwd", b"salt", 1, &mut dk).unwrap();
983 assert_eq!(
984 dk,
985 [
986 0x55, 0xac, 0x04, 0x6e, 0x56, 0xe3, 0x08, 0x9f, 0xec, 0x16, 0x91, 0xc2, 0x25, 0x44, 0xb6, 0x05, 0xf9, 0x41,
987 0x85, 0x21, 0x6d, 0xde, 0x04, 0x65, 0xe6, 0x8b, 0x9d, 0x57, 0xc2, 0x0d, 0xac, 0xbc, 0x49, 0xca, 0x9c, 0xcc,
988 0xf1, 0x79, 0xb6, 0x45, 0x99, 0x16, 0x64, 0xb3, 0x9d, 0x77, 0xef, 0x31, 0x7c, 0x71, 0xb8, 0x45, 0xb1, 0xe3,
989 0x0b, 0xd5, 0x09, 0x11, 0x20, 0x41, 0xd3, 0xa1, 0x97, 0x83,
990 ]
991 );
992 }
993
994 #[cfg(not(miri))]
995 #[test]
996 fn rfc7914_sha256_vector_2() {
997 let mut dk = [0u8; 64];
998 Pbkdf2Sha256::derive_key(b"Password", b"NaCl", 80000, &mut dk).unwrap();
999 assert_eq!(
1000 dk,
1001 [
1002 0x4d, 0xdc, 0xd8, 0xf6, 0x0b, 0x98, 0xbe, 0x21, 0x83, 0x0c, 0xee, 0x5e, 0xf2, 0x27, 0x01, 0xf9, 0x64, 0x1a,
1003 0x44, 0x18, 0xd0, 0x4c, 0x04, 0x14, 0xae, 0xff, 0x08, 0x87, 0x6b, 0x34, 0xab, 0x56, 0xa1, 0xd4, 0x25, 0xa1,
1004 0x22, 0x58, 0x33, 0x54, 0x9a, 0xdb, 0x84, 0x1b, 0x51, 0xc9, 0xb3, 0x17, 0x6a, 0x27, 0x2b, 0xde, 0xbb, 0xa1,
1005 0xd0, 0x78, 0x47, 0x8f, 0x62, 0xb3, 0x97, 0xf3, 0x3c, 0x8d,
1006 ]
1007 );
1008 }
1009
1010 fn oracle_sha256(password: &[u8], salt: &[u8], iterations: u32, out: &mut [u8]) {
1013 pbkdf2::pbkdf2_hmac::<sha2::Sha256>(password, salt, iterations, out);
1014 }
1015
1016 fn oracle_sha512(password: &[u8], salt: &[u8], iterations: u32, out: &mut [u8]) {
1017 pbkdf2::pbkdf2_hmac::<sha2::Sha512>(password, salt, iterations, out);
1018 }
1019
1020 #[test]
1021 fn sha256_matches_oracle() {
1022 #[cfg(not(miri))]
1023 let cases: &[(&[u8], &[u8], u32, usize)] = &[
1024 (b"password", b"salt", 1, 32),
1025 (b"password", b"salt", 2, 32),
1026 (b"password", b"salt", 4096, 32),
1027 (b"password", b"salt", 1, 64),
1028 (b"password", b"salt", 100, 64),
1029 (b"", b"salt", 1, 32),
1030 (b"password", b"", 1, 32),
1031 (b"", b"", 1, 32),
1032 (b"p", b"s", 1, 1),
1033 (b"password", b"salt", 1, 20),
1034 (b"password", b"salt", 1, 48),
1035 (b"password", b"salt", 1, 96),
1036 (b"password", b"salt", 1, 128),
1037 (&[0xAA; 100], b"salt", 1, 32),
1039 (b"password", &[0xBB; 200], 1, 32),
1041 (&[0xCC; 128], b"salt", 1, 32),
1043 ];
1044 #[cfg(miri)]
1045 let cases: &[(&[u8], &[u8], u32, usize)] = &[
1046 (b"password", b"salt", 1, 32),
1047 (b"password", b"salt", 2, 32),
1048 (b"password", b"salt", 16, 64),
1049 (b"", b"", 1, 32),
1050 (b"p", b"s", 1, 1),
1051 (b"password", b"salt", 1, 96),
1052 (&[0xAA; 100], b"salt", 1, 32),
1053 (b"password", &[0xBB; 200], 1, 32),
1054 (&[0xCC; 128], b"salt", 1, 32),
1055 ];
1056
1057 for &(password, salt, iterations, dk_len) in cases {
1058 let mut expected = vec![0u8; dk_len];
1059 oracle_sha256(password, salt, iterations, &mut expected);
1060
1061 let mut actual = vec![0u8; dk_len];
1062 Pbkdf2Sha256::derive_key(password, salt, iterations, &mut actual).unwrap();
1063
1064 assert_eq!(
1065 actual,
1066 expected,
1067 "SHA-256 mismatch: pw_len={} salt_len={} c={} dk_len={}",
1068 password.len(),
1069 salt.len(),
1070 iterations,
1071 dk_len,
1072 );
1073 }
1074 }
1075
1076 #[test]
1077 fn sha512_matches_oracle() {
1078 #[cfg(not(miri))]
1079 let cases: &[(&[u8], &[u8], u32, usize)] = &[
1080 (b"password", b"salt", 1, 64),
1081 (b"password", b"salt", 2, 64),
1082 (b"password", b"salt", 4096, 64),
1083 (b"password", b"salt", 1, 128),
1084 (b"password", b"salt", 100, 128),
1085 (b"", b"salt", 1, 64),
1086 (b"password", b"", 1, 64),
1087 (b"", b"", 1, 64),
1088 (b"p", b"s", 1, 1),
1089 (b"password", b"salt", 1, 20),
1090 (b"password", b"salt", 1, 48),
1091 (b"password", b"salt", 1, 96),
1092 (b"password", b"salt", 1, 192),
1093 (b"password", &[0xBB; 200], 1, 64),
1095 (&[0xCC; 200], b"salt", 1, 64),
1097 ];
1098 #[cfg(miri)]
1099 let cases: &[(&[u8], &[u8], u32, usize)] = &[
1100 (b"password", b"salt", 1, 64),
1101 (b"password", b"salt", 2, 64),
1102 (b"password", b"salt", 16, 128),
1103 (b"", b"", 1, 64),
1104 (b"p", b"s", 1, 1),
1105 (b"password", b"salt", 1, 192),
1106 (b"password", &[0xBB; 200], 1, 64),
1107 (&[0xCC; 200], b"salt", 1, 64),
1108 ];
1109
1110 for &(password, salt, iterations, dk_len) in cases {
1111 let mut expected = vec![0u8; dk_len];
1112 oracle_sha512(password, salt, iterations, &mut expected);
1113
1114 let mut actual = vec![0u8; dk_len];
1115 Pbkdf2Sha512::derive_key(password, salt, iterations, &mut actual).unwrap();
1116
1117 assert_eq!(
1118 actual,
1119 expected,
1120 "SHA-512 mismatch: pw_len={} salt_len={} c={} dk_len={}",
1121 password.len(),
1122 salt.len(),
1123 iterations,
1124 dk_len,
1125 );
1126 }
1127 }
1128
1129 #[test]
1132 fn sha256_verify_correct_password() {
1133 let dk = Pbkdf2Sha256::derive_key_array::<32>(b"password", b"salt", 100).unwrap();
1134 assert!(Pbkdf2Sha256::verify_password(b"password", b"salt", 100, &dk).is_ok());
1135 }
1136
1137 #[test]
1138 fn sha256_verify_wrong_password() {
1139 let dk = Pbkdf2Sha256::derive_key_array::<32>(b"password", b"salt", 100).unwrap();
1140 assert!(Pbkdf2Sha256::verify_password(b"wrong", b"salt", 100, &dk).is_err());
1141 }
1142
1143 #[test]
1144 fn sha256_verify_wrong_salt() {
1145 let dk = Pbkdf2Sha256::derive_key_array::<32>(b"password", b"salt", 100).unwrap();
1146 assert!(Pbkdf2Sha256::verify_password(b"password", b"wrong", 100, &dk).is_err());
1147 }
1148
1149 #[test]
1150 fn sha256_verify_wrong_iterations() {
1151 let dk = Pbkdf2Sha256::derive_key_array::<32>(b"password", b"salt", 100).unwrap();
1152 assert!(Pbkdf2Sha256::verify_password(b"password", b"salt", 101, &dk).is_err());
1153 }
1154
1155 #[test]
1156 fn sha512_verify_correct_password() {
1157 let dk = Pbkdf2Sha512::derive_key_array::<64>(b"password", b"salt", 100).unwrap();
1158 assert!(Pbkdf2Sha512::verify_password(b"password", b"salt", 100, &dk).is_ok());
1159 }
1160
1161 #[test]
1162 fn sha512_verify_wrong_password() {
1163 let dk = Pbkdf2Sha512::derive_key_array::<64>(b"password", b"salt", 100).unwrap();
1164 assert!(Pbkdf2Sha512::verify_password(b"wrong", b"salt", 100, &dk).is_err());
1165 }
1166
1167 #[test]
1170 fn sha256_zero_iterations_error() {
1171 let mut dk = [0u8; 32];
1172 assert_eq!(
1173 Pbkdf2Sha256::derive_key(b"pw", b"salt", 0, &mut dk),
1174 Err(Pbkdf2Error::InvalidIterations)
1175 );
1176 }
1177
1178 #[test]
1179 fn sha512_zero_iterations_error() {
1180 let mut dk = [0u8; 64];
1181 assert_eq!(
1182 Pbkdf2Sha512::derive_key(b"pw", b"salt", 0, &mut dk),
1183 Err(Pbkdf2Error::InvalidIterations)
1184 );
1185 }
1186
1187 #[test]
1188 fn sha256_empty_output_ok() {
1189 assert!(Pbkdf2Sha256::derive_key(b"pw", b"salt", 1, &mut []).is_ok());
1190 }
1191
1192 #[test]
1193 fn sha512_empty_output_ok() {
1194 assert!(Pbkdf2Sha512::derive_key(b"pw", b"salt", 1, &mut []).is_ok());
1195 }
1196
1197 #[test]
1198 fn sha256_verify_zero_iterations() {
1199 assert!(Pbkdf2Sha256::verify_password(b"pw", b"salt", 0, &[0u8; 32]).is_err());
1200 }
1201
1202 #[test]
1203 fn sha256_verify_empty_expected() {
1204 assert!(Pbkdf2Sha256::verify_password(b"pw", b"salt", 1, &[]).is_err());
1205 }
1206
1207 #[test]
1208 fn sha256_verify_password_covers_output_lengths_and_mismatch_positions() {
1209 let password = [0xA5; 97];
1210 let salt = [0x5A; 200];
1211 #[cfg(not(miri))]
1212 let output_lengths = 1..=96;
1213 #[cfg(miri)]
1214 let output_lengths = [1usize, 2, 31, 32, 33, 63, 64, 65, 95, 96].into_iter();
1215
1216 for out_len in output_lengths {
1217 let mut expected = vec![0u8; out_len];
1218 Pbkdf2Sha256::derive_key(&password, &salt, 2, &mut expected).unwrap();
1219 assert!(Pbkdf2Sha256::verify_password(&password, &salt, 2, &expected).is_ok());
1220
1221 let mut wrong_first = expected.clone();
1222 wrong_first[0] ^= 1;
1223 assert!(Pbkdf2Sha256::verify_password(&password, &salt, 2, &wrong_first).is_err());
1224
1225 let mut wrong_last = expected.clone();
1226 let last = wrong_last.len().strict_sub(1);
1227 wrong_last[last] ^= 1;
1228 assert!(Pbkdf2Sha256::verify_password(&password, &salt, 2, &wrong_last).is_err());
1229 }
1230 }
1231
1232 #[test]
1233 fn sha512_verify_password_covers_output_lengths_and_mismatch_positions() {
1234 let password = [0x3C; 193];
1235 let salt = [0xC3; 260];
1236 #[cfg(not(miri))]
1237 let output_lengths = 1..=192;
1238 #[cfg(miri)]
1239 let output_lengths = [1usize, 2, 63, 64, 65, 127, 128, 129, 191, 192].into_iter();
1240
1241 for out_len in output_lengths {
1242 let mut expected = vec![0u8; out_len];
1243 Pbkdf2Sha512::derive_key(&password, &salt, 2, &mut expected).unwrap();
1244 assert!(Pbkdf2Sha512::verify_password(&password, &salt, 2, &expected).is_ok());
1245
1246 let mut wrong_first = expected.clone();
1247 wrong_first[0] ^= 1;
1248 assert!(Pbkdf2Sha512::verify_password(&password, &salt, 2, &wrong_first).is_err());
1249
1250 let mut wrong_last = expected.clone();
1251 let last = wrong_last.len().strict_sub(1);
1252 wrong_last[last] ^= 1;
1253 assert!(Pbkdf2Sha512::verify_password(&password, &salt, 2, &wrong_last).is_err());
1254 }
1255 }
1256
1257 #[test]
1260 fn sha256_state_reuse_matches_oneshot() {
1261 let state = Pbkdf2Sha256::new(b"password");
1262 let dk1 = state.derive_array::<32>(b"salt1", 100).unwrap();
1263 let dk2 = state.derive_array::<32>(b"salt2", 100).unwrap();
1264
1265 let oneshot1 = Pbkdf2Sha256::derive_key_array::<32>(b"password", b"salt1", 100).unwrap();
1266 let oneshot2 = Pbkdf2Sha256::derive_key_array::<32>(b"password", b"salt2", 100).unwrap();
1267
1268 assert_eq!(dk1, oneshot1);
1269 assert_eq!(dk2, oneshot2);
1270 assert_ne!(dk1, dk2);
1271 }
1272
1273 #[test]
1276 fn sha256_single_iteration() {
1277 let mut expected = [0u8; 32];
1278 oracle_sha256(b"pw", b"salt", 1, &mut expected);
1279 let actual = Pbkdf2Sha256::derive_key_array::<32>(b"pw", b"salt", 1).unwrap();
1280 assert_eq!(actual, expected);
1281 }
1282
1283 #[test]
1284 fn sha512_single_iteration() {
1285 let mut expected = [0u8; 64];
1286 oracle_sha512(b"pw", b"salt", 1, &mut expected);
1287 let actual = Pbkdf2Sha512::derive_key_array::<64>(b"pw", b"salt", 1).unwrap();
1288 assert_eq!(actual, expected);
1289 }
1290
1291 #[test]
1294 fn error_is_copy() {
1295 let e = Pbkdf2Error::InvalidIterations;
1296 let e2 = e;
1297 assert_eq!(e, e2);
1298 }
1299
1300 #[test]
1301 fn error_display() {
1302 fn assert_display<T: core::fmt::Display>() {}
1303 assert_display::<Pbkdf2Error>();
1304 }
1305
1306 #[test]
1307 fn error_debug() {
1308 fn assert_debug<T: core::fmt::Debug>() {}
1309 assert_debug::<Pbkdf2Error>();
1310 }
1311
1312 #[test]
1313 fn error_implements_error_trait() {
1314 fn assert_error<T: core::error::Error>() {}
1315 assert_error::<Pbkdf2Error>();
1316 }
1317
1318 use crate::hashes::crypto::{
1321 sha256::kernels::{
1322 Sha256KernelId, compress_blocks_fn as sha256_compress_blocks_fn, required_caps as sha256_required_caps,
1323 },
1324 sha512::kernels::{
1325 ALL as SHA512_KERNELS, Sha512KernelId, compress_blocks_fn as sha512_compress_blocks_fn,
1326 required_caps as sha512_required_caps,
1327 },
1328 };
1329
1330 const SHA256_KERNELS: &[Sha256KernelId] = &[
1332 Sha256KernelId::Portable,
1333 #[cfg(target_arch = "x86_64")]
1334 Sha256KernelId::X86Sha,
1335 #[cfg(target_arch = "aarch64")]
1336 Sha256KernelId::Aarch64Sha2,
1337 #[cfg(any(target_arch = "riscv64", target_arch = "riscv32"))]
1338 Sha256KernelId::RiscvZknh,
1339 #[cfg(target_arch = "wasm32")]
1340 Sha256KernelId::WasmSimd128,
1341 #[cfg(target_arch = "s390x")]
1342 Sha256KernelId::S390xKimd,
1343 ];
1344
1345 static SHA256_VERIFY_BLOCKS: AtomicUsize = AtomicUsize::new(0);
1346 static SHA512_VERIFY_BLOCKS: AtomicUsize = AtomicUsize::new(0);
1347
1348 fn counting_sha256_compress(state: &mut [u32; 8], blocks: &[u8]) {
1349 SHA256_VERIFY_BLOCKS.fetch_add(blocks.len() / SHA256_BLOCK_SIZE, Ordering::Relaxed);
1350 sha256_compress_blocks_fn(Sha256KernelId::Portable)(state, blocks);
1351 }
1352
1353 fn counting_sha512_compress(state: &mut [u64; 8], blocks: &[u8]) {
1354 SHA512_VERIFY_BLOCKS.fetch_add(blocks.len() / SHA512_BLOCK_SIZE, Ordering::Relaxed);
1355 sha512_compress_blocks_fn(Sha512KernelId::Portable)(state, blocks);
1356 }
1357
1358 fn counted_sha256_verify(
1359 state: &Pbkdf2Sha256,
1360 salt: &[u8],
1361 iterations: u32,
1362 expected: &[u8],
1363 ) -> (Result<(), VerificationError>, usize) {
1364 SHA256_VERIFY_BLOCKS.store(0, Ordering::Relaxed);
1365 let result = state.verify(salt, iterations, expected);
1366 let blocks = SHA256_VERIFY_BLOCKS.swap(0, Ordering::Relaxed);
1367 (result, blocks)
1368 }
1369
1370 fn counted_sha512_verify(
1371 state: &Pbkdf2Sha512,
1372 salt: &[u8],
1373 iterations: u32,
1374 expected: &[u8],
1375 ) -> (Result<(), VerificationError>, usize) {
1376 SHA512_VERIFY_BLOCKS.store(0, Ordering::Relaxed);
1377 let result = state.verify(salt, iterations, expected);
1378 let blocks = SHA512_VERIFY_BLOCKS.swap(0, Ordering::Relaxed);
1379 (result, blocks)
1380 }
1381
1382 fn assert_pbkdf2_sha256_kernel(id: Sha256KernelId) {
1383 let compress = sha256_compress_blocks_fn(id);
1384 let cases: &[(&[u8], &[u8], u32, usize)] = &[
1385 (b"password", b"salt", 1, 32),
1386 (b"password", b"salt", 4, 32),
1387 (b"password", b"salt", 100, 32),
1388 (b"password", b"salt", 1, 64), (b"", b"salt", 1, 32), (b"password", b"", 1, 32), (b"p", b"s", 1, 1), (b"password", &[0xBB; 200], 1, 32), (&[0xCC; 128], b"salt", 1, 32), ];
1395
1396 for &(password, salt, iterations, dk_len) in cases {
1397 let mut expected = vec![0u8; dk_len];
1398 oracle_sha256(password, salt, iterations, &mut expected);
1399
1400 let state = Pbkdf2Sha256::new_with_compress_for_test(password, compress);
1401 let mut actual = vec![0u8; dk_len];
1402 state.derive(salt, iterations, &mut actual).unwrap();
1403
1404 assert_eq!(
1405 actual,
1406 expected,
1407 "pbkdf2-sha256 forced mismatch kernel={} pw_len={} salt_len={} c={} dk_len={}",
1408 id.as_str(),
1409 password.len(),
1410 salt.len(),
1411 iterations,
1412 dk_len,
1413 );
1414 }
1415 }
1416
1417 fn assert_pbkdf2_sha512_kernel(id: Sha512KernelId) {
1418 let compress = sha512_compress_blocks_fn(id);
1419 let cases: &[(&[u8], &[u8], u32, usize)] = &[
1420 (b"password", b"salt", 1, 64),
1421 (b"password", b"salt", 4, 64),
1422 (b"password", b"salt", 100, 64),
1423 (b"password", b"salt", 1, 128), (b"", b"salt", 1, 64), (b"password", b"", 1, 64), (b"p", b"s", 1, 1), (b"password", &[0xBB; 200], 1, 64), (&[0xCC; 200], b"salt", 1, 64), ];
1430
1431 for &(password, salt, iterations, dk_len) in cases {
1432 let mut expected = vec![0u8; dk_len];
1433 oracle_sha512(password, salt, iterations, &mut expected);
1434
1435 let state = Pbkdf2Sha512::new_with_compress_for_test(password, compress);
1436 let mut actual = vec![0u8; dk_len];
1437 state.derive(salt, iterations, &mut actual).unwrap();
1438
1439 assert_eq!(
1440 actual,
1441 expected,
1442 "pbkdf2-sha512 forced mismatch kernel={} pw_len={} salt_len={} c={} dk_len={}",
1443 id.as_str(),
1444 password.len(),
1445 salt.len(),
1446 iterations,
1447 dk_len,
1448 );
1449 }
1450 }
1451
1452 #[test]
1453 fn pbkdf2_sha256_forced_kernels_match_oracle() {
1454 let caps = crate::platform::caps();
1455 for &id in SHA256_KERNELS {
1456 if caps.has(sha256_required_caps(id)) {
1457 assert_pbkdf2_sha256_kernel(id);
1458 }
1459 }
1460 }
1461
1462 #[test]
1463 fn pbkdf2_sha512_forced_kernels_match_oracle() {
1464 let caps = crate::platform::caps();
1465 for &id in SHA512_KERNELS {
1466 if caps.has(sha512_required_caps(id)) {
1467 assert_pbkdf2_sha512_kernel(id);
1468 }
1469 }
1470 }
1471
1472 #[test]
1473 fn sha256_verify_keeps_same_compress_work_for_match_and_mismatch_positions() {
1474 let password = [0x11; 89];
1475 let salt = [0x22; 200];
1476 let state = Pbkdf2Sha256::new_with_compress_for_test(&password, counting_sha256_compress);
1477 #[cfg(not(miri))]
1478 let output_lengths = 1..=96;
1479 #[cfg(miri)]
1480 let output_lengths = [1usize, 2, 31, 32, 33, 63, 64, 65, 95, 96].into_iter();
1481
1482 for out_len in output_lengths {
1483 let mut expected = vec![0u8; out_len];
1484 state.derive(&salt, 3, &mut expected).unwrap();
1485
1486 let (ok, ok_blocks) = counted_sha256_verify(&state, &salt, 3, &expected);
1487 assert!(ok.is_ok(), "sha256 verify must accept correct output_len={out_len}");
1488
1489 let mut wrong_first = expected.clone();
1490 wrong_first[0] ^= 1;
1491 let (wrong_first_result, wrong_first_blocks) = counted_sha256_verify(&state, &salt, 3, &wrong_first);
1492 assert!(
1493 wrong_first_result.is_err(),
1494 "sha256 verify must reject first-byte mismatch output_len={out_len}"
1495 );
1496
1497 let mut wrong_last = expected.clone();
1498 let last = wrong_last.len().strict_sub(1);
1499 wrong_last[last] ^= 1;
1500 let (wrong_last_result, wrong_last_blocks) = counted_sha256_verify(&state, &salt, 3, &wrong_last);
1501 assert!(
1502 wrong_last_result.is_err(),
1503 "sha256 verify must reject last-byte mismatch output_len={out_len}"
1504 );
1505
1506 assert!(ok_blocks > 0, "sha256 verify must do real work output_len={out_len}");
1507 assert_eq!(
1508 ok_blocks, wrong_first_blocks,
1509 "sha256 verify must not short-circuit on first-byte mismatch output_len={out_len}"
1510 );
1511 assert_eq!(
1512 ok_blocks, wrong_last_blocks,
1513 "sha256 verify must not short-circuit on last-byte mismatch output_len={out_len}"
1514 );
1515 }
1516 }
1517
1518 #[test]
1519 fn sha512_verify_keeps_same_compress_work_for_match_and_mismatch_positions() {
1520 let password = [0x44; 161];
1521 let salt = [0x55; 260];
1522 let state = Pbkdf2Sha512::new_with_compress_for_test(&password, counting_sha512_compress);
1523 #[cfg(not(miri))]
1524 let output_lengths = 1..=192;
1525 #[cfg(miri)]
1526 let output_lengths = [1usize, 2, 63, 64, 65, 127, 128, 129, 191, 192].into_iter();
1527
1528 for out_len in output_lengths {
1529 let mut expected = vec![0u8; out_len];
1530 state.derive(&salt, 3, &mut expected).unwrap();
1531
1532 let (ok, ok_blocks) = counted_sha512_verify(&state, &salt, 3, &expected);
1533 assert!(ok.is_ok(), "sha512 verify must accept correct output_len={out_len}");
1534
1535 let mut wrong_first = expected.clone();
1536 wrong_first[0] ^= 1;
1537 let (wrong_first_result, wrong_first_blocks) = counted_sha512_verify(&state, &salt, 3, &wrong_first);
1538 assert!(
1539 wrong_first_result.is_err(),
1540 "sha512 verify must reject first-byte mismatch output_len={out_len}"
1541 );
1542
1543 let mut wrong_last = expected.clone();
1544 let last = wrong_last.len().strict_sub(1);
1545 wrong_last[last] ^= 1;
1546 let (wrong_last_result, wrong_last_blocks) = counted_sha512_verify(&state, &salt, 3, &wrong_last);
1547 assert!(
1548 wrong_last_result.is_err(),
1549 "sha512 verify must reject last-byte mismatch output_len={out_len}"
1550 );
1551
1552 assert!(ok_blocks > 0, "sha512 verify must do real work output_len={out_len}");
1553 assert_eq!(
1554 ok_blocks, wrong_first_blocks,
1555 "sha512 verify must not short-circuit on first-byte mismatch output_len={out_len}"
1556 );
1557 assert_eq!(
1558 ok_blocks, wrong_last_blocks,
1559 "sha512 verify must not short-circuit on last-byte mismatch output_len={out_len}"
1560 );
1561 }
1562 }
1563}