dryoc/classic/
crypto_kdf.rs

1//! # Key derivation function
2//!
3//! Implements libsodium's key derivation functions (`crypto_kdf_*`).
4//!
5//! For details, refer to [libsodium docs](https://doc.libsodium.org/key_derivation).
6//!
7//! # Classic API example
8//!
9//! ```
10//! use base64::Engine as _;
11//! use base64::engine::general_purpose;
12//! use dryoc::classic::crypto_kdf::*;
13//!
14//! // Generate a random main key
15//! let main_key = crypto_kdf_keygen();
16//! // Provide 8 bytes of context data, can be any data
17//! let context = b"hello123";
18//!
19//! // Derive 20 subkeys
20//! for i in 0..20 {
21//!     let mut key = Key::default();
22//!     crypto_kdf_derive_from_key(&mut key, i, context, &main_key).expect("kdf failed");
23//!     println!("Subkey {}: {}", i, general_purpose::STANDARD.encode(&key));
24//! }
25//! ```
26
27use crate::blake2b;
28use crate::constants::{
29    CRYPTO_GENERICHASH_BLAKE2B_PERSONALBYTES, CRYPTO_GENERICHASH_BLAKE2B_SALTBYTES,
30    CRYPTO_KDF_BLAKE2B_BYTES_MAX, CRYPTO_KDF_BLAKE2B_BYTES_MIN, CRYPTO_KDF_CONTEXTBYTES,
31    CRYPTO_KDF_KEYBYTES,
32};
33use crate::error::Error;
34
35/// Key type for the main key used for deriving subkeys.
36pub type Key = [u8; CRYPTO_KDF_KEYBYTES];
37/// Context for key derivation.
38pub type Context = [u8; CRYPTO_KDF_CONTEXTBYTES];
39
40/// Generates a random key, suitable for use as a main key with
41/// [`crypto_kdf_derive_from_key`].
42pub fn crypto_kdf_keygen() -> Key {
43    use crate::rng::copy_randombytes;
44    let mut key = Key::default();
45    copy_randombytes(&mut key);
46    key
47}
48
49/// Derives `subkey` from `main_key`, using `context` and `subkey_id` such that
50/// `subkey` will always be the same for the given set of inputs, but `main_key`
51/// cannot be derived from `subkey`.
52pub fn crypto_kdf_derive_from_key(
53    subkey: &mut [u8],
54    subkey_id: u64,
55    context: &Context,
56    main_key: &Key,
57) -> Result<(), Error> {
58    if subkey.len() < CRYPTO_KDF_BLAKE2B_BYTES_MIN || subkey.len() > CRYPTO_KDF_BLAKE2B_BYTES_MAX {
59        Err(dryoc_error!(format!(
60            "invalid subkey length {}, should be at least {} and no more than {}",
61            subkey.len(),
62            CRYPTO_KDF_BLAKE2B_BYTES_MIN,
63            CRYPTO_KDF_BLAKE2B_BYTES_MAX
64        )))
65    } else {
66        let mut ctx_padded = [0u8; CRYPTO_GENERICHASH_BLAKE2B_PERSONALBYTES];
67        let mut salt = [0u8; CRYPTO_GENERICHASH_BLAKE2B_SALTBYTES];
68
69        ctx_padded[..CRYPTO_KDF_CONTEXTBYTES].copy_from_slice(context);
70        salt[..8].copy_from_slice(&subkey_id.to_le_bytes());
71
72        let state = blake2b::State::init(
73            CRYPTO_KDF_KEYBYTES as u8,
74            Some(main_key),
75            Some(&salt),
76            Some(&ctx_padded),
77        )?;
78        state.finalize(subkey)
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn test_derive_key() {
88        use sodiumoxide::crypto::{kdf, secretbox};
89        let main_key = crypto_kdf_keygen();
90        let context = b"hello123";
91
92        for i in 0..20 {
93            let mut key = Key::default();
94            crypto_kdf_derive_from_key(&mut key, i, context, &main_key).expect("kdf failed");
95
96            let mut so_key = secretbox::Key([0; secretbox::KEYBYTES]);
97            kdf::derive_from_key(
98                &mut so_key.0[..],
99                i,
100                *context,
101                &kdf::blake2b::Key::from_slice(&main_key).expect("key failed"),
102            )
103            .expect("so kdf failed");
104
105            assert_eq!(so_key.0, key);
106        }
107    }
108}