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