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 pub fn derive_key(password: &[u8], salt: &[u8], iterations: u32, okm: &mut [u8]) -> Result<(), Pbkdf2Error> {
326 Self::new(password).derive(salt, iterations, okm)
327 }
328
329 #[inline]
331 pub fn derive_key_array<const N: usize>(
332 password: &[u8],
333 salt: &[u8],
334 iterations: u32,
335 ) -> Result<[u8; N], Pbkdf2Error> {
336 Self::new(password).derive_array(salt, iterations)
337 }
338
339 #[inline]
341 #[must_use = "password verification must be checked; a dropped Result silently accepts the wrong password"]
342 pub fn verify_password(
343 password: &[u8],
344 salt: &[u8],
345 iterations: u32,
346 expected: &[u8],
347 ) -> Result<(), VerificationError> {
348 Self::new(password).verify(salt, iterations, expected)
349 }
350
351 #[cfg(test)]
353 #[allow(clippy::indexing_slicing)]
354 pub(crate) fn new_with_compress_for_test(password: &[u8], compress: $compress_ty) -> Self {
355 let mut key_block = [0u8; $block_size_const];
356 if password.len() > $block_size_const {
357 key_block[..$output_size_const].copy_from_slice(&$test_oneshot(password, compress));
358 } else {
359 key_block[..password.len()].copy_from_slice(password);
360 }
361
362 let (inner_init, outer_init) = hmac_prefix_state(&mut key_block, |ipad, opad| {
363 let mut inner_init = $h0;
364 compress(&mut inner_init, ipad);
365
366 let mut outer_init = $h0;
367 compress(&mut outer_init, opad);
368
369 (inner_init, outer_init)
370 });
371
372 Self {
373 inner_init,
374 outer_init,
375 compress,
376 }
377 }
378 }
379
380 impl Drop for $name {
381 fn drop(&mut self) {
382 for word in self.inner_init.iter_mut().chain(self.outer_init.iter_mut()) {
383 unsafe { core::ptr::write_volatile(word, 0) };
385 }
386 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
387 }
388 }
389 };
390}
391
392define_pbkdf2_sha2! {
395 Pbkdf2Sha256 {
419 output_size_const: SHA256_OUTPUT_SIZE,
420 block_size_const: SHA256_BLOCK_SIZE,
421 compress_ty: Sha256CompressBlocksFn,
422 digest_ty: Sha256,
423 h0: SHA256_H0,
424 dispatch: sha256_dispatch,
425 f_fn: pbkdf2_sha256_f,
426 iter1_fn: pbkdf2_sha256_iter1,
427 test_oneshot: sha256_oneshot_with_compress,
428 word_ty: u32,
429 recommended_iterations: 600_000,
430 }
431}
432
433#[cfg(test)]
435#[allow(clippy::indexing_slicing)]
436fn sha256_oneshot_with_compress(data: &[u8], compress: Sha256CompressBlocksFn) -> [u8; SHA256_OUTPUT_SIZE] {
437 let mut state = SHA256_H0;
438 let mut pos = 0usize;
439 while pos.strict_add(SHA256_BLOCK_SIZE) <= data.len() {
440 compress(&mut state, &data[pos..pos.strict_add(SHA256_BLOCK_SIZE)]);
441 pos = pos.strict_add(SHA256_BLOCK_SIZE);
442 }
443 let mut block = [0u8; SHA256_BLOCK_SIZE];
444 let tail = data.len().strict_sub(pos);
445 block[..tail].copy_from_slice(&data[pos..]);
446 block[tail] = 0x80;
447 if tail >= 56 {
448 compress(&mut state, &block);
449 block = [0u8; SHA256_BLOCK_SIZE];
450 }
451 block[56..64].copy_from_slice(&(data.len() as u64).strict_mul(8).to_be_bytes());
452 compress(&mut state, &block);
453 let mut out = [0u8; SHA256_OUTPUT_SIZE];
454 for (chunk, &word) in out.chunks_exact_mut(4).zip(state.iter()) {
455 chunk.copy_from_slice(&word.to_be_bytes());
456 }
457 out
458}
459
460#[allow(clippy::indexing_slicing)]
466#[inline(always)]
467fn pbkdf2_sha256_f(
468 compress: Sha256CompressBlocksFn,
469 inner_init: &[u32; 8],
470 outer_init: &[u32; 8],
471 salt: &[u8],
472 iterations: u32,
473 block_index: u32,
474 output: &mut [u8; SHA256_OUTPUT_SIZE],
475) {
476 let mut state: [u32; 8];
477 let mut u_words: [u32; 8];
478 let mut result_words: [u32; 8];
479
480 state = *inner_init;
482 let msg_len = salt.len().strict_add(4);
483 let total_inner = (SHA256_BLOCK_SIZE as u64).strict_add(msg_len as u64);
484
485 let mut block = [0u8; SHA256_BLOCK_SIZE];
486 if salt.len() <= SHA256_INLINE_SALT_MAX {
487 block[..salt.len()].copy_from_slice(salt);
488 let pos = salt.len().strict_add(4);
489 block[salt.len()..pos].copy_from_slice(&block_index.to_be_bytes());
490 block[pos] = 0x80;
491 block[56..SHA256_BLOCK_SIZE].copy_from_slice(&total_inner.strict_mul(8).to_be_bytes());
492 compress(&mut state, &block);
493 } else {
494 let mut pos = 0usize;
495
496 let mut salt_off = 0usize;
498 while salt_off < salt.len() {
499 let space = SHA256_BLOCK_SIZE.strict_sub(pos);
500 let remaining = salt.len().strict_sub(salt_off);
501 let take = if space < remaining { space } else { remaining };
502 block[pos..pos.strict_add(take)].copy_from_slice(&salt[salt_off..salt_off.strict_add(take)]);
503 pos = pos.strict_add(take);
504 salt_off = salt_off.strict_add(take);
505 if pos == SHA256_BLOCK_SIZE {
506 compress(&mut state, &block);
507 block = [0u8; SHA256_BLOCK_SIZE];
508 pos = 0;
509 }
510 }
511
512 for &b in &block_index.to_be_bytes() {
514 block[pos] = b;
515 pos = pos.strict_add(1);
516 if pos == SHA256_BLOCK_SIZE {
517 compress(&mut state, &block);
518 block = [0u8; SHA256_BLOCK_SIZE];
519 pos = 0;
520 }
521 }
522
523 block[pos] = 0x80;
525 if pos.strict_add(1) > 56 {
526 compress(&mut state, &block);
527 block = [0u8; SHA256_BLOCK_SIZE];
528 }
529 block[56..SHA256_BLOCK_SIZE].copy_from_slice(&total_inner.strict_mul(8).to_be_bytes());
530 compress(&mut state, &block);
531 }
532
533 let mut outer_block = [0u8; SHA256_BLOCK_SIZE];
535 write_u32x8_be(&mut outer_block[..SHA256_OUTPUT_SIZE], &state);
536 outer_block[SHA256_OUTPUT_SIZE] = 0x80;
537 outer_block[56..SHA256_BLOCK_SIZE].copy_from_slice(&768u64.to_be_bytes());
539
540 state = *outer_init;
541 compress(&mut state, &outer_block);
542 u_words = state;
543 result_words = u_words;
544
545 if iterations == 1 {
546 write_u32x8_be(output, &result_words);
547 ct::zeroize_no_fence(&mut outer_block);
548 ct::zeroize_no_fence(&mut block);
549 zeroize_u32x8_no_fence(&mut state);
550 zeroize_u32x8_no_fence(&mut u_words);
551 zeroize_u32x8_no_fence(&mut result_words);
552 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
553 return;
554 }
555
556 let mut inner_block = [0u8; SHA256_BLOCK_SIZE];
559 inner_block[SHA256_OUTPUT_SIZE] = 0x80;
560 inner_block[56..SHA256_BLOCK_SIZE].copy_from_slice(&768u64.to_be_bytes());
561
562 for _ in 1..iterations {
563 write_u32x8_be(&mut inner_block[..SHA256_OUTPUT_SIZE], &u_words);
565 state = *inner_init;
566 compress(&mut state, &inner_block);
567
568 write_u32x8_be(&mut outer_block[..SHA256_OUTPUT_SIZE], &state);
570
571 state = *outer_init;
573 compress(&mut state, &outer_block);
574
575 u_words = state;
576
577 for (dst, &word) in result_words.iter_mut().zip(u_words.iter()) {
579 *dst ^= word;
580 }
581 }
582
583 write_u32x8_be(output, &result_words);
584
585 ct::zeroize_no_fence(&mut inner_block);
587 ct::zeroize_no_fence(&mut outer_block);
588 ct::zeroize_no_fence(&mut block);
589 zeroize_u32x8_no_fence(&mut state);
590 zeroize_u32x8_no_fence(&mut u_words);
591 zeroize_u32x8_no_fence(&mut result_words);
592 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
593}
594
595#[allow(clippy::indexing_slicing)]
596#[inline(always)]
597fn pbkdf2_sha256_iter1(
598 compress: Sha256CompressBlocksFn,
599 inner_init: &[u32; 8],
600 outer_init: &[u32; 8],
601 salt: &[u8],
602 okm: &mut [u8],
603) {
604 if salt.len() > SHA256_INLINE_SALT_MAX {
605 let mut block_index = 1u32;
606 let mut chunks = okm.chunks_exact_mut(SHA256_OUTPUT_SIZE);
607 for chunk in chunks.by_ref() {
608 let full_chunk = unsafe { &mut *(chunk.as_mut_ptr().cast::<[u8; SHA256_OUTPUT_SIZE]>()) };
610 pbkdf2_sha256_f(compress, inner_init, outer_init, salt, 1, block_index, full_chunk);
611 block_index = block_index.strict_add(1);
612 }
613 let tail = chunks.into_remainder();
614 if !tail.is_empty() {
615 let mut block_out = [0u8; SHA256_OUTPUT_SIZE];
616 pbkdf2_sha256_f(compress, inner_init, outer_init, salt, 1, block_index, &mut block_out);
617 tail.copy_from_slice(&block_out[..tail.len()]);
618 ct::zeroize(&mut block_out);
619 }
620 return;
621 }
622
623 let msg_len = salt.len().strict_add(4);
624 let total_inner = (SHA256_BLOCK_SIZE as u64).strict_add(msg_len as u64);
625 let index_pos = salt.len();
626 let pad_pos = index_pos.strict_add(4);
627
628 let mut block = [0u8; SHA256_BLOCK_SIZE];
629 block[..salt.len()].copy_from_slice(salt);
630 block[pad_pos] = 0x80;
631 block[56..SHA256_BLOCK_SIZE].copy_from_slice(&total_inner.strict_mul(8).to_be_bytes());
632
633 let mut outer_block = [0u8; SHA256_BLOCK_SIZE];
634 outer_block[SHA256_OUTPUT_SIZE] = 0x80;
635 outer_block[56..SHA256_BLOCK_SIZE].copy_from_slice(&768u64.to_be_bytes());
636
637 let mut state = [0u32; 8];
638 let mut block_index = 1u32;
639
640 let mut chunks = okm.chunks_exact_mut(SHA256_OUTPUT_SIZE);
641 for chunk in chunks.by_ref() {
642 block[index_pos..pad_pos].copy_from_slice(&block_index.to_be_bytes());
643
644 state = *inner_init;
645 compress(&mut state, &block);
646
647 write_u32x8_be(&mut outer_block[..SHA256_OUTPUT_SIZE], &state);
648 state = *outer_init;
649 compress(&mut state, &outer_block);
650
651 write_u32x8_be(chunk, &state);
652 block_index = block_index.strict_add(1);
653 }
654
655 let tail = chunks.into_remainder();
656 if !tail.is_empty() {
657 block[index_pos..pad_pos].copy_from_slice(&block_index.to_be_bytes());
658
659 state = *inner_init;
660 compress(&mut state, &block);
661
662 write_u32x8_be(&mut outer_block[..SHA256_OUTPUT_SIZE], &state);
663 state = *outer_init;
664 compress(&mut state, &outer_block);
665
666 let mut block_out = [0u8; SHA256_OUTPUT_SIZE];
667 write_u32x8_be(&mut block_out, &state);
668 tail.copy_from_slice(&block_out[..tail.len()]);
669 ct::zeroize_no_fence(&mut block_out);
670 }
671
672 ct::zeroize_no_fence(&mut block);
673 ct::zeroize_no_fence(&mut outer_block);
674 zeroize_u32x8_no_fence(&mut state);
675 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
676}
677
678define_pbkdf2_sha2! {
681 Pbkdf2Sha512 {
697 output_size_const: SHA512_OUTPUT_SIZE,
698 block_size_const: SHA512_BLOCK_SIZE,
699 compress_ty: Sha512CompressBlocksFn,
700 digest_ty: Sha512,
701 h0: SHA512_H0,
702 dispatch: sha512_dispatch,
703 f_fn: pbkdf2_sha512_f,
704 iter1_fn: pbkdf2_sha512_iter1,
705 test_oneshot: sha512_oneshot_with_compress,
706 word_ty: u64,
707 recommended_iterations: 210_000,
708 }
709}
710
711#[cfg(test)]
713#[allow(clippy::indexing_slicing)]
714fn sha512_oneshot_with_compress(data: &[u8], compress: Sha512CompressBlocksFn) -> [u8; SHA512_OUTPUT_SIZE] {
715 let mut state = SHA512_H0;
716 let mut pos = 0usize;
717 while pos.strict_add(SHA512_BLOCK_SIZE) <= data.len() {
718 compress(&mut state, &data[pos..pos.strict_add(SHA512_BLOCK_SIZE)]);
719 pos = pos.strict_add(SHA512_BLOCK_SIZE);
720 }
721 let mut block = [0u8; SHA512_BLOCK_SIZE];
722 let tail = data.len().strict_sub(pos);
723 block[..tail].copy_from_slice(&data[pos..]);
724 block[tail] = 0x80;
725 if tail >= 112 {
726 compress(&mut state, &block);
727 block = [0u8; SHA512_BLOCK_SIZE];
728 }
729 block[112..128].copy_from_slice(&(data.len() as u128).strict_mul(8).to_be_bytes());
730 compress(&mut state, &block);
731 let mut out = [0u8; SHA512_OUTPUT_SIZE];
732 for (chunk, &word) in out.chunks_exact_mut(8).zip(state.iter()) {
733 chunk.copy_from_slice(&word.to_be_bytes());
734 }
735 out
736}
737
738#[allow(clippy::indexing_slicing)]
740#[inline(always)]
741fn pbkdf2_sha512_f(
742 compress: Sha512CompressBlocksFn,
743 inner_init: &[u64; 8],
744 outer_init: &[u64; 8],
745 salt: &[u8],
746 iterations: u32,
747 block_index: u32,
748 output: &mut [u8; SHA512_OUTPUT_SIZE],
749) {
750 let mut state: [u64; 8];
751 let mut u_words: [u64; 8];
752 let mut result_words: [u64; 8];
753
754 state = *inner_init;
756 let msg_len = salt.len().strict_add(4);
757 let total_inner = (SHA512_BLOCK_SIZE as u128).strict_add(msg_len as u128);
758
759 let mut block = [0u8; SHA512_BLOCK_SIZE];
760 if salt.len() <= SHA512_INLINE_SALT_MAX {
761 block[..salt.len()].copy_from_slice(salt);
762 let pos = salt.len().strict_add(4);
763 block[salt.len()..pos].copy_from_slice(&block_index.to_be_bytes());
764 block[pos] = 0x80;
765 block[112..SHA512_BLOCK_SIZE].copy_from_slice(&total_inner.strict_mul(8).to_be_bytes());
766 compress(&mut state, &block);
767 } else {
768 let mut pos = 0usize;
769
770 let mut salt_off = 0usize;
772 while salt_off < salt.len() {
773 let space = SHA512_BLOCK_SIZE.strict_sub(pos);
774 let remaining = salt.len().strict_sub(salt_off);
775 let take = if space < remaining { space } else { remaining };
776 block[pos..pos.strict_add(take)].copy_from_slice(&salt[salt_off..salt_off.strict_add(take)]);
777 pos = pos.strict_add(take);
778 salt_off = salt_off.strict_add(take);
779 if pos == SHA512_BLOCK_SIZE {
780 compress(&mut state, &block);
781 block = [0u8; SHA512_BLOCK_SIZE];
782 pos = 0;
783 }
784 }
785
786 for &b in &block_index.to_be_bytes() {
788 block[pos] = b;
789 pos = pos.strict_add(1);
790 if pos == SHA512_BLOCK_SIZE {
791 compress(&mut state, &block);
792 block = [0u8; SHA512_BLOCK_SIZE];
793 pos = 0;
794 }
795 }
796
797 block[pos] = 0x80;
799 if pos.strict_add(1) > 112 {
800 compress(&mut state, &block);
801 block = [0u8; SHA512_BLOCK_SIZE];
802 }
803 block[112..SHA512_BLOCK_SIZE].copy_from_slice(&total_inner.strict_mul(8).to_be_bytes());
804 compress(&mut state, &block);
805 }
806
807 let mut outer_block = [0u8; SHA512_BLOCK_SIZE];
809 write_u64x8_be(&mut outer_block[..SHA512_OUTPUT_SIZE], &state);
810 outer_block[SHA512_OUTPUT_SIZE] = 0x80;
811 outer_block[112..SHA512_BLOCK_SIZE].copy_from_slice(&1536u128.to_be_bytes());
813
814 state = *outer_init;
815 compress(&mut state, &outer_block);
816 u_words = state;
817 result_words = u_words;
818
819 let mut inner_block = [0u8; SHA512_BLOCK_SIZE];
821 inner_block[SHA512_OUTPUT_SIZE] = 0x80;
822 inner_block[112..SHA512_BLOCK_SIZE].copy_from_slice(&1536u128.to_be_bytes());
823
824 for _ in 1..iterations {
825 write_u64x8_be(&mut inner_block[..SHA512_OUTPUT_SIZE], &u_words);
826 state = *inner_init;
827 compress(&mut state, &inner_block);
828
829 write_u64x8_be(&mut outer_block[..SHA512_OUTPUT_SIZE], &state);
830
831 state = *outer_init;
832 compress(&mut state, &outer_block);
833
834 u_words = state;
835
836 for (dst, &word) in result_words.iter_mut().zip(u_words.iter()) {
837 *dst ^= word;
838 }
839 }
840
841 write_u64x8_be(output, &result_words);
842
843 ct::zeroize_no_fence(&mut inner_block);
844 ct::zeroize_no_fence(&mut outer_block);
845 ct::zeroize_no_fence(&mut block);
846 zeroize_u64x8_no_fence(&mut state);
847 zeroize_u64x8_no_fence(&mut u_words);
848 zeroize_u64x8_no_fence(&mut result_words);
849 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
850}
851
852#[allow(clippy::indexing_slicing)]
853#[inline(always)]
854fn pbkdf2_sha512_iter1(
855 compress: Sha512CompressBlocksFn,
856 inner_init: &[u64; 8],
857 outer_init: &[u64; 8],
858 salt: &[u8],
859 okm: &mut [u8],
860) {
861 if salt.len() > SHA512_INLINE_SALT_MAX {
862 let mut block_index = 1u32;
863 let mut chunks = okm.chunks_exact_mut(SHA512_OUTPUT_SIZE);
864 for chunk in chunks.by_ref() {
865 let full_chunk = unsafe { &mut *(chunk.as_mut_ptr().cast::<[u8; SHA512_OUTPUT_SIZE]>()) };
867 pbkdf2_sha512_f(compress, inner_init, outer_init, salt, 1, block_index, full_chunk);
868 block_index = block_index.strict_add(1);
869 }
870 let tail = chunks.into_remainder();
871 if !tail.is_empty() {
872 let mut block_out = [0u8; SHA512_OUTPUT_SIZE];
873 pbkdf2_sha512_f(compress, inner_init, outer_init, salt, 1, block_index, &mut block_out);
874 tail.copy_from_slice(&block_out[..tail.len()]);
875 ct::zeroize(&mut block_out);
876 }
877 return;
878 }
879
880 let msg_len = salt.len().strict_add(4);
881 let total_inner = (SHA512_BLOCK_SIZE as u128).strict_add(msg_len as u128);
882 let index_pos = salt.len();
883 let pad_pos = index_pos.strict_add(4);
884
885 let mut block = [0u8; SHA512_BLOCK_SIZE];
886 block[..salt.len()].copy_from_slice(salt);
887 block[pad_pos] = 0x80;
888 block[112..SHA512_BLOCK_SIZE].copy_from_slice(&total_inner.strict_mul(8).to_be_bytes());
889
890 let mut outer_block = [0u8; SHA512_BLOCK_SIZE];
891 outer_block[SHA512_OUTPUT_SIZE] = 0x80;
892 outer_block[112..SHA512_BLOCK_SIZE].copy_from_slice(&1536u128.to_be_bytes());
893
894 let mut state = [0u64; 8];
895 let mut block_index = 1u32;
896
897 let mut chunks = okm.chunks_exact_mut(SHA512_OUTPUT_SIZE);
898 for chunk in chunks.by_ref() {
899 block[index_pos..pad_pos].copy_from_slice(&block_index.to_be_bytes());
900
901 state = *inner_init;
902 compress(&mut state, &block);
903
904 write_u64x8_be(&mut outer_block[..SHA512_OUTPUT_SIZE], &state);
905 state = *outer_init;
906 compress(&mut state, &outer_block);
907
908 write_u64x8_be(chunk, &state);
909 block_index = block_index.strict_add(1);
910 }
911
912 let tail = chunks.into_remainder();
913 if !tail.is_empty() {
914 block[index_pos..pad_pos].copy_from_slice(&block_index.to_be_bytes());
915
916 state = *inner_init;
917 compress(&mut state, &block);
918
919 write_u64x8_be(&mut outer_block[..SHA512_OUTPUT_SIZE], &state);
920 state = *outer_init;
921 compress(&mut state, &outer_block);
922
923 let mut block_out = [0u8; SHA512_OUTPUT_SIZE];
924 write_u64x8_be(&mut block_out, &state);
925 tail.copy_from_slice(&block_out[..tail.len()]);
926 ct::zeroize_no_fence(&mut block_out);
927 }
928
929 ct::zeroize_no_fence(&mut block);
930 ct::zeroize_no_fence(&mut outer_block);
931 zeroize_u64x8_no_fence(&mut state);
932 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
933}
934
935#[cfg(test)]
936mod tests {
937 use alloc::vec;
938 use core::sync::atomic::{AtomicUsize, Ordering};
939
940 use super::*;
941
942 #[test]
945 fn rfc7914_sha256_vector_1() {
946 let mut dk = [0u8; 64];
947 Pbkdf2Sha256::derive_key(b"passwd", b"salt", 1, &mut dk).unwrap();
948 assert_eq!(
949 dk,
950 [
951 0x55, 0xac, 0x04, 0x6e, 0x56, 0xe3, 0x08, 0x9f, 0xec, 0x16, 0x91, 0xc2, 0x25, 0x44, 0xb6, 0x05, 0xf9, 0x41,
952 0x85, 0x21, 0x6d, 0xde, 0x04, 0x65, 0xe6, 0x8b, 0x9d, 0x57, 0xc2, 0x0d, 0xac, 0xbc, 0x49, 0xca, 0x9c, 0xcc,
953 0xf1, 0x79, 0xb6, 0x45, 0x99, 0x16, 0x64, 0xb3, 0x9d, 0x77, 0xef, 0x31, 0x7c, 0x71, 0xb8, 0x45, 0xb1, 0xe3,
954 0x0b, 0xd5, 0x09, 0x11, 0x20, 0x41, 0xd3, 0xa1, 0x97, 0x83,
955 ]
956 );
957 }
958
959 #[cfg(not(miri))]
960 #[test]
961 fn rfc7914_sha256_vector_2() {
962 let mut dk = [0u8; 64];
963 Pbkdf2Sha256::derive_key(b"Password", b"NaCl", 80000, &mut dk).unwrap();
964 assert_eq!(
965 dk,
966 [
967 0x4d, 0xdc, 0xd8, 0xf6, 0x0b, 0x98, 0xbe, 0x21, 0x83, 0x0c, 0xee, 0x5e, 0xf2, 0x27, 0x01, 0xf9, 0x64, 0x1a,
968 0x44, 0x18, 0xd0, 0x4c, 0x04, 0x14, 0xae, 0xff, 0x08, 0x87, 0x6b, 0x34, 0xab, 0x56, 0xa1, 0xd4, 0x25, 0xa1,
969 0x22, 0x58, 0x33, 0x54, 0x9a, 0xdb, 0x84, 0x1b, 0x51, 0xc9, 0xb3, 0x17, 0x6a, 0x27, 0x2b, 0xde, 0xbb, 0xa1,
970 0xd0, 0x78, 0x47, 0x8f, 0x62, 0xb3, 0x97, 0xf3, 0x3c, 0x8d,
971 ]
972 );
973 }
974
975 fn oracle_sha256(password: &[u8], salt: &[u8], iterations: u32, out: &mut [u8]) {
978 pbkdf2::pbkdf2_hmac::<sha2::Sha256>(password, salt, iterations, out);
979 }
980
981 fn oracle_sha512(password: &[u8], salt: &[u8], iterations: u32, out: &mut [u8]) {
982 pbkdf2::pbkdf2_hmac::<sha2::Sha512>(password, salt, iterations, out);
983 }
984
985 #[test]
986 fn sha256_matches_oracle() {
987 #[cfg(not(miri))]
988 let cases: &[(&[u8], &[u8], u32, usize)] = &[
989 (b"password", b"salt", 1, 32),
990 (b"password", b"salt", 2, 32),
991 (b"password", b"salt", 4096, 32),
992 (b"password", b"salt", 1, 64),
993 (b"password", b"salt", 100, 64),
994 (b"", b"salt", 1, 32),
995 (b"password", b"", 1, 32),
996 (b"", b"", 1, 32),
997 (b"p", b"s", 1, 1),
998 (b"password", b"salt", 1, 20),
999 (b"password", b"salt", 1, 48),
1000 (b"password", b"salt", 1, 96),
1001 (b"password", b"salt", 1, 128),
1002 (&[0xAA; 100], b"salt", 1, 32),
1004 (b"password", &[0xBB; 200], 1, 32),
1006 (&[0xCC; 128], b"salt", 1, 32),
1008 ];
1009 #[cfg(miri)]
1010 let cases: &[(&[u8], &[u8], u32, usize)] = &[
1011 (b"password", b"salt", 1, 32),
1012 (b"password", b"salt", 2, 32),
1013 (b"password", b"salt", 16, 64),
1014 (b"", b"", 1, 32),
1015 (b"p", b"s", 1, 1),
1016 (b"password", b"salt", 1, 96),
1017 (&[0xAA; 100], b"salt", 1, 32),
1018 (b"password", &[0xBB; 200], 1, 32),
1019 (&[0xCC; 128], b"salt", 1, 32),
1020 ];
1021
1022 for &(password, salt, iterations, dk_len) in cases {
1023 let mut expected = vec![0u8; dk_len];
1024 oracle_sha256(password, salt, iterations, &mut expected);
1025
1026 let mut actual = vec![0u8; dk_len];
1027 Pbkdf2Sha256::derive_key(password, salt, iterations, &mut actual).unwrap();
1028
1029 assert_eq!(
1030 actual,
1031 expected,
1032 "SHA-256 mismatch: pw_len={} salt_len={} c={} dk_len={}",
1033 password.len(),
1034 salt.len(),
1035 iterations,
1036 dk_len,
1037 );
1038 }
1039 }
1040
1041 #[test]
1042 fn sha512_matches_oracle() {
1043 #[cfg(not(miri))]
1044 let cases: &[(&[u8], &[u8], u32, usize)] = &[
1045 (b"password", b"salt", 1, 64),
1046 (b"password", b"salt", 2, 64),
1047 (b"password", b"salt", 4096, 64),
1048 (b"password", b"salt", 1, 128),
1049 (b"password", b"salt", 100, 128),
1050 (b"", b"salt", 1, 64),
1051 (b"password", b"", 1, 64),
1052 (b"", b"", 1, 64),
1053 (b"p", b"s", 1, 1),
1054 (b"password", b"salt", 1, 20),
1055 (b"password", b"salt", 1, 48),
1056 (b"password", b"salt", 1, 96),
1057 (b"password", b"salt", 1, 192),
1058 (b"password", &[0xBB; 200], 1, 64),
1060 (&[0xCC; 200], b"salt", 1, 64),
1062 ];
1063 #[cfg(miri)]
1064 let cases: &[(&[u8], &[u8], u32, usize)] = &[
1065 (b"password", b"salt", 1, 64),
1066 (b"password", b"salt", 2, 64),
1067 (b"password", b"salt", 16, 128),
1068 (b"", b"", 1, 64),
1069 (b"p", b"s", 1, 1),
1070 (b"password", b"salt", 1, 192),
1071 (b"password", &[0xBB; 200], 1, 64),
1072 (&[0xCC; 200], b"salt", 1, 64),
1073 ];
1074
1075 for &(password, salt, iterations, dk_len) in cases {
1076 let mut expected = vec![0u8; dk_len];
1077 oracle_sha512(password, salt, iterations, &mut expected);
1078
1079 let mut actual = vec![0u8; dk_len];
1080 Pbkdf2Sha512::derive_key(password, salt, iterations, &mut actual).unwrap();
1081
1082 assert_eq!(
1083 actual,
1084 expected,
1085 "SHA-512 mismatch: pw_len={} salt_len={} c={} dk_len={}",
1086 password.len(),
1087 salt.len(),
1088 iterations,
1089 dk_len,
1090 );
1091 }
1092 }
1093
1094 #[test]
1097 fn sha256_verify_correct_password() {
1098 let dk = Pbkdf2Sha256::derive_key_array::<32>(b"password", b"salt", 100).unwrap();
1099 assert!(Pbkdf2Sha256::verify_password(b"password", b"salt", 100, &dk).is_ok());
1100 }
1101
1102 #[test]
1103 fn sha256_verify_wrong_password() {
1104 let dk = Pbkdf2Sha256::derive_key_array::<32>(b"password", b"salt", 100).unwrap();
1105 assert!(Pbkdf2Sha256::verify_password(b"wrong", b"salt", 100, &dk).is_err());
1106 }
1107
1108 #[test]
1109 fn sha256_verify_wrong_salt() {
1110 let dk = Pbkdf2Sha256::derive_key_array::<32>(b"password", b"salt", 100).unwrap();
1111 assert!(Pbkdf2Sha256::verify_password(b"password", b"wrong", 100, &dk).is_err());
1112 }
1113
1114 #[test]
1115 fn sha256_verify_wrong_iterations() {
1116 let dk = Pbkdf2Sha256::derive_key_array::<32>(b"password", b"salt", 100).unwrap();
1117 assert!(Pbkdf2Sha256::verify_password(b"password", b"salt", 101, &dk).is_err());
1118 }
1119
1120 #[test]
1121 fn sha512_verify_correct_password() {
1122 let dk = Pbkdf2Sha512::derive_key_array::<64>(b"password", b"salt", 100).unwrap();
1123 assert!(Pbkdf2Sha512::verify_password(b"password", b"salt", 100, &dk).is_ok());
1124 }
1125
1126 #[test]
1127 fn sha512_verify_wrong_password() {
1128 let dk = Pbkdf2Sha512::derive_key_array::<64>(b"password", b"salt", 100).unwrap();
1129 assert!(Pbkdf2Sha512::verify_password(b"wrong", b"salt", 100, &dk).is_err());
1130 }
1131
1132 #[test]
1135 fn sha256_zero_iterations_error() {
1136 let mut dk = [0u8; 32];
1137 assert_eq!(
1138 Pbkdf2Sha256::derive_key(b"pw", b"salt", 0, &mut dk),
1139 Err(Pbkdf2Error::InvalidIterations)
1140 );
1141 }
1142
1143 #[test]
1144 fn sha512_zero_iterations_error() {
1145 let mut dk = [0u8; 64];
1146 assert_eq!(
1147 Pbkdf2Sha512::derive_key(b"pw", b"salt", 0, &mut dk),
1148 Err(Pbkdf2Error::InvalidIterations)
1149 );
1150 }
1151
1152 #[test]
1153 fn sha256_empty_output_ok() {
1154 assert!(Pbkdf2Sha256::derive_key(b"pw", b"salt", 1, &mut []).is_ok());
1155 }
1156
1157 #[test]
1158 fn sha512_empty_output_ok() {
1159 assert!(Pbkdf2Sha512::derive_key(b"pw", b"salt", 1, &mut []).is_ok());
1160 }
1161
1162 #[test]
1163 fn sha256_verify_zero_iterations() {
1164 assert!(Pbkdf2Sha256::verify_password(b"pw", b"salt", 0, &[0u8; 32]).is_err());
1165 }
1166
1167 #[test]
1168 fn sha256_verify_empty_expected() {
1169 assert!(Pbkdf2Sha256::verify_password(b"pw", b"salt", 1, &[]).is_err());
1170 }
1171
1172 #[test]
1173 fn sha256_verify_password_covers_output_lengths_and_mismatch_positions() {
1174 let password = [0xA5; 97];
1175 let salt = [0x5A; 200];
1176 #[cfg(not(miri))]
1177 let output_lengths = 1..=96;
1178 #[cfg(miri)]
1179 let output_lengths = [1usize, 2, 31, 32, 33, 63, 64, 65, 95, 96].into_iter();
1180
1181 for out_len in output_lengths {
1182 let mut expected = vec![0u8; out_len];
1183 Pbkdf2Sha256::derive_key(&password, &salt, 2, &mut expected).unwrap();
1184 assert!(Pbkdf2Sha256::verify_password(&password, &salt, 2, &expected).is_ok());
1185
1186 let mut wrong_first = expected.clone();
1187 wrong_first[0] ^= 1;
1188 assert!(Pbkdf2Sha256::verify_password(&password, &salt, 2, &wrong_first).is_err());
1189
1190 let mut wrong_last = expected.clone();
1191 let last = wrong_last.len().strict_sub(1);
1192 wrong_last[last] ^= 1;
1193 assert!(Pbkdf2Sha256::verify_password(&password, &salt, 2, &wrong_last).is_err());
1194 }
1195 }
1196
1197 #[test]
1198 fn sha512_verify_password_covers_output_lengths_and_mismatch_positions() {
1199 let password = [0x3C; 193];
1200 let salt = [0xC3; 260];
1201 #[cfg(not(miri))]
1202 let output_lengths = 1..=192;
1203 #[cfg(miri)]
1204 let output_lengths = [1usize, 2, 63, 64, 65, 127, 128, 129, 191, 192].into_iter();
1205
1206 for out_len in output_lengths {
1207 let mut expected = vec![0u8; out_len];
1208 Pbkdf2Sha512::derive_key(&password, &salt, 2, &mut expected).unwrap();
1209 assert!(Pbkdf2Sha512::verify_password(&password, &salt, 2, &expected).is_ok());
1210
1211 let mut wrong_first = expected.clone();
1212 wrong_first[0] ^= 1;
1213 assert!(Pbkdf2Sha512::verify_password(&password, &salt, 2, &wrong_first).is_err());
1214
1215 let mut wrong_last = expected.clone();
1216 let last = wrong_last.len().strict_sub(1);
1217 wrong_last[last] ^= 1;
1218 assert!(Pbkdf2Sha512::verify_password(&password, &salt, 2, &wrong_last).is_err());
1219 }
1220 }
1221
1222 #[test]
1225 fn sha256_state_reuse_matches_oneshot() {
1226 let state = Pbkdf2Sha256::new(b"password");
1227 let dk1 = state.derive_array::<32>(b"salt1", 100).unwrap();
1228 let dk2 = state.derive_array::<32>(b"salt2", 100).unwrap();
1229
1230 let oneshot1 = Pbkdf2Sha256::derive_key_array::<32>(b"password", b"salt1", 100).unwrap();
1231 let oneshot2 = Pbkdf2Sha256::derive_key_array::<32>(b"password", b"salt2", 100).unwrap();
1232
1233 assert_eq!(dk1, oneshot1);
1234 assert_eq!(dk2, oneshot2);
1235 assert_ne!(dk1, dk2);
1236 }
1237
1238 #[test]
1241 fn sha256_single_iteration() {
1242 let mut expected = [0u8; 32];
1243 oracle_sha256(b"pw", b"salt", 1, &mut expected);
1244 let actual = Pbkdf2Sha256::derive_key_array::<32>(b"pw", b"salt", 1).unwrap();
1245 assert_eq!(actual, expected);
1246 }
1247
1248 #[test]
1249 fn sha512_single_iteration() {
1250 let mut expected = [0u8; 64];
1251 oracle_sha512(b"pw", b"salt", 1, &mut expected);
1252 let actual = Pbkdf2Sha512::derive_key_array::<64>(b"pw", b"salt", 1).unwrap();
1253 assert_eq!(actual, expected);
1254 }
1255
1256 #[test]
1259 fn error_is_copy() {
1260 let e = Pbkdf2Error::InvalidIterations;
1261 let e2 = e;
1262 assert_eq!(e, e2);
1263 }
1264
1265 #[test]
1266 fn error_display() {
1267 fn assert_display<T: core::fmt::Display>() {}
1268 assert_display::<Pbkdf2Error>();
1269 }
1270
1271 #[test]
1272 fn error_debug() {
1273 fn assert_debug<T: core::fmt::Debug>() {}
1274 assert_debug::<Pbkdf2Error>();
1275 }
1276
1277 #[test]
1278 fn error_implements_error_trait() {
1279 fn assert_error<T: core::error::Error>() {}
1280 assert_error::<Pbkdf2Error>();
1281 }
1282
1283 use crate::hashes::crypto::{
1286 sha256::kernels::{
1287 Sha256KernelId, compress_blocks_fn as sha256_compress_blocks_fn, required_caps as sha256_required_caps,
1288 },
1289 sha512::kernels::{
1290 ALL as SHA512_KERNELS, Sha512KernelId, compress_blocks_fn as sha512_compress_blocks_fn,
1291 required_caps as sha512_required_caps,
1292 },
1293 };
1294
1295 const SHA256_KERNELS: &[Sha256KernelId] = &[
1297 Sha256KernelId::Portable,
1298 #[cfg(target_arch = "x86_64")]
1299 Sha256KernelId::X86Sha,
1300 #[cfg(target_arch = "aarch64")]
1301 Sha256KernelId::Aarch64Sha2,
1302 #[cfg(any(target_arch = "riscv64", target_arch = "riscv32"))]
1303 Sha256KernelId::RiscvZknh,
1304 #[cfg(target_arch = "wasm32")]
1305 Sha256KernelId::WasmSimd128,
1306 #[cfg(target_arch = "s390x")]
1307 Sha256KernelId::S390xKimd,
1308 ];
1309
1310 static SHA256_VERIFY_BLOCKS: AtomicUsize = AtomicUsize::new(0);
1311 static SHA512_VERIFY_BLOCKS: AtomicUsize = AtomicUsize::new(0);
1312
1313 fn counting_sha256_compress(state: &mut [u32; 8], blocks: &[u8]) {
1314 SHA256_VERIFY_BLOCKS.fetch_add(blocks.len() / SHA256_BLOCK_SIZE, Ordering::Relaxed);
1315 sha256_compress_blocks_fn(Sha256KernelId::Portable)(state, blocks);
1316 }
1317
1318 fn counting_sha512_compress(state: &mut [u64; 8], blocks: &[u8]) {
1319 SHA512_VERIFY_BLOCKS.fetch_add(blocks.len() / SHA512_BLOCK_SIZE, Ordering::Relaxed);
1320 sha512_compress_blocks_fn(Sha512KernelId::Portable)(state, blocks);
1321 }
1322
1323 fn counted_sha256_verify(
1324 state: &Pbkdf2Sha256,
1325 salt: &[u8],
1326 iterations: u32,
1327 expected: &[u8],
1328 ) -> (Result<(), VerificationError>, usize) {
1329 SHA256_VERIFY_BLOCKS.store(0, Ordering::Relaxed);
1330 let result = state.verify(salt, iterations, expected);
1331 let blocks = SHA256_VERIFY_BLOCKS.swap(0, Ordering::Relaxed);
1332 (result, blocks)
1333 }
1334
1335 fn counted_sha512_verify(
1336 state: &Pbkdf2Sha512,
1337 salt: &[u8],
1338 iterations: u32,
1339 expected: &[u8],
1340 ) -> (Result<(), VerificationError>, usize) {
1341 SHA512_VERIFY_BLOCKS.store(0, Ordering::Relaxed);
1342 let result = state.verify(salt, iterations, expected);
1343 let blocks = SHA512_VERIFY_BLOCKS.swap(0, Ordering::Relaxed);
1344 (result, blocks)
1345 }
1346
1347 fn assert_pbkdf2_sha256_kernel(id: Sha256KernelId) {
1348 let compress = sha256_compress_blocks_fn(id);
1349 let cases: &[(&[u8], &[u8], u32, usize)] = &[
1350 (b"password", b"salt", 1, 32),
1351 (b"password", b"salt", 4, 32),
1352 (b"password", b"salt", 100, 32),
1353 (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), ];
1360
1361 for &(password, salt, iterations, dk_len) in cases {
1362 let mut expected = vec![0u8; dk_len];
1363 oracle_sha256(password, salt, iterations, &mut expected);
1364
1365 let state = Pbkdf2Sha256::new_with_compress_for_test(password, compress);
1366 let mut actual = vec![0u8; dk_len];
1367 state.derive(salt, iterations, &mut actual).unwrap();
1368
1369 assert_eq!(
1370 actual,
1371 expected,
1372 "pbkdf2-sha256 forced mismatch kernel={} pw_len={} salt_len={} c={} dk_len={}",
1373 id.as_str(),
1374 password.len(),
1375 salt.len(),
1376 iterations,
1377 dk_len,
1378 );
1379 }
1380 }
1381
1382 fn assert_pbkdf2_sha512_kernel(id: Sha512KernelId) {
1383 let compress = sha512_compress_blocks_fn(id);
1384 let cases: &[(&[u8], &[u8], u32, usize)] = &[
1385 (b"password", b"salt", 1, 64),
1386 (b"password", b"salt", 4, 64),
1387 (b"password", b"salt", 100, 64),
1388 (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), ];
1395
1396 for &(password, salt, iterations, dk_len) in cases {
1397 let mut expected = vec![0u8; dk_len];
1398 oracle_sha512(password, salt, iterations, &mut expected);
1399
1400 let state = Pbkdf2Sha512::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-sha512 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 #[test]
1418 fn pbkdf2_sha256_forced_kernels_match_oracle() {
1419 let caps = crate::platform::caps();
1420 for &id in SHA256_KERNELS {
1421 if caps.has(sha256_required_caps(id)) {
1422 assert_pbkdf2_sha256_kernel(id);
1423 }
1424 }
1425 }
1426
1427 #[test]
1428 fn pbkdf2_sha512_forced_kernels_match_oracle() {
1429 let caps = crate::platform::caps();
1430 for &id in SHA512_KERNELS {
1431 if caps.has(sha512_required_caps(id)) {
1432 assert_pbkdf2_sha512_kernel(id);
1433 }
1434 }
1435 }
1436
1437 #[test]
1438 fn sha256_verify_keeps_same_compress_work_for_match_and_mismatch_positions() {
1439 let password = [0x11; 89];
1440 let salt = [0x22; 200];
1441 let state = Pbkdf2Sha256::new_with_compress_for_test(&password, counting_sha256_compress);
1442 #[cfg(not(miri))]
1443 let output_lengths = 1..=96;
1444 #[cfg(miri)]
1445 let output_lengths = [1usize, 2, 31, 32, 33, 63, 64, 65, 95, 96].into_iter();
1446
1447 for out_len in output_lengths {
1448 let mut expected = vec![0u8; out_len];
1449 state.derive(&salt, 3, &mut expected).unwrap();
1450
1451 let (ok, ok_blocks) = counted_sha256_verify(&state, &salt, 3, &expected);
1452 assert!(ok.is_ok(), "sha256 verify must accept correct output_len={out_len}");
1453
1454 let mut wrong_first = expected.clone();
1455 wrong_first[0] ^= 1;
1456 let (wrong_first_result, wrong_first_blocks) = counted_sha256_verify(&state, &salt, 3, &wrong_first);
1457 assert!(
1458 wrong_first_result.is_err(),
1459 "sha256 verify must reject first-byte mismatch output_len={out_len}"
1460 );
1461
1462 let mut wrong_last = expected.clone();
1463 let last = wrong_last.len().strict_sub(1);
1464 wrong_last[last] ^= 1;
1465 let (wrong_last_result, wrong_last_blocks) = counted_sha256_verify(&state, &salt, 3, &wrong_last);
1466 assert!(
1467 wrong_last_result.is_err(),
1468 "sha256 verify must reject last-byte mismatch output_len={out_len}"
1469 );
1470
1471 assert!(ok_blocks > 0, "sha256 verify must do real work output_len={out_len}");
1472 assert_eq!(
1473 ok_blocks, wrong_first_blocks,
1474 "sha256 verify must not short-circuit on first-byte mismatch output_len={out_len}"
1475 );
1476 assert_eq!(
1477 ok_blocks, wrong_last_blocks,
1478 "sha256 verify must not short-circuit on last-byte mismatch output_len={out_len}"
1479 );
1480 }
1481 }
1482
1483 #[test]
1484 fn sha512_verify_keeps_same_compress_work_for_match_and_mismatch_positions() {
1485 let password = [0x44; 161];
1486 let salt = [0x55; 260];
1487 let state = Pbkdf2Sha512::new_with_compress_for_test(&password, counting_sha512_compress);
1488 #[cfg(not(miri))]
1489 let output_lengths = 1..=192;
1490 #[cfg(miri)]
1491 let output_lengths = [1usize, 2, 63, 64, 65, 127, 128, 129, 191, 192].into_iter();
1492
1493 for out_len in output_lengths {
1494 let mut expected = vec![0u8; out_len];
1495 state.derive(&salt, 3, &mut expected).unwrap();
1496
1497 let (ok, ok_blocks) = counted_sha512_verify(&state, &salt, 3, &expected);
1498 assert!(ok.is_ok(), "sha512 verify must accept correct output_len={out_len}");
1499
1500 let mut wrong_first = expected.clone();
1501 wrong_first[0] ^= 1;
1502 let (wrong_first_result, wrong_first_blocks) = counted_sha512_verify(&state, &salt, 3, &wrong_first);
1503 assert!(
1504 wrong_first_result.is_err(),
1505 "sha512 verify must reject first-byte mismatch output_len={out_len}"
1506 );
1507
1508 let mut wrong_last = expected.clone();
1509 let last = wrong_last.len().strict_sub(1);
1510 wrong_last[last] ^= 1;
1511 let (wrong_last_result, wrong_last_blocks) = counted_sha512_verify(&state, &salt, 3, &wrong_last);
1512 assert!(
1513 wrong_last_result.is_err(),
1514 "sha512 verify must reject last-byte mismatch output_len={out_len}"
1515 );
1516
1517 assert!(ok_blocks > 0, "sha512 verify must do real work output_len={out_len}");
1518 assert_eq!(
1519 ok_blocks, wrong_first_blocks,
1520 "sha512 verify must not short-circuit on first-byte mismatch output_len={out_len}"
1521 );
1522 assert_eq!(
1523 ok_blocks, wrong_last_blocks,
1524 "sha512 verify must not short-circuit on last-byte mismatch output_len={out_len}"
1525 );
1526 }
1527 }
1528}