dryoc/kdf.rs
1//! # Key derivation functions
2//!
3//! [`Kdf`] implements libsodium's key derivation functions, based on the
4//! Blake2b hash function.
5//!
6//! You should use [`Kdf`] when you want to:
7//!
8//! * create many subkeys from a main key, without having to risk leaking the
9//! main key
10//! * ensure that if a subkey were to become compromised, one could not derive
11//! the main key
12//!
13//! # Rustaceous API example
14//!
15//! ```
16//! use base64::Engine as _;
17//! use base64::engine::general_purpose;
18//! use dryoc::kdf::*;
19//!
20//! // Randomly generate a main key and context, using the default stack-allocated
21//! // types
22//! let key = StackKdf::generate();
23//! let subkey_id = 0;
24//!
25//! let subkey = key
26//! .derive_subkey_to_vec(subkey_id, 32)
27//! .expect("derive failed");
28//! println!(
29//! "Subkey {}: {}",
30//! subkey_id,
31//! general_purpose::STANDARD.encode(&subkey)
32//! );
33//! ```
34//!
35//! ## Additional resources
36//!
37//! * See <https://doc.libsodium.org/key_derivation> for additional details on
38//! key derivation
39
40use std::fmt;
41
42#[cfg(feature = "serde")]
43use serde::{Deserialize, Serialize};
44use zeroize::{Zeroize, ZeroizeOnDrop};
45
46use crate::classic::crypto_kdf::{crypto_kdf_derive_from_key, validate_subkey_length};
47use crate::constants::{CRYPTO_KDF_CONTEXTBYTES, CRYPTO_KDF_KEYBYTES};
48use crate::error::Error;
49use crate::types::*;
50
51/// Stack-allocated key type alias for key derivation with [`Kdf`].
52pub type Key = StackByteArray<CRYPTO_KDF_KEYBYTES>;
53/// Stack-allocated context type alias for key derivation with [`Kdf`].
54pub type Context = StackByteArray<CRYPTO_KDF_CONTEXTBYTES>;
55
56#[cfg_attr(feature = "serde", derive(Zeroize, Clone, Serialize, Deserialize))]
57#[cfg_attr(not(feature = "serde"), derive(Zeroize, Clone))]
58/// Key derivation implementation based on Blake2b, compatible with libsodium's
59/// `crypto_kdf_*` functions.
60///
61/// The main-key type must implement [`ZeroizeOnDrop`] so keys remain
62/// self-wiping after [`Kdf::into_parts`] transfers ownership to the caller.
63pub struct Kdf<
64 Key: ByteArray<CRYPTO_KDF_KEYBYTES> + Zeroize + ZeroizeOnDrop,
65 Context: ByteArray<CRYPTO_KDF_CONTEXTBYTES> + Zeroize,
66> {
67 main_key: Key,
68 context: Context,
69}
70
71impl<
72 Key: ByteArray<CRYPTO_KDF_KEYBYTES> + Zeroize + ZeroizeOnDrop,
73 Context: ByteArray<CRYPTO_KDF_CONTEXTBYTES> + Zeroize,
74> fmt::Debug for Kdf<Key, Context>
75{
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 f.debug_struct("Kdf")
78 .field("main_key", &"[REDACTED]")
79 .field("context", &self.context.as_slice())
80 .finish()
81 }
82}
83
84/// Stack-allocated type alias for [`Kdf`]. Provided for convenience.
85pub type StackKdf = Kdf<Key, Context>;
86
87#[cfg(any(all(feature = "protected", any(unix, windows)), all(doc, not(doctest))))]
88#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
89pub mod protected {
90 //! # Protected memory type aliases for [`Kdf`]
91 //!
92 //! Protected-memory aliases for key derivation.
93 //!
94 //! ## Example
95 //!
96 //! ```
97 //! use base64::Engine as _;
98 //! use base64::engine::general_purpose;
99 //! use dryoc::kdf::Kdf;
100 //! use dryoc::kdf::protected::*;
101 //!
102 //! // Randomly generate a main key and context, using locked memory
103 //! let key: LockedKdf = Kdf::generate();
104 //! let subkey_id = 0;
105 //!
106 //! let subkey: Locked<Key> = key.derive_subkey(subkey_id).expect("derive failed");
107 //! println!(
108 //! "Subkey {}: {}",
109 //! subkey_id,
110 //! general_purpose::STANDARD.encode(&subkey)
111 //! );
112 //! ```
113 use super::*;
114 pub use crate::protected::*;
115
116 /// Heap-allocated, page-aligned key type alias for key derivation with
117 /// [`Kdf`].
118 pub type Key = HeapByteArray<CRYPTO_KDF_KEYBYTES>;
119 /// Heap-allocated, page-aligned context type alias for key derivation with
120 /// [`Kdf`].
121 pub type Context = HeapByteArray<CRYPTO_KDF_CONTEXTBYTES>;
122
123 /// Locked [`Kdf`], provided as a type alias for convenience.
124 pub type LockedKdf = Kdf<Locked<Key>, Locked<Context>>;
125}
126
127impl<
128 Key: NewByteArray<CRYPTO_KDF_KEYBYTES> + Zeroize + ZeroizeOnDrop,
129 Context: NewByteArray<CRYPTO_KDF_CONTEXTBYTES> + Zeroize,
130> Kdf<Key, Context>
131{
132 /// Randomly generates a new pair of main key and context.
133 pub fn generate() -> Self {
134 Self {
135 main_key: Key::generate(),
136 context: Context::generate(),
137 }
138 }
139
140 /// Randomly generates a new pair of main key and context.
141 ///
142 /// Prefer [`generate`](Self::generate). `gen` is retained for compatibility
143 /// with older Rust editions.
144 #[deprecated(note = "use generate() instead")]
145 pub fn r#gen() -> Self {
146 Self::generate()
147 }
148}
149
150impl<
151 Key: ByteArray<CRYPTO_KDF_KEYBYTES> + Zeroize + ZeroizeOnDrop,
152 Context: ByteArray<CRYPTO_KDF_CONTEXTBYTES> + Zeroize,
153> Kdf<Key, Context>
154{
155 /// Derives a subkey for `subkey_id`, returning it.
156 ///
157 /// # Errors
158 ///
159 /// Returns an error unless `LENGTH` is between
160 /// [`CRYPTO_KDF_BLAKE2B_BYTES_MIN`](crate::constants::CRYPTO_KDF_BLAKE2B_BYTES_MIN)
161 /// and
162 /// [`CRYPTO_KDF_BLAKE2B_BYTES_MAX`](crate::constants::CRYPTO_KDF_BLAKE2B_BYTES_MAX),
163 /// inclusive.
164 pub fn derive_subkey<const LENGTH: usize, Subkey: NewByteArray<LENGTH>>(
165 &self,
166 subkey_id: u64,
167 ) -> Result<Subkey, Error> {
168 validate_subkey_length(LENGTH)?;
169 let mut subkey = Subkey::new_byte_array();
170 crypto_kdf_derive_from_key(
171 subkey.as_mut_array(),
172 subkey_id,
173 self.context.as_array(),
174 self.main_key.as_array(),
175 )?;
176 Ok(subkey)
177 }
178
179 /// Derives a subkey for `subkey_id`, returning it as a [`Vec`]. Provided
180 /// for convenience.
181 ///
182 /// # Errors
183 ///
184 /// Returns an error unless `length` is between
185 /// [`CRYPTO_KDF_BLAKE2B_BYTES_MIN`](crate::constants::CRYPTO_KDF_BLAKE2B_BYTES_MIN)
186 /// and
187 /// [`CRYPTO_KDF_BLAKE2B_BYTES_MAX`](crate::constants::CRYPTO_KDF_BLAKE2B_BYTES_MAX),
188 /// inclusive.
189 pub fn derive_subkey_to_vec(&self, subkey_id: u64, length: usize) -> Result<Vec<u8>, Error> {
190 validate_subkey_length(length)?;
191 let mut subkey = vec![0u8; length];
192 crypto_kdf_derive_from_key(
193 &mut subkey,
194 subkey_id,
195 self.context.as_array(),
196 self.main_key.as_array(),
197 )?;
198 Ok(subkey)
199 }
200
201 /// Constructs a new instance from `key` and `context`, consuming them both.
202 pub fn from_parts(main_key: Key, context: Context) -> Self {
203 Self { main_key, context }
204 }
205
206 /// Moves the key and context out of this instance, returning them as a
207 /// tuple.
208 pub fn into_parts(self) -> (Key, Context) {
209 (self.main_key, self.context)
210 }
211}
212
213impl Kdf<Key, Context> {
214 /// Randomly generates a new pair of main key and context.
215 pub fn generate_with_defaults() -> Self {
216 Self {
217 main_key: Key::generate(),
218 context: Context::generate(),
219 }
220 }
221
222 /// Randomly generates a new pair of main key and context.
223 ///
224 /// Prefer [`generate_with_defaults`](Self::generate_with_defaults). This
225 /// method is retained for compatibility.
226 #[deprecated(note = "use generate_with_defaults() instead")]
227 pub fn gen_with_defaults() -> Self {
228 Self::generate_with_defaults()
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn test_kdf() {
238 let key = StackKdf::generate();
239
240 let short_subkey: StackByteArray<16> = key.derive_subkey(0).expect("derive failed");
241 let long_subkey = key.derive_subkey_to_vec(0, 64).expect("derive failed");
242
243 assert_eq!(short_subkey.len(), 16);
244 assert_eq!(long_subkey.len(), 64);
245 assert!(format!("{key:?}").contains("[REDACTED]"));
246 assert!(matches!(
247 key.derive_subkey_to_vec(0, usize::MAX),
248 Err(Error::InvalidLength {
249 context: crate::ErrorContext::Subkey,
250 actual: usize::MAX,
251 ..
252 })
253 ));
254
255 let invalid_fixed: Result<StackByteArray<15>, Error> = key.derive_subkey(0);
256 assert!(invalid_fixed.is_err());
257 }
258}