Skip to main content

dryoc/classic/
crypto_kdf.rs

1//! # Key derivation function
2//!
3//! Implements libsodium's key derivation functions (`crypto_kdf_*`).
4//!
5//! The Blake2b `crypto_kdf_*` functions derive bounded subkeys from a random
6//! main key and an 8-byte application context. The HKDF functions derive output
7//! keying material from existing input keying material, using an optional salt
8//! and a public context string.
9//!
10//! Use `crypto_kdf_derive_from_key` when you have one random main key and need
11//! numbered subkeys. Use the HKDF functions when you already have keying
12//! material, such as a key-exchange result, and need to turn it into one or
13//! more purpose-specific keys.
14//!
15//! For details, refer to [libsodium docs](https://doc.libsodium.org/key_derivation).
16//!
17//! # Classic API example
18//!
19//! ```
20//! use dryoc::classic::crypto_kdf::*;
21//! use dryoc::constants::CRYPTO_KDF_KEYBYTES;
22//!
23//! // Generate a random main key
24//! let main_key = crypto_kdf_keygen();
25//! // Provide exactly 8 bytes of public context data
26//! let context = b"WTCHKEYS";
27//!
28//! // Derive 20 subkeys
29//! for i in 0..20 {
30//!     let mut key = Key::default();
31//!     crypto_kdf_derive_from_key(&mut key, i, context, &main_key).expect("kdf failed");
32//!     assert_eq!(key.len(), CRYPTO_KDF_KEYBYTES);
33//! }
34//! ```
35//!
36//! # HKDF-SHA-256 example
37//!
38//! ```
39//! use dryoc::classic::crypto_kdf::*;
40//!
41//! let mut prk = HkdfSha256Key::default();
42//! crypto_kdf_hkdf_sha256_extract(&mut prk, Some(b"salt"), b"Some rise by sin");
43//!
44//! let mut output = [0u8; 42];
45//! crypto_kdf_hkdf_sha256_expand(&mut output, b"encryption key", &prk).expect("expand failed");
46//! ```
47//!
48//! The HKDF extract step can also be fed incrementally. This is useful when the
49//! input keying material arrives in pieces:
50//!
51//! ```
52//! use dryoc::classic::crypto_kdf::*;
53//!
54//! let mut state = crypto_kdf_hkdf_sha256_extract_init(Some(b"salt"));
55//! crypto_kdf_hkdf_sha256_extract_update(&mut state, b"Some rise ");
56//! crypto_kdf_hkdf_sha256_extract_update(&mut state, b"by sin");
57//!
58//! let mut prk = HkdfSha256Key::default();
59//! crypto_kdf_hkdf_sha256_extract_final(state, &mut prk);
60//! ```
61//!
62//! # HKDF-SHA-512 example
63//!
64//! ```
65//! use dryoc::classic::crypto_kdf::*;
66//!
67//! let mut prk: HkdfSha512Key = [0u8; 64];
68//! crypto_kdf_hkdf_sha512_extract(&mut prk, None, b"and some by virtue fall");
69//!
70//! let mut output = [0u8; 64];
71//! crypto_kdf_hkdf_sha512_expand(&mut output, b"authentication key", &prk).expect("expand failed");
72//! ```
73
74use crate::blake2b;
75use crate::classic::crypto_auth_hmac_impl::{
76    HmacHash, HmacState, hmac_final, hmac_init, hmac_keygen, hmac_update,
77};
78use crate::constants::{
79    CRYPTO_GENERICHASH_BLAKE2B_PERSONALBYTES, CRYPTO_GENERICHASH_BLAKE2B_SALTBYTES,
80    CRYPTO_KDF_BLAKE2B_BYTES_MAX, CRYPTO_KDF_BLAKE2B_BYTES_MIN, CRYPTO_KDF_CONTEXTBYTES,
81    CRYPTO_KDF_HKDF_SHA256_BYTES_MAX, CRYPTO_KDF_HKDF_SHA256_BYTES_MIN,
82    CRYPTO_KDF_HKDF_SHA256_KEYBYTES, CRYPTO_KDF_HKDF_SHA512_BYTES_MAX,
83    CRYPTO_KDF_HKDF_SHA512_BYTES_MIN, CRYPTO_KDF_HKDF_SHA512_KEYBYTES, CRYPTO_KDF_KEYBYTES,
84};
85use crate::error::Error;
86use crate::rng::copy_randombytes;
87use crate::sha256::Sha256;
88use crate::sha512::Sha512;
89use crate::utils::zeroize_bytes;
90
91/// Key type for the main key used for deriving subkeys.
92pub type Key = [u8; CRYPTO_KDF_KEYBYTES];
93/// Context for key derivation.
94pub type Context = [u8; CRYPTO_KDF_CONTEXTBYTES];
95/// Pseudorandom key for HKDF-SHA-256.
96pub type HkdfSha256Key = [u8; CRYPTO_KDF_HKDF_SHA256_KEYBYTES];
97/// Pseudorandom key for HKDF-SHA-512.
98pub type HkdfSha512Key = [u8; CRYPTO_KDF_HKDF_SHA512_KEYBYTES];
99
100/// Internal state for incremental HKDF-SHA-256 extract.
101pub struct HkdfSha256State(HmacState<Sha256, 64, CRYPTO_KDF_HKDF_SHA256_KEYBYTES>);
102
103/// Internal state for incremental HKDF-SHA-512 extract.
104pub struct HkdfSha512State(HmacState<Sha512, 128, CRYPTO_KDF_HKDF_SHA512_KEYBYTES>);
105
106/// Generates a random key, suitable for use as a main key with
107/// [`crypto_kdf_derive_from_key`].
108#[must_use]
109pub fn crypto_kdf_keygen() -> Key {
110    let mut key = Key::default();
111    copy_randombytes(&mut key);
112    key
113}
114
115/// Generates a random pseudorandom key for HKDF-SHA-256 expand.
116#[must_use]
117pub fn crypto_kdf_hkdf_sha256_keygen() -> HkdfSha256Key {
118    hmac_keygen()
119}
120
121/// Generates a random pseudorandom key for HKDF-SHA-512 expand.
122#[must_use]
123pub fn crypto_kdf_hkdf_sha512_keygen() -> HkdfSha512Key {
124    hmac_keygen()
125}
126
127/// Derives `subkey` from `main_key`, using `context` and `subkey_id` such that
128/// `subkey` will always be the same for the given set of inputs, but `main_key`
129/// cannot be derived from `subkey`.
130///
131/// # Errors
132///
133/// Returns an error if `subkey` is outside the supported length range.
134pub fn crypto_kdf_derive_from_key(
135    subkey: &mut [u8],
136    subkey_id: u64,
137    context: &Context,
138    main_key: &Key,
139) -> Result<(), Error> {
140    validate_subkey_length(subkey.len())?;
141
142    let mut ctx_padded = [0u8; CRYPTO_GENERICHASH_BLAKE2B_PERSONALBYTES];
143    let mut salt = [0u8; CRYPTO_GENERICHASH_BLAKE2B_SALTBYTES];
144
145    ctx_padded[..CRYPTO_KDF_CONTEXTBYTES].copy_from_slice(context);
146    salt[..8].copy_from_slice(&subkey_id.to_le_bytes());
147
148    blake2b::hash_key_only(subkey, main_key, &salt, &ctx_padded)
149}
150
151pub(crate) fn validate_subkey_length(length: usize) -> Result<(), Error> {
152    if !(CRYPTO_KDF_BLAKE2B_BYTES_MIN..=CRYPTO_KDF_BLAKE2B_BYTES_MAX).contains(&length) {
153        Err(length_error!(
154            crate::ErrorContext::Subkey,
155            length,
156            range CRYPTO_KDF_BLAKE2B_BYTES_MIN,
157            CRYPTO_KDF_BLAKE2B_BYTES_MAX
158        ))
159    } else {
160        Ok(())
161    }
162}
163
164fn validate_hkdf_output_len(
165    output_len: usize,
166    min_len: usize,
167    max_len: usize,
168) -> Result<(), Error> {
169    if output_len < min_len || output_len > max_len {
170        Err(length_error!(crate::ErrorContext::Output, output_len, range min_len, max_len))
171    } else {
172        Ok(())
173    }
174}
175
176/// Creates an HKDF-SHA-256 pseudorandom key from input keying material.
177pub fn crypto_kdf_hkdf_sha256_extract(prk: &mut HkdfSha256Key, salt: Option<&[u8]>, ikm: &[u8]) {
178    let mut state = crypto_kdf_hkdf_sha256_extract_init(salt);
179    crypto_kdf_hkdf_sha256_extract_update(&mut state, ikm);
180    crypto_kdf_hkdf_sha256_extract_final(state, prk);
181}
182
183/// Initializes incremental HKDF-SHA-256 extract.
184#[must_use]
185pub fn crypto_kdf_hkdf_sha256_extract_init(salt: Option<&[u8]>) -> HkdfSha256State {
186    HkdfSha256State(hmac_init::<Sha256, 64, CRYPTO_KDF_HKDF_SHA256_KEYBYTES>(
187        salt.unwrap_or(&[]),
188    ))
189}
190
191/// Updates incremental HKDF-SHA-256 extract with input keying material.
192pub fn crypto_kdf_hkdf_sha256_extract_update(state: &mut HkdfSha256State, ikm: &[u8]) {
193    hmac_update(&mut state.0, ikm);
194}
195
196/// Finalizes incremental HKDF-SHA-256 extract and writes the pseudorandom key.
197pub fn crypto_kdf_hkdf_sha256_extract_final(state: HkdfSha256State, prk: &mut HkdfSha256Key) {
198    hmac_final(state.0, prk);
199}
200
201/// Expands an HKDF-SHA-256 pseudorandom key into output keying material.
202///
203/// # Errors
204///
205/// Returns an error if `output` is outside the supported HKDF-SHA-256 output
206/// length range.
207pub fn crypto_kdf_hkdf_sha256_expand(
208    output: &mut [u8],
209    context: &[u8],
210    prk: &HkdfSha256Key,
211) -> Result<(), Error> {
212    hkdf_expand::<Sha256, 64, CRYPTO_KDF_HKDF_SHA256_KEYBYTES>(
213        output,
214        context,
215        prk,
216        CRYPTO_KDF_HKDF_SHA256_BYTES_MIN,
217        CRYPTO_KDF_HKDF_SHA256_BYTES_MAX,
218    )
219}
220
221/// Creates an HKDF-SHA-512 pseudorandom key from input keying material.
222pub fn crypto_kdf_hkdf_sha512_extract(prk: &mut HkdfSha512Key, salt: Option<&[u8]>, ikm: &[u8]) {
223    let mut state = crypto_kdf_hkdf_sha512_extract_init(salt);
224    crypto_kdf_hkdf_sha512_extract_update(&mut state, ikm);
225    crypto_kdf_hkdf_sha512_extract_final(state, prk);
226}
227
228/// Initializes incremental HKDF-SHA-512 extract.
229#[must_use]
230pub fn crypto_kdf_hkdf_sha512_extract_init(salt: Option<&[u8]>) -> HkdfSha512State {
231    HkdfSha512State(hmac_init::<Sha512, 128, CRYPTO_KDF_HKDF_SHA512_KEYBYTES>(
232        salt.unwrap_or(&[]),
233    ))
234}
235
236/// Updates incremental HKDF-SHA-512 extract with input keying material.
237pub fn crypto_kdf_hkdf_sha512_extract_update(state: &mut HkdfSha512State, ikm: &[u8]) {
238    hmac_update(&mut state.0, ikm);
239}
240
241/// Finalizes incremental HKDF-SHA-512 extract and writes the pseudorandom key.
242pub fn crypto_kdf_hkdf_sha512_extract_final(state: HkdfSha512State, prk: &mut HkdfSha512Key) {
243    hmac_final(state.0, prk);
244}
245
246/// Expands an HKDF-SHA-512 pseudorandom key into output keying material.
247///
248/// # Errors
249///
250/// Returns an error if `output` is outside the supported HKDF-SHA-512 output
251/// length range.
252pub fn crypto_kdf_hkdf_sha512_expand(
253    output: &mut [u8],
254    context: &[u8],
255    prk: &HkdfSha512Key,
256) -> Result<(), Error> {
257    hkdf_expand::<Sha512, 128, CRYPTO_KDF_HKDF_SHA512_KEYBYTES>(
258        output,
259        context,
260        prk,
261        CRYPTO_KDF_HKDF_SHA512_BYTES_MIN,
262        CRYPTO_KDF_HKDF_SHA512_BYTES_MAX,
263    )
264}
265
266fn hkdf_expand<H, const BLOCK_BYTES: usize, const OUT_BYTES: usize>(
267    output: &mut [u8],
268    context: &[u8],
269    prk: &[u8; OUT_BYTES],
270    min_len: usize,
271    max_len: usize,
272) -> Result<(), Error>
273where
274    H: HmacHash<OUT_BYTES>,
275{
276    validate_hkdf_output_len(output.len(), min_len, max_len)?;
277
278    // The keyed inner/outer states are the same for every block, so derive
279    // them once and clone per block instead of rehashing the pads.
280    let keyed = hmac_init::<H, BLOCK_BYTES, OUT_BYTES>(prk);
281    let mut previous = [0u8; OUT_BYTES];
282    let mut offset = 0usize;
283    for counter in 1..=255u8 {
284        if offset == output.len() {
285            break;
286        }
287
288        let mut state = keyed.clone();
289        if counter > 1 {
290            hmac_update(&mut state, &previous);
291        }
292        hmac_update(&mut state, context);
293        hmac_update(&mut state, &[counter]);
294        hmac_final(state, &mut previous);
295
296        let chunk_len = (output.len() - offset).min(previous.len());
297        output[offset..offset + chunk_len].copy_from_slice(&previous[..chunk_len]);
298        offset += chunk_len;
299    }
300
301    zeroize_bytes(&mut previous);
302
303    Ok(())
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309    use crate::test_prelude::*;
310
311    #[test]
312    fn test_crypto_kdf_rejects_invalid_subkey_lengths() {
313        let context = Context::default();
314        let key = Key::default();
315
316        for length in [
317            CRYPTO_KDF_BLAKE2B_BYTES_MIN - 1,
318            CRYPTO_KDF_BLAKE2B_BYTES_MAX + 1,
319        ] {
320            let mut subkey = vec![0u8; length];
321            assert!(matches!(
322                crypto_kdf_derive_from_key(&mut subkey, 0, &context, &key),
323                Err(Error::InvalidLength {
324                    context: crate::ErrorContext::Subkey,
325                    actual,
326                    constraint: crate::LengthConstraint::Between {
327                        min: CRYPTO_KDF_BLAKE2B_BYTES_MIN,
328                        max: CRYPTO_KDF_BLAKE2B_BYTES_MAX,
329                    },
330                }) if actual == length
331            ));
332        }
333    }
334
335    #[cfg(dryoc_native_tests)]
336    #[test]
337    fn test_crypto_kdf_boundary_parameters_match_libsodium() {
338        crate::native_test_util::init();
339        let key: Key = core::array::from_fn(|i| (i as u8).wrapping_mul(37).wrapping_add(11));
340        for context in [
341            [0u8; CRYPTO_KDF_CONTEXTBYTES],
342            [0xff; CRYPTO_KDF_CONTEXTBYTES],
343        ] {
344            for subkey_id in [0, 1, u64::MAX] {
345                for length in [16usize, 64] {
346                    let mut ours = vec![0u8; length];
347                    let mut sodium = vec![0u8; length];
348                    crypto_kdf_derive_from_key(&mut ours, subkey_id, &context, &key)
349                        .expect("dryoc derivation failed");
350                    let rc = unsafe {
351                        libsodium_sys::crypto_kdf_derive_from_key(
352                            sodium.as_mut_ptr(),
353                            sodium.len(),
354                            subkey_id,
355                            context.as_ptr().cast(),
356                            key.as_ptr(),
357                        )
358                    };
359                    assert_eq!(rc, 0);
360                    assert_eq!(
361                        ours, sodium,
362                        "context {context:x?}, id {subkey_id}, length {length}"
363                    );
364                }
365            }
366        }
367    }
368
369    fn bytes_in_range(start: u8, end_inclusive: u8) -> Vec<u8> {
370        (start..=end_inclusive).collect()
371    }
372
373    fn assert_hkdf_sha256(
374        ikm: &[u8],
375        salt: Option<&[u8]>,
376        info: &[u8],
377        output_len: usize,
378        expected_prk_hex: &str,
379        expected_okm_hex: &str,
380    ) {
381        let expected_prk = hex::decode(expected_prk_hex).expect("hex failed");
382        let expected_okm = hex::decode(expected_okm_hex).expect("hex failed");
383        let mut prk = HkdfSha256Key::default();
384        crypto_kdf_hkdf_sha256_extract(&mut prk, salt, ikm);
385        assert_eq!(prk.as_slice(), expected_prk.as_slice());
386
387        let mut okm = vec![0u8; output_len];
388        crypto_kdf_hkdf_sha256_expand(&mut okm, info, &prk).expect("expand failed");
389        assert_eq!(okm, expected_okm);
390    }
391
392    fn assert_hkdf_sha512(
393        ikm: &[u8],
394        salt: Option<&[u8]>,
395        info: &[u8],
396        output_len: usize,
397        expected_prk_hex: &str,
398        expected_okm_hex: &str,
399    ) {
400        let expected_prk = hex::decode(expected_prk_hex).expect("hex failed");
401        let expected_okm = hex::decode(expected_okm_hex).expect("hex failed");
402        let mut prk = [0u8; CRYPTO_KDF_HKDF_SHA512_KEYBYTES];
403        crypto_kdf_hkdf_sha512_extract(&mut prk, salt, ikm);
404        assert_eq!(prk.as_slice(), expected_prk.as_slice());
405
406        let mut okm = vec![0u8; output_len];
407        crypto_kdf_hkdf_sha512_expand(&mut okm, info, &prk).expect("expand failed");
408        assert_eq!(okm, expected_okm);
409    }
410
411    #[test]
412    fn test_hkdf_sha256_rfc5869_case_1() {
413        let ikm = [0x0bu8; 22];
414        let salt = hex::decode("000102030405060708090a0b0c").expect("hex failed");
415        let info = hex::decode("f0f1f2f3f4f5f6f7f8f9").expect("hex failed");
416        assert_hkdf_sha256(
417            &ikm,
418            Some(&salt),
419            &info,
420            42,
421            "077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5",
422            concat!(
423                "3cb25f25faacd57a90434f64d0362f2a",
424                "2d2d0a90cf1a5a4c5db02d56ecc4c5bf",
425                "34007208d5b887185865",
426            ),
427        );
428    }
429
430    #[test]
431    fn test_hkdf_sha256_rfc5869_case_2() {
432        let ikm = bytes_in_range(0x00, 0x4f);
433        let salt = bytes_in_range(0x60, 0xaf);
434        let info = bytes_in_range(0xb0, 0xff);
435        assert_hkdf_sha256(
436            &ikm,
437            Some(&salt),
438            &info,
439            82,
440            "06a6b88c5853361a06104c9ceb35b45cef760014904671014a193f40c15fc244",
441            concat!(
442                "b11e398dc80327a1c8e7f78c596a4934",
443                "4f012eda2d4efad8a050cc4c19afa97c",
444                "59045a99cac7827271cb41c65e590e09",
445                "da3275600c2f09b8367793a9aca3db71",
446                "cc30c58179ec3e87c14c01d5c1f3434f",
447                "1d87",
448            ),
449        );
450    }
451
452    #[test]
453    fn test_hkdf_sha256_rfc5869_case_3_no_salt_or_info() {
454        let ikm = [0x0bu8; 22];
455        assert_hkdf_sha256(
456            &ikm,
457            None,
458            &[],
459            42,
460            "19ef24a32c717b167f33a91d6f648bdf96596776afdb6377ac434c1c293ccb04",
461            concat!(
462                "8da4e775a563c18f715f802a063c5a31",
463                "b8a11f5c5ee1879ec3454e5f3c738d2d",
464                "9d201395faa4b61a96c8",
465            ),
466        );
467    }
468
469    /// RFC 5869 A.1 inputs with SHA-512: the OpenSSL-derived vector in
470    /// `test/recipes/30-test_evp_data/evpkdf_hkdf.txt` (RFC 5869 itself has no
471    /// SHA-512 answers).
472    #[test]
473    fn test_hkdf_sha512_a1_inputs_openssl_vector() {
474        let ikm = [0x0bu8; 22];
475        let salt = hex::decode("000102030405060708090a0b0c").expect("hex failed");
476        let info = hex::decode("f0f1f2f3f4f5f6f7f8f9").expect("hex failed");
477        assert_hkdf_sha512(
478            &ikm,
479            Some(&salt),
480            &info,
481            42,
482            "665799823737ded04a88e47e54a5890bb2c3d247c7a4254a8e61350723590a26c36238127d8661b88cf80ef802d57e2f7cebcf1e00e083848be19929c61b4237",
483            "832390086cda71fb47625bb5ceb168e4c8e26a1a16ed34d9fc7fe92c1481579338da362cb8d9f925d7cb",
484        );
485    }
486
487    #[test]
488    fn test_hkdf_sha512_long_inputs_openssl_vector() {
489        let ikm = bytes_in_range(0x00, 0x4f);
490        let salt = bytes_in_range(0x60, 0xaf);
491        let info = bytes_in_range(0xb0, 0xff);
492        assert_hkdf_sha512(
493            &ikm,
494            Some(&salt),
495            &info,
496            82,
497            concat!(
498                "35672542907d4e142c00e84499e74e1d",
499                "e08be86535f924e022804ad775dde27e",
500                "c86cd1e5b7d178c74489bdbeb30712be",
501                "b82d4f97416c5a94ea81ebdf3e629e4a",
502            ),
503            concat!(
504                "ce6c97192805b346e6161e821ed16567",
505                "3b84f400a2b514b2fe23d84cd189ddf1",
506                "b695b48cbd1c8388441137b3ce28f16a",
507                "a64ba33ba466b24df6cfcb021ecff235",
508                "f6a2056ce3af1de44d572097a8505d",
509                "9e7a93",
510            ),
511        );
512    }
513
514    #[test]
515    fn test_hkdf_sha512_no_salt_or_info_openssl_vector() {
516        let ikm = [0x0bu8; 22];
517        assert_hkdf_sha512(
518            &ikm,
519            None,
520            &[],
521            42,
522            concat!(
523                "fd200c4987ac491313bd4a2a13287121",
524                "247239e11c9ef82802044b66ef357e5b",
525                "194498d0682611382348572a7b1611de",
526                "54764094286320578a863f36562b0df6",
527            ),
528            concat!(
529                "f5fa02b18298a72a8c23898a8703472c",
530                "6eb179dc204c03425c970e3b164bf90f",
531                "ff22d04836d0e2343bac",
532            ),
533        );
534    }
535
536    #[test]
537    fn test_hkdf_output_length_limits() {
538        let prk256 = HkdfSha256Key::default();
539        let mut okm256 = vec![0u8; CRYPTO_KDF_HKDF_SHA256_BYTES_MAX + 1];
540        crypto_kdf_hkdf_sha256_expand(&mut okm256, b"context", &prk256)
541            .expect_err("oversized output should fail");
542
543        let prk512 = [0u8; CRYPTO_KDF_HKDF_SHA512_KEYBYTES];
544        let mut okm512 = vec![0u8; CRYPTO_KDF_HKDF_SHA512_BYTES_MAX + 1];
545        crypto_kdf_hkdf_sha512_expand(&mut okm512, b"context", &prk512)
546            .expect_err("oversized output should fail");
547    }
548
549    #[test]
550    fn test_hkdf_empty_and_max_output_lengths() {
551        let prk256 = HkdfSha256Key::default();
552        let mut empty256 = [];
553        crypto_kdf_hkdf_sha256_expand(&mut empty256, b"context", &prk256)
554            .expect("empty SHA-256 output should be allowed");
555        let mut max256 = vec![0u8; CRYPTO_KDF_HKDF_SHA256_BYTES_MAX];
556        crypto_kdf_hkdf_sha256_expand(&mut max256, b"context", &prk256)
557            .expect("max SHA-256 output should be allowed");
558
559        let prk512 = [0u8; CRYPTO_KDF_HKDF_SHA512_KEYBYTES];
560        let mut empty512 = [];
561        crypto_kdf_hkdf_sha512_expand(&mut empty512, b"context", &prk512)
562            .expect("empty SHA-512 output should be allowed");
563        let mut max512 = vec![0u8; CRYPTO_KDF_HKDF_SHA512_BYTES_MAX];
564        crypto_kdf_hkdf_sha512_expand(&mut max512, b"context", &prk512)
565            .expect("max SHA-512 output should be allowed");
566    }
567
568    #[test]
569    fn test_hkdf_digest_boundary_output_lengths() {
570        let prk256 = HkdfSha256Key::default();
571        for len in [31, 32, 33] {
572            let mut okm = vec![0u8; len];
573            crypto_kdf_hkdf_sha256_expand(&mut okm, b"context", &prk256)
574                .expect("SHA-256 boundary output should be allowed");
575        }
576
577        let prk512 = [0u8; CRYPTO_KDF_HKDF_SHA512_KEYBYTES];
578        for len in [63, 64, 65] {
579            let mut okm = vec![0u8; len];
580            crypto_kdf_hkdf_sha512_expand(&mut okm, b"context", &prk512)
581                .expect("SHA-512 boundary output should be allowed");
582        }
583    }
584
585    #[test]
586    fn test_hkdf_none_and_empty_salt_match() {
587        let ikm = [0xabu8; 20];
588
589        let mut none_salt256 = HkdfSha256Key::default();
590        let mut empty_salt256 = HkdfSha256Key::default();
591        crypto_kdf_hkdf_sha256_extract(&mut none_salt256, None, &ikm);
592        crypto_kdf_hkdf_sha256_extract(&mut empty_salt256, Some(&[]), &ikm);
593        assert_eq!(none_salt256, empty_salt256);
594
595        let mut none_salt512 = [0u8; CRYPTO_KDF_HKDF_SHA512_KEYBYTES];
596        let mut empty_salt512 = [0u8; CRYPTO_KDF_HKDF_SHA512_KEYBYTES];
597        crypto_kdf_hkdf_sha512_extract(&mut none_salt512, None, &ikm);
598        crypto_kdf_hkdf_sha512_extract(&mut empty_salt512, Some(&[]), &ikm);
599        assert_eq!(none_salt512, empty_salt512);
600    }
601
602    #[test]
603    fn test_hkdf_incremental_extract_matches_one_shot() {
604        let salt = b"salt";
605        let ikm_parts = [
606            b"input ".as_slice(),
607            b"keying ".as_slice(),
608            b"material".as_slice(),
609        ];
610        let ikm = ikm_parts.concat();
611
612        let mut one_shot256 = HkdfSha256Key::default();
613        crypto_kdf_hkdf_sha256_extract(&mut one_shot256, Some(salt), &ikm);
614
615        let mut state256 = crypto_kdf_hkdf_sha256_extract_init(Some(salt));
616        for part in ikm_parts {
617            crypto_kdf_hkdf_sha256_extract_update(&mut state256, part);
618        }
619        let mut incremental256 = HkdfSha256Key::default();
620        crypto_kdf_hkdf_sha256_extract_final(state256, &mut incremental256);
621        assert_eq!(one_shot256, incremental256);
622
623        let mut one_shot512 = [0u8; CRYPTO_KDF_HKDF_SHA512_KEYBYTES];
624        crypto_kdf_hkdf_sha512_extract(&mut one_shot512, Some(salt), &ikm);
625
626        let mut state512 = crypto_kdf_hkdf_sha512_extract_init(Some(salt));
627        for part in ikm_parts {
628            crypto_kdf_hkdf_sha512_extract_update(&mut state512, part);
629        }
630        let mut incremental512 = [0u8; CRYPTO_KDF_HKDF_SHA512_KEYBYTES];
631        crypto_kdf_hkdf_sha512_extract_final(state512, &mut incremental512);
632        assert_eq!(one_shot512, incremental512);
633    }
634
635    #[cfg(dryoc_native_tests)]
636    #[test]
637    fn test_derive_key() {
638        use crate::native_test_util::kdf_blake2b_derive_from_key;
639
640        let main_key = crypto_kdf_keygen();
641        let context = b"hello123";
642
643        for i in 0..20 {
644            let mut key = Key::default();
645            crypto_kdf_derive_from_key(&mut key, i, context, &main_key).expect("kdf failed");
646
647            let so_key: [u8; 32] = kdf_blake2b_derive_from_key(i, context, &main_key);
648
649            assert_eq!(so_key, key);
650        }
651    }
652}