1use crate::{Hasher, MAX_HASH_BLOCK_SIZE, hmac::Hmac};
3
4pub fn derive<H: Hasher, const N: usize>(password: &[u8], salt: &[u8], iterations: u32) -> [u8; N] {
32 assert!(iterations != 0, "PBKDF2 iterations must be >= 1");
33 const {
34 assert!(
35 N <= (u32::MAX as usize) * H::OUTPUT_SIZE,
36 "PBKDF2 output length exceeds RFC 2898 limit",
37 );
38 }
39
40 let hlen = H::OUTPUT_SIZE;
41 let block_count = (N + hlen - 1) / hlen;
42
43 let mut okm = [0u8; N];
44 let mut t = [0u8; MAX_HASH_BLOCK_SIZE];
45 let mut u = [0u8; MAX_HASH_BLOCK_SIZE];
46
47 for block in 1..=block_count {
48 let mut mac = Hmac::<H>::new(password);
49 mac.update(salt);
50 mac.update(&(block as u32).to_be_bytes());
51 let hash = mac.finalize();
52 let hash_bytes = hash.as_ref();
53 t[..hlen].copy_from_slice(hash_bytes);
54 u[..hlen].copy_from_slice(hash_bytes);
55
56 for _ in 2..=iterations {
57 let mut mac = Hmac::<H>::new(password);
58 mac.update(&u[..hlen]);
59 let hash = mac.finalize();
60 let hash_bytes = hash.as_ref();
61 u[..hlen].copy_from_slice(hash_bytes);
62 for i in 0..hlen {
63 t[i] ^= u[i];
64 }
65 }
66
67 let start = (block - 1) * hlen;
68 let end = usize::min(start + hlen, N);
69 okm[start..end].copy_from_slice(&t[..end - start]);
70 }
71
72 return okm;
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78 use crate::sha2::{Sha256, Sha512};
79
80 #[test]
81 #[should_panic(expected = "PBKDF2 iterations must be >= 1")]
82 fn pbkdf2_zero_iterations() {
83 derive::<Sha256, 32>(b"password", b"salt", 0);
84 }
85
86 #[test]
87 fn pbkdf2_zero_length_output() {
88 assert_eq!(derive::<Sha256, 0>(b"password", b"salt", 1), [] as [u8; 0]);
89 }
90
91 #[test]
92 fn pbkdf2_sha256_wycheproof() {
93 const MAX_OUTPUT: usize = 128;
94
95 let data: serde_json::Value = serde_json::from_str(include_str!(
96 "../testdata/wycheproof/testvectors_v1/pbkdf2_hmacsha256_test.json"
97 ))
98 .unwrap();
99 let mut tested = 0u64;
100 for group in data["testGroups"].as_array().unwrap() {
101 for test in group["tests"].as_array().unwrap() {
102 let password_hex = test["password"].as_str().unwrap();
103 let salt_hex = test["salt"].as_str().unwrap();
104 let iteration_count = test["iterationCount"].as_u64().unwrap() as u32;
105 let dk_len = test["dkLen"].as_u64().unwrap() as usize;
106 let expected_dk_hex = test["dk"].as_str().unwrap();
107
108 let password = hex::decode(password_hex).unwrap();
109 let salt = hex::decode(salt_hex).unwrap();
110
111 let dk = derive::<Sha256, MAX_OUTPUT>(&password, &salt, iteration_count);
112 let dk_hex = hex::encode(&dk[..dk_len]);
113 assert_eq!(
114 dk_hex, expected_dk_hex,
115 "wycheproof PBKDF2-SHA-256 tcId={} dkLen={}",
116 test["tcId"], dk_len
117 );
118 tested += 1;
119 }
120 }
121 assert!(tested > 0, "no PBKDF2-SHA-256 wycheproof tests were run");
122 }
123
124 #[test]
125 fn pbkdf2_sha512_wycheproof() {
126 const MAX_OUTPUT: usize = 128;
127
128 let data: serde_json::Value = serde_json::from_str(include_str!(
129 "../testdata/wycheproof/testvectors_v1/pbkdf2_hmacsha512_test.json"
130 ))
131 .unwrap();
132 let mut tested = 0u64;
133 for group in data["testGroups"].as_array().unwrap() {
134 for test in group["tests"].as_array().unwrap() {
135 let password_hex = test["password"].as_str().unwrap();
136 let salt_hex = test["salt"].as_str().unwrap();
137 let iteration_count = test["iterationCount"].as_u64().unwrap() as u32;
138 let dk_len = test["dkLen"].as_u64().unwrap() as usize;
139 let expected_dk_hex = test["dk"].as_str().unwrap();
140
141 let password = hex::decode(password_hex).unwrap();
142 let salt = hex::decode(salt_hex).unwrap();
143
144 let dk = derive::<Sha512, MAX_OUTPUT>(&password, &salt, iteration_count);
145 let dk_hex = hex::encode(&dk[..dk_len]);
146 assert_eq!(
147 dk_hex, expected_dk_hex,
148 "wycheproof PBKDF2-SHA-512 tcId={} dkLen={}",
149 test["tcId"], dk_len
150 );
151 tested += 1;
152 }
153 }
154 assert!(tested > 0, "no PBKDF2-SHA-512 wycheproof tests were run");
155 }
156}