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