1#![forbid(unsafe_code)]
2
3pub mod argon2_kdf;
15pub mod balloon;
16pub mod hkdf_label;
17pub mod kbkdf;
18pub mod pbkdf2_kdf;
19pub mod scrypt_kdf;
20pub mod stretcher;
21
22pub const PBKDF2_SHA256_MIN_ITERATIONS: u32 = 600_000;
29
30pub const PBKDF2_SHA512_MIN_ITERATIONS: u32 = 210_000;
36
37pub use argon2_kdf::{
38 argon2d_derive, argon2i_derive, argon2id_derive, argon2id_to_phc_string, argon2id_verify_phc,
39 Argon2Params, Argon2idHasher,
40};
41pub use balloon::{
42 balloon_sha256, balloon_sha256_secret, balloon_sha512, balloon_sha512_secret, BalloonHasher,
43 BalloonParams, BalloonVariant, BALLOON_DELTA,
44};
45pub use hkdf_label::{hkdf_expand_label_sha256, hkdf_expand_label_sha384};
46pub use kbkdf::{
47 kbkdf_counter_hmac_sha256, kbkdf_counter_hmac_sha256_secret, kbkdf_counter_hmac_sha384,
48 kbkdf_counter_hmac_sha512,
49};
50pub use pbkdf2_kdf::{
51 pbkdf2_sha256, pbkdf2_sha512, Pbkdf2Params, Pbkdf2Sha256Hasher, Pbkdf2Sha512Hasher,
52};
53pub use scrypt_kdf::{scrypt_derive, ScryptHasher, ScryptParams};
54pub use stretcher::{
55 Argon2idStretchParams, BalloonStretchParams, KeyStretcher, Pbkdf2StretchParams,
56 ScryptStretchParams, StretchParams, Stretcher,
57};
58
59use hkdf::Hkdf;
60use oxicrypto_core::{CryptoError, Kdf, PasswordHash};
61use subtle::ConstantTimeEq;
62
63#[derive(Debug, Default, Clone, Copy)]
67pub struct HkdfSha256;
68
69impl Kdf for HkdfSha256 {
70 fn name(&self) -> &'static str {
71 "HKDF-SHA-256"
72 }
73 fn derive(
74 &self,
75 ikm: &[u8],
76 salt: &[u8],
77 info: &[u8],
78 okm_out: &mut [u8],
79 ) -> Result<(), CryptoError> {
80 if okm_out.is_empty() {
81 return Err(CryptoError::BadInput);
82 }
83 let salt_opt = if salt.is_empty() { None } else { Some(salt) };
84 let hk = Hkdf::<sha2::Sha256>::new(salt_opt, ikm);
85 hk.expand(info, okm_out)
86 .map_err(|_| CryptoError::Internal("HKDF expand failed (output too long)"))?;
87 Ok(())
88 }
89}
90
91#[derive(Debug, Default, Clone, Copy)]
95pub struct HkdfSha512;
96
97impl Kdf for HkdfSha512 {
98 fn name(&self) -> &'static str {
99 "HKDF-SHA-512"
100 }
101 fn derive(
102 &self,
103 ikm: &[u8],
104 salt: &[u8],
105 info: &[u8],
106 okm_out: &mut [u8],
107 ) -> Result<(), CryptoError> {
108 if okm_out.is_empty() {
109 return Err(CryptoError::BadInput);
110 }
111 let salt_opt = if salt.is_empty() { None } else { Some(salt) };
112 let hk = Hkdf::<sha2::Sha512>::new(salt_opt, ikm);
113 hk.expand(info, okm_out)
114 .map_err(|_| CryptoError::Internal("HKDF expand failed (output too long)"))?;
115 Ok(())
116 }
117}
118
119#[derive(Debug, Default, Clone, Copy)]
123pub struct HkdfSha384;
124
125impl Kdf for HkdfSha384 {
126 fn name(&self) -> &'static str {
127 "HKDF-SHA-384"
128 }
129 fn derive(
130 &self,
131 ikm: &[u8],
132 salt: &[u8],
133 info: &[u8],
134 okm_out: &mut [u8],
135 ) -> Result<(), CryptoError> {
136 if okm_out.is_empty() {
137 return Err(CryptoError::BadInput);
138 }
139 let salt_opt = if salt.is_empty() { None } else { Some(salt) };
140 let hk = Hkdf::<sha2::Sha384>::new(salt_opt, ikm);
141 hk.expand(info, okm_out)
142 .map_err(|_| CryptoError::Internal("HKDF-SHA-384 expand failed (output too long)"))?;
143 Ok(())
144 }
145}
146
147#[must_use]
156pub fn hkdf_sha256_extract(salt: &[u8], ikm: &[u8]) -> [u8; 32] {
157 let salt_opt = if salt.is_empty() { None } else { Some(salt) };
158 let (prk, _) = Hkdf::<sha2::Sha256>::extract(salt_opt, ikm);
159 let mut out = [0u8; 32];
160 out.copy_from_slice(&prk);
161 out
162}
163
164#[must_use = "HKDF expand result must be checked"]
169pub fn hkdf_sha256_expand(prk: &[u8], info: &[u8], okm_out: &mut [u8]) -> Result<(), CryptoError> {
170 if okm_out.is_empty() {
171 return Err(CryptoError::BadInput);
172 }
173 let hk = Hkdf::<sha2::Sha256>::from_prk(prk).map_err(|_| CryptoError::InvalidKey)?;
174 hk.expand(info, okm_out)
175 .map_err(|_| CryptoError::Internal("HKDF-SHA-256 expand failed (output too long)"))?;
176 Ok(())
177}
178
179#[must_use]
183pub fn hkdf_sha384_extract(salt: &[u8], ikm: &[u8]) -> [u8; 48] {
184 let salt_opt = if salt.is_empty() { None } else { Some(salt) };
185 let (prk, _) = Hkdf::<sha2::Sha384>::extract(salt_opt, ikm);
186 let mut out = [0u8; 48];
187 out.copy_from_slice(&prk);
188 out
189}
190
191#[must_use = "HKDF expand result must be checked"]
193pub fn hkdf_sha384_expand(prk: &[u8], info: &[u8], okm_out: &mut [u8]) -> Result<(), CryptoError> {
194 if okm_out.is_empty() {
195 return Err(CryptoError::BadInput);
196 }
197 let hk = Hkdf::<sha2::Sha384>::from_prk(prk).map_err(|_| CryptoError::InvalidKey)?;
198 hk.expand(info, okm_out)
199 .map_err(|_| CryptoError::Internal("HKDF-SHA-384 expand failed (output too long)"))?;
200 Ok(())
201}
202
203#[must_use]
207pub fn hkdf_sha512_extract(salt: &[u8], ikm: &[u8]) -> [u8; 64] {
208 let salt_opt = if salt.is_empty() { None } else { Some(salt) };
209 let (prk, _) = Hkdf::<sha2::Sha512>::extract(salt_opt, ikm);
210 let mut out = [0u8; 64];
211 out.copy_from_slice(&prk);
212 out
213}
214
215#[must_use = "HKDF expand result must be checked"]
217pub fn hkdf_sha512_expand(prk: &[u8], info: &[u8], okm_out: &mut [u8]) -> Result<(), CryptoError> {
218 if okm_out.is_empty() {
219 return Err(CryptoError::BadInput);
220 }
221 let hk = Hkdf::<sha2::Sha512>::from_prk(prk).map_err(|_| CryptoError::InvalidKey)?;
222 hk.expand(info, okm_out)
223 .map_err(|_| CryptoError::Internal("HKDF-SHA-512 expand failed (output too long)"))?;
224 Ok(())
225}
226
227#[must_use = "HKDF derive result must be checked"]
239pub fn hkdf_sha256_derive_to_vec(
240 ikm: &[u8],
241 salt: &[u8],
242 info: &[u8],
243 len: usize,
244) -> Result<Vec<u8>, CryptoError> {
245 if len == 0 {
246 return Err(CryptoError::BadInput);
247 }
248 let mut out = vec![0u8; len];
249 HkdfSha256.derive(ikm, salt, info, &mut out)?;
250 Ok(out)
251}
252
253#[must_use = "HKDF derive result must be checked"]
260pub fn hkdf_sha384_derive_to_vec(
261 ikm: &[u8],
262 salt: &[u8],
263 info: &[u8],
264 len: usize,
265) -> Result<Vec<u8>, CryptoError> {
266 if len == 0 {
267 return Err(CryptoError::BadInput);
268 }
269 let mut out = vec![0u8; len];
270 HkdfSha384.derive(ikm, salt, info, &mut out)?;
271 Ok(out)
272}
273
274#[must_use = "HKDF derive result must be checked"]
281pub fn hkdf_sha512_derive_to_vec(
282 ikm: &[u8],
283 salt: &[u8],
284 info: &[u8],
285 len: usize,
286) -> Result<Vec<u8>, CryptoError> {
287 if len == 0 {
288 return Err(CryptoError::BadInput);
289 }
290 let mut out = vec![0u8; len];
291 HkdfSha512.derive(ikm, salt, info, &mut out)?;
292 Ok(out)
293}
294
295#[must_use = "generated salt result must be checked"]
305pub fn generate_salt_16() -> Result<[u8; 16], CryptoError> {
306 let bytes = oxicrypto_rand::random_bytes(16)?;
307 let mut out = [0u8; 16];
308 out.copy_from_slice(&bytes);
309 Ok(out)
310}
311
312#[must_use = "generated salt result must be checked"]
320pub fn generate_salt_32() -> Result<[u8; 32], CryptoError> {
321 let bytes = oxicrypto_rand::random_bytes(32)?;
322 let mut out = [0u8; 32];
323 out.copy_from_slice(&bytes);
324 Ok(out)
325}
326
327#[must_use = "password verification result must be checked"]
356pub fn verify_password<H>(
357 hasher: &H,
358 password: &[u8],
359 salt: &[u8],
360 expected: &[u8],
361) -> Result<(), CryptoError>
362where
363 H: PasswordHash,
364{
365 if expected.is_empty() {
366 return Err(CryptoError::BadInput);
367 }
368
369 let mut computed = vec![0u8; expected.len()];
373
374 struct NullParams;
376 impl oxicrypto_core::PasswordHashParams for NullParams {
377 fn memory_cost(&self) -> Option<u32> {
378 None
379 }
380 fn time_cost(&self) -> Option<u32> {
381 None
382 }
383 fn parallelism(&self) -> Option<u32> {
384 None
385 }
386 }
387
388 hasher.hash_password(password, salt, &NullParams, &mut computed)?;
389
390 let ok: bool = computed.ct_eq(expected).into();
392 if ok {
393 Ok(())
394 } else {
395 Err(CryptoError::InvalidTag)
396 }
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402
403 fn hex_decode(s: &str) -> Vec<u8> {
404 (0..s.len())
405 .step_by(2)
406 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
407 .collect()
408 }
409
410 #[test]
418 fn hkdf_sha256_rfc5869_tc1() {
419 let ikm = hex_decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
420 let salt = hex_decode("000102030405060708090a0b0c");
421 let info = hex_decode("f0f1f2f3f4f5f6f7f8f9");
422 let expected = hex_decode(
423 "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865",
424 );
425
426 let kdf = HkdfSha256;
427 let mut okm = vec![0u8; 42];
428 kdf.derive(&ikm, &salt, &info, &mut okm)
429 .expect("HKDF-SHA-256 RFC5869 TC1 failed");
430 assert_eq!(okm, expected, "HKDF-SHA-256 RFC5869 TC1 mismatch");
431 }
432
433 #[test]
434 fn hkdf_sha256_empty_salt() {
435 let kdf = HkdfSha256;
437 let mut okm = [0u8; 32];
438 kdf.derive(b"input key material", b"", b"info", &mut okm)
439 .expect("HKDF with empty salt failed");
440 assert_ne!(okm, [0u8; 32]);
441 }
442
443 #[test]
444 fn hkdf_sha512_round_trip() {
445 let kdf = HkdfSha512;
446 let mut okm1 = [0u8; 64];
447 let mut okm2 = [0u8; 64];
448 kdf.derive(b"secret", b"salt", b"info", &mut okm1).unwrap();
449 kdf.derive(b"secret", b"salt", b"info", &mut okm2).unwrap();
450 assert_eq!(okm1, okm2, "HKDF-SHA-512 must be deterministic");
451 assert_ne!(okm1, [0u8; 64]);
452 }
453
454 #[test]
455 fn hkdf_empty_output_errors() {
456 let kdf = HkdfSha256;
457 let result = kdf.derive(b"ikm", b"salt", b"info", &mut []);
458 assert_eq!(result, Err(CryptoError::BadInput));
459 }
460
461 #[test]
464 fn hkdf_sha384_round_trip() {
465 let kdf = HkdfSha384;
466 let mut okm1 = [0u8; 48];
467 let mut okm2 = [0u8; 48];
468 kdf.derive(b"secret", b"salt", b"info", &mut okm1)
469 .expect("derive 1 failed");
470 kdf.derive(b"secret", b"salt", b"info", &mut okm2)
471 .expect("derive 2 failed");
472 assert_eq!(okm1, okm2, "HKDF-SHA-384 must be deterministic");
473 assert_ne!(okm1, [0u8; 48]);
474 }
475
476 #[test]
477 fn hkdf_sha384_empty_output_errors() {
478 let kdf = HkdfSha384;
479 let result = kdf.derive(b"ikm", b"salt", b"info", &mut []);
480 assert_eq!(result, Err(CryptoError::BadInput));
481 }
482
483 #[test]
486 fn hkdf_sha256_extract_expand_equivalent() {
487 let ikm = b"input key material";
489 let salt = b"salt value";
490 let info = b"info";
491
492 let kdf = HkdfSha256;
494 let mut okm_full = [0u8; 42];
495 kdf.derive(ikm, salt, info, &mut okm_full)
496 .expect("full derive failed");
497
498 let prk = hkdf_sha256_extract(salt, ikm);
500 let mut okm_sep = [0u8; 42];
501 hkdf_sha256_expand(&prk, info, &mut okm_sep).expect("expand failed");
502
503 assert_eq!(okm_full, okm_sep, "Extract+Expand must equal full derive");
504 }
505
506 #[test]
507 fn hkdf_sha384_extract_expand_round_trip() {
508 let prk = hkdf_sha384_extract(b"salt", b"ikm");
509 assert_eq!(prk.len(), 48);
510 let mut okm = [0u8; 32];
511 hkdf_sha384_expand(&prk, b"info", &mut okm).expect("expand failed");
512 assert_ne!(okm, [0u8; 32]);
513 }
514
515 #[test]
516 fn hkdf_sha512_extract_expand_round_trip() {
517 let prk = hkdf_sha512_extract(b"salt", b"ikm");
518 assert_eq!(prk.len(), 64);
519 let mut okm = [0u8; 64];
520 hkdf_sha512_expand(&prk, b"info", &mut okm).expect("expand failed");
521 assert_ne!(okm, [0u8; 64]);
522 }
523
524 #[test]
525 fn hkdf_expand_empty_output_errors() {
526 let prk = hkdf_sha256_extract(b"salt", b"ikm");
527 let result = hkdf_sha256_expand(&prk, b"info", &mut []);
528 assert_eq!(result, Err(CryptoError::BadInput));
529 }
530
531 const VERIFY_SALT: &[u8] = b"0123456789abcdef"; #[test]
536 fn verify_password_argon2id_correct() {
537 let hasher = Argon2idHasher::new(Argon2Params::TEST_PARAMS);
538 let mut expected = [0u8; 32];
539 hasher
540 .hash_password(b"password", VERIFY_SALT, &hasher.params, &mut expected)
541 .expect("hash");
542 verify_password(&hasher, b"password", VERIFY_SALT, &expected)
543 .expect("correct password must pass");
544 }
545
546 #[test]
547 fn verify_password_argon2id_wrong_password() {
548 let hasher = Argon2idHasher::new(Argon2Params::TEST_PARAMS);
549 let mut expected = [0u8; 32];
550 hasher
551 .hash_password(b"password", VERIFY_SALT, &hasher.params, &mut expected)
552 .expect("hash");
553 let result = verify_password(&hasher, b"wrongpassword", VERIFY_SALT, &expected);
554 assert_eq!(result, Err(CryptoError::InvalidTag));
555 }
556
557 #[test]
558 fn verify_password_pbkdf2_correct() {
559 let hasher = Pbkdf2Sha256Hasher::new(1_000);
560 let mut expected = [0u8; 32];
561 hasher
562 .hash_password(b"mypassword", VERIFY_SALT, &hasher.params(), &mut expected)
563 .expect("hash");
564 verify_password(&hasher, b"mypassword", VERIFY_SALT, &expected)
565 .expect("correct password must pass");
566 }
567
568 #[test]
569 fn verify_password_pbkdf2_wrong_password() {
570 let hasher = Pbkdf2Sha256Hasher::new(1_000);
571 let mut expected = [0u8; 32];
572 hasher
573 .hash_password(b"mypassword", VERIFY_SALT, &hasher.params(), &mut expected)
574 .expect("hash");
575 let result = verify_password(&hasher, b"notmypassword", VERIFY_SALT, &expected);
576 assert_eq!(result, Err(CryptoError::InvalidTag));
577 }
578
579 #[test]
580 fn verify_password_empty_expected_errors() {
581 let hasher = Pbkdf2Sha256Hasher::new(1_000);
582 let result = verify_password(&hasher, b"password", VERIFY_SALT, &[]);
583 assert_eq!(result, Err(CryptoError::BadInput));
584 }
585}