1#![forbid(unsafe_code)]
2
3use oxicrypto_core::{CryptoError, Kdf, PasswordHash as PasswordHashTrait, PasswordHashParams};
9use sha2::{Sha256, Sha512};
10
11#[must_use = "PBKDF2 derive result must be checked"]
23pub fn pbkdf2_sha256(
24 password: &[u8],
25 salt: &[u8],
26 iterations: u32,
27 out: &mut [u8],
28) -> Result<(), CryptoError> {
29 if out.is_empty() {
30 return Err(CryptoError::BadInput);
31 }
32 if iterations == 0 {
33 return Err(CryptoError::BadInput);
34 }
35 pbkdf2::pbkdf2_hmac::<Sha256>(password, salt, iterations, out);
36 Ok(())
37}
38
39#[must_use = "PBKDF2 derive result must be checked"]
41pub fn pbkdf2_sha512(
42 password: &[u8],
43 salt: &[u8],
44 iterations: u32,
45 out: &mut [u8],
46) -> Result<(), CryptoError> {
47 if out.is_empty() {
48 return Err(CryptoError::BadInput);
49 }
50 if iterations == 0 {
51 return Err(CryptoError::BadInput);
52 }
53 pbkdf2::pbkdf2_hmac::<Sha512>(password, salt, iterations, out);
54 Ok(())
55}
56
57#[derive(Debug, Clone, Copy)]
66pub struct Pbkdf2Params {
67 pub iterations: u32,
69}
70
71impl PasswordHashParams for Pbkdf2Params {
72 fn memory_cost(&self) -> Option<u32> {
73 None
74 }
75
76 fn time_cost(&self) -> Option<u32> {
77 Some(self.iterations)
78 }
79
80 fn parallelism(&self) -> Option<u32> {
81 None
82 }
83}
84
85#[derive(Debug, Clone, Copy)]
102pub struct Pbkdf2Sha256Hasher {
103 pub iterations: u32,
105}
106
107impl Pbkdf2Sha256Hasher {
108 #[must_use]
110 pub fn new(iterations: u32) -> Self {
111 Self { iterations }
112 }
113
114 #[must_use]
116 pub fn interactive() -> Self {
117 Self {
118 iterations: 310_000,
119 }
120 }
121
122 #[must_use]
124 pub fn moderate() -> Self {
125 Self {
126 iterations: 600_000,
127 }
128 }
129
130 #[must_use]
132 pub fn sensitive() -> Self {
133 Self {
134 iterations: 1_000_000,
135 }
136 }
137
138 #[must_use]
140 pub fn params(&self) -> Pbkdf2Params {
141 Pbkdf2Params {
142 iterations: self.iterations,
143 }
144 }
145}
146
147impl PasswordHashTrait for Pbkdf2Sha256Hasher {
148 fn name(&self) -> &'static str {
149 "pbkdf2-sha256"
150 }
151
152 fn hash_password(
153 &self,
154 password: &[u8],
155 salt: &[u8],
156 _params: &dyn PasswordHashParams,
157 out: &mut [u8],
158 ) -> Result<(), CryptoError> {
159 pbkdf2_sha256(password, salt, self.iterations, out)
160 }
161}
162
163impl Kdf for Pbkdf2Sha256Hasher {
164 fn name(&self) -> &'static str {
165 "PBKDF2-SHA-256"
166 }
167
168 fn derive(
173 &self,
174 ikm: &[u8],
175 salt: &[u8],
176 _info: &[u8],
177 okm_out: &mut [u8],
178 ) -> Result<(), CryptoError> {
179 pbkdf2_sha256(ikm, salt, self.iterations, okm_out)
180 }
181}
182
183#[derive(Debug, Clone, Copy)]
198pub struct Pbkdf2Sha512Hasher {
199 pub iterations: u32,
201}
202
203impl Pbkdf2Sha512Hasher {
204 #[must_use]
206 pub fn new(iterations: u32) -> Self {
207 Self { iterations }
208 }
209
210 #[must_use]
213 pub fn interactive() -> Self {
214 Self {
215 iterations: 210_000,
216 }
217 }
218
219 #[must_use]
221 pub fn moderate() -> Self {
222 Self {
223 iterations: 400_000,
224 }
225 }
226
227 #[must_use]
229 pub fn sensitive() -> Self {
230 Self {
231 iterations: 700_000,
232 }
233 }
234
235 #[must_use]
237 pub fn params(&self) -> Pbkdf2Params {
238 Pbkdf2Params {
239 iterations: self.iterations,
240 }
241 }
242}
243
244impl PasswordHashTrait for Pbkdf2Sha512Hasher {
245 fn name(&self) -> &'static str {
246 "pbkdf2-sha512"
247 }
248
249 fn hash_password(
250 &self,
251 password: &[u8],
252 salt: &[u8],
253 _params: &dyn PasswordHashParams,
254 out: &mut [u8],
255 ) -> Result<(), CryptoError> {
256 pbkdf2_sha512(password, salt, self.iterations, out)
257 }
258}
259
260impl Kdf for Pbkdf2Sha512Hasher {
261 fn name(&self) -> &'static str {
262 "PBKDF2-SHA-512"
263 }
264
265 fn derive(
269 &self,
270 ikm: &[u8],
271 salt: &[u8],
272 _info: &[u8],
273 okm_out: &mut [u8],
274 ) -> Result<(), CryptoError> {
275 pbkdf2_sha512(ikm, salt, self.iterations, okm_out)
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 const SALT: &[u8] = b"test-salt-16byte";
284 const ITERS: u32 = 1_000; #[test]
287 fn pbkdf2_sha256_deterministic() {
288 let mut out1 = [0u8; 32];
289 let mut out2 = [0u8; 32];
290 pbkdf2_sha256(b"password", SALT, ITERS, &mut out1).expect("derive 1");
291 pbkdf2_sha256(b"password", SALT, ITERS, &mut out2).expect("derive 2");
292 assert_eq!(out1, out2);
293 assert_ne!(out1, [0u8; 32]);
294 }
295
296 #[test]
297 fn pbkdf2_sha512_deterministic() {
298 let mut out1 = [0u8; 64];
299 let mut out2 = [0u8; 64];
300 pbkdf2_sha512(b"password", SALT, ITERS, &mut out1).expect("derive 1");
301 pbkdf2_sha512(b"password", SALT, ITERS, &mut out2).expect("derive 2");
302 assert_eq!(out1, out2);
303 assert_ne!(out1, [0u8; 64]);
304 }
305
306 #[test]
307 fn pbkdf2_sha256_zero_iterations_errors() {
308 let mut out = [0u8; 32];
309 assert_eq!(
310 pbkdf2_sha256(b"pw", SALT, 0, &mut out),
311 Err(CryptoError::BadInput)
312 );
313 }
314
315 #[test]
316 fn pbkdf2_sha256_empty_output_errors() {
317 assert_eq!(
318 pbkdf2_sha256(b"pw", SALT, ITERS, &mut []),
319 Err(CryptoError::BadInput)
320 );
321 }
322
323 #[test]
326 fn pbkdf2_sha256_hasher_hash_password_deterministic() {
327 let hasher = Pbkdf2Sha256Hasher::new(ITERS);
328 let params = hasher.params();
329 let mut out1 = [0u8; 32];
330 let mut out2 = [0u8; 32];
331 hasher
332 .hash_password(b"password", SALT, ¶ms, &mut out1)
333 .expect("hash 1");
334 hasher
335 .hash_password(b"password", SALT, ¶ms, &mut out2)
336 .expect("hash 2");
337 assert_eq!(out1, out2);
338 assert_ne!(out1, [0u8; 32]);
339 }
340
341 #[test]
342 fn pbkdf2_sha512_hasher_hash_password_deterministic() {
343 let hasher = Pbkdf2Sha512Hasher::new(ITERS);
344 let params = hasher.params();
345 let mut out1 = [0u8; 64];
346 let mut out2 = [0u8; 64];
347 hasher
348 .hash_password(b"password", SALT, ¶ms, &mut out1)
349 .expect("hash 1");
350 hasher
351 .hash_password(b"password", SALT, ¶ms, &mut out2)
352 .expect("hash 2");
353 assert_eq!(out1, out2);
354 assert_ne!(out1, [0u8; 64]);
355 }
356
357 #[test]
360 fn pbkdf2_sha256_kdf_matches_standalone() {
361 let hasher = Pbkdf2Sha256Hasher::new(ITERS);
362 let mut from_kdf = [0u8; 32];
363 let mut from_fn = [0u8; 32];
364 hasher
365 .derive(b"key", SALT, b"", &mut from_kdf)
366 .expect("kdf derive");
367 pbkdf2_sha256(b"key", SALT, ITERS, &mut from_fn).expect("fn derive");
368 assert_eq!(from_kdf, from_fn, "Kdf::derive must match standalone fn");
369 }
370
371 #[test]
372 fn pbkdf2_sha512_kdf_matches_standalone() {
373 let hasher = Pbkdf2Sha512Hasher::new(ITERS);
374 let mut from_kdf = [0u8; 64];
375 let mut from_fn = [0u8; 64];
376 hasher
377 .derive(b"key", SALT, b"", &mut from_kdf)
378 .expect("kdf derive");
379 pbkdf2_sha512(b"key", SALT, ITERS, &mut from_fn).expect("fn derive");
380 assert_eq!(from_kdf, from_fn, "Kdf::derive must match standalone fn");
381 }
382
383 #[test]
386 fn pbkdf2_sha256_preset_cost_ordering() {
387 let interactive = Pbkdf2Sha256Hasher::interactive();
388 let moderate = Pbkdf2Sha256Hasher::moderate();
389 let sensitive = Pbkdf2Sha256Hasher::sensitive();
390 assert!(sensitive.iterations > moderate.iterations);
391 assert!(moderate.iterations > interactive.iterations);
392 }
393
394 #[test]
395 fn pbkdf2_sha512_preset_cost_ordering() {
396 let interactive = Pbkdf2Sha512Hasher::interactive();
397 let moderate = Pbkdf2Sha512Hasher::moderate();
398 let sensitive = Pbkdf2Sha512Hasher::sensitive();
399 assert!(sensitive.iterations > moderate.iterations);
400 assert!(moderate.iterations > interactive.iterations);
401 }
402
403 #[test]
404 fn pbkdf2_params_trait_impl() {
405 let params = Pbkdf2Params { iterations: 42 };
406 assert_eq!(params.memory_cost(), None);
407 assert_eq!(params.time_cost(), Some(42));
408 assert_eq!(params.parallelism(), None);
409 }
410
411 #[test]
412 fn hasher_names() {
413 assert_eq!(
414 <Pbkdf2Sha256Hasher as oxicrypto_core::PasswordHash>::name(&Pbkdf2Sha256Hasher::new(1)),
415 "pbkdf2-sha256"
416 );
417 assert_eq!(
418 <Pbkdf2Sha512Hasher as oxicrypto_core::PasswordHash>::name(&Pbkdf2Sha512Hasher::new(1)),
419 "pbkdf2-sha512"
420 );
421 }
422}