use crate::hash::{Digest, Hmac};
pub fn pbkdf2<D: Digest>(password: &[u8], salt: &[u8], iterations: u32, out: &mut [u8]) {
assert!(iterations >= 1, "PBKDF2 requires at least one iteration");
let hlen = D::OUTPUT_LEN;
let max_len = (u32::MAX as usize).saturating_mul(hlen);
assert!(
out.len() <= max_len,
"PBKDF2 output length exceeds RFC 8018 §5.2 limit (2^32 - 1 blocks)",
);
let mut block_index: u32 = 1;
for chunk in out.chunks_mut(hlen) {
derive_block::<D>(password, salt, iterations, block_index, chunk);
block_index = block_index
.checked_add(1)
.expect("PBKDF2 block counter overflowed 2^32");
}
}
fn derive_block<D: Digest>(
password: &[u8],
salt: &[u8],
iterations: u32,
index: u32,
out: &mut [u8],
) {
let mut u = {
let mut mac = Hmac::<D>::new(password);
mac.update(salt);
mac.update(&index.to_be_bytes());
mac.finalize()
};
let mut acc = u; for _ in 1..iterations {
u = Hmac::<D>::mac(password, u.as_ref());
for (a, b) in acc.as_mut().iter_mut().zip(u.as_ref().iter()) {
*a ^= *b;
}
}
let n = out.len().min(acc.as_ref().len());
out[..n].copy_from_slice(&acc.as_ref()[..n]);
for b in u.as_mut() {
*b = 0;
}
for b in acc.as_mut() {
*b = 0;
}
let _ = core::hint::black_box(u.as_ref());
let _ = core::hint::black_box(acc.as_ref());
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hash::Sha256;
use crate::test_util::from_hex;
#[test]
fn sha256_c1() {
let mut out = [0u8; 32];
pbkdf2::<Sha256>(b"password", b"salt", 1, &mut out);
assert_eq!(
out,
from_hex::<32>("120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b")
);
}
#[test]
fn sha256_c2() {
let mut out = [0u8; 32];
pbkdf2::<Sha256>(b"password", b"salt", 2, &mut out);
assert_eq!(
out,
from_hex::<32>("ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43")
);
}
#[test]
fn sha256_c4096() {
let mut out = [0u8; 32];
pbkdf2::<Sha256>(b"password", b"salt", 4096, &mut out);
assert_eq!(
out,
from_hex::<32>("c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a")
);
}
#[test]
fn sha256_multiblock_output() {
let mut out = [0u8; 40];
pbkdf2::<Sha256>(
b"passwordPASSWORDpassword",
b"saltSALTsaltSALTsaltSALTsaltSALTsalt",
4096,
&mut out,
);
assert_eq!(
out,
from_hex::<40>(
"348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c4e2a1fb8dd53e1\
c635518c7dac47e9"
)
);
}
#[test]
fn partial_block_output() {
let mut full = [0u8; 32];
pbkdf2::<Sha256>(b"pw", b"salt", 10, &mut full);
let mut short = [0u8; 20];
pbkdf2::<Sha256>(b"pw", b"salt", 10, &mut short);
assert_eq!(short, full[..20]);
}
}