bouncycastle_hkdf/lib.rs
1//! HMAC-based Extract-and-Expand Key Derivation Function (HKDF) as per RFC5859, as allowed by
2//! NIST SP 800-56Cr2.
3//!
4//! # Usage
5//!
6//! Since HKDF uses `HMAC<HASH>` as its underlying primitive, most of what is said in the [HMAC] crate docs
7//! about instantiating HMAC objects applies here as well. Unlike HMAC, an HKDF object is created without
8//! an initial key, and will self-initialize the internal HMAC object as part of the [HKDF::extract] phase.
9//!
10//!
11//! # Examples
12//! ## Constructing an object
13//!
14//! HMAC objects can be constructed with any underlying hash function that implements [Hash].
15//! Type aliases are provided for the common HKDF-HASH algorithms.
16//!
17//! The following object instantiations are equivalent:
18//!
19//! ```
20//! use bouncycastle_hkdf::HKDF_SHA256;
21//!
22//! let hkdf = HKDF_SHA256::new();
23//! ```
24//! and
25//! ```
26//! use bouncycastle_hkdf::HKDF;
27//! use bouncycastle_sha2::SHA256;
28//!
29//! let hkdf = HKDF::<SHA256>::new();
30//! ```
31//!
32//! ## Deriving a key via the [KDF] trait
33//! Being a Key Derivation Function (KDF), the objective of HKDF is to take input key material which is not
34//! directly usable for its intended purpose and transform into a suitable output key.
35//! Typically, this takes one or both of the following forms:
36//!
37//! * Starting with a seed and mixing in additional input to diversify the output key (ie make it unique). An example of this would be starting with a secret seed and mixing in a public ID or URL to generate keys which are unique per URL.
38//! * Starting with a full-entropy seed which is at the correct security level for the application, but which is not long enough. An example could be starting with a 128-bit seed and mixing it with the strings "read" and "write" to produce one AES-128 key for each of the two directions of a communication channel.
39//!
40//! The simplest usage is via the one-shot functions provided by the [KDF] trait.
41//!
42//! ```
43//! use bouncycastle_core_interface::key_material::{KeyMaterial256, KeyType};
44//! use bouncycastle_core_interface::traits::{KDF };
45//! use bouncycastle_hkdf::HKDF_SHA256;
46//!
47//! let key = KeyMaterial256::from_bytes_as_type(
48//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
49//! KeyType::Seed).unwrap();
50//!
51//! let hkdf = HKDF_SHA256::new();
52//! let key = hkdf.derive_key(&key, b"extra input").unwrap();
53//! ```
54//!
55//! [KDF::derive_key] will produce a key the same length as the underlying hash function.
56//! Longer output can be requested by instead using [KDF::derive_key_out] and providing a larger output buffer,
57//! which will be filled.
58//!
59//! As with other uses of [KeyMaterial], the [KDF::derive_key] function will track the entropy of the input
60//! key material, and will set the entropy of the output key material accordingly.
61//!
62//! The [KDF] trait also provides the [KDF::derive_key_from_multiple] and [KDF::derive_key_from_multiple_out]
63//! functions, which allows for multiple inputs to be mixed into a single output key, and which allows
64//! for some advanced control of the underlying HKDF primitive.
65//!
66//!
67//! ## HKDF Extract-and-Expand
68//!
69//! The HKDF algorithm defined in RFC5896 and SP 800-56Cr2 is a two-step KDF, broken into an Extract step
70//! which essentially absorbs entropy from the input key material,
71//! and an Expand step which produces the output key material of any requested size.
72//! This interface is essentially a pre-cursor to the [XOF] API which was introduced with SHA3; the main
73//! difference being that HKDF-Expand needs to be told up-front how much output to produce, whereas XOFs
74//! can stream output as needed.
75//!
76//! Naturally, the full two-step HKDF-Extract and HKDF-Expand interface is provided by the [HKDF] struct,
77//! and exposes additional HKDF-specific parameters beyond what is exposed by the functions of the [KDF] trait.
78//!
79//! The usage pattern here is flexible, but generally follows the pattern of first calling [HKDF::extract]
80//! with a `salt` and an input key material `ikm`, which produces a pseudorandom key `prk`.
81//! The `prk` will have a [KeyType] and [SecurityStrength] that results from combining the two provided input keys,
82//! The `prk` may be! used directly as a full-entropy cryptographic key.
83//!
84//! Since the extract step may be called with any number of input keys, a streaming interface is provided
85//! whereby streaming mode in initialized with a call to [HKDF::do_extract_init], and then
86//! repeated calls to [HKDF::do_extract_update_key] and [HKDF::do_extract_update_bytes] may be made.
87//! Entropy from the inputs keys provided via [HKDF::do_extract_update_key] are credited towards the output key,
88//! while bytes provided via [HKDF::do_extract_update_bytes] are not.
89//! One restriction here is that once you start provided un-credited bytes via [HKDF::do_extract_update_bytes],
90//! no more calls to [HKDF::do_extract_update_key] may be made.
91//! The streaming API is completed with a call to either [HKDF::do_extract_final] or [HKDF::do_extract_final_out].
92//!
93//! The second stage, [HKDF::expand_out] stretches the `prk` into a longer output key, still of the same [KeyType]
94//! and [SecurityStrength].
95//!
96//! A typical flow looks like this:
97//!
98//! ```
99//! use bouncycastle_core_interface::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialInternal, KeyType};
100//! use bouncycastle_core_interface::traits::KDF;
101//! use bouncycastle_hkdf::{HKDF, HKDF_SHA256};
102//! use bouncycastle_sha2::{SHA256};
103//!
104//! // setup variables
105//! let salt = KeyMaterial256::from_bytes_as_type(
106//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
107//! KeyType::MACKey).unwrap();
108//!
109//! let ikm = KeyMaterial256::from_bytes_as_type(
110//! b"\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00",
111//! KeyType::MACKey).unwrap();
112//!
113//! let info = b"some extra context info";
114//!
115//! // Use the streaming API to derive an output key of length 200 bytes.
116//! let mut okm = KeyMaterialInternal::<200>::new();
117//! let mut hkdf = HKDF::<SHA256>::default();
118//! hkdf.do_extract_init(&salt).unwrap();
119//! hkdf.do_extract_update_bytes(ikm.ref_to_bytes()).unwrap();
120//! let prk = hkdf.do_extract_final().unwrap();
121//! HKDF_SHA256::expand_out(&prk, info, 200, &mut okm).unwrap();
122//! ```
123//!
124//! Various convenience wrapper functions are provided which can reduce the amount of boilerplate code
125//! for common cases.
126//! For example, the above code can be condensed to:
127//!
128//! ```
129//! use bouncycastle_core_interface::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialInternal, KeyType};
130//! use bouncycastle_hkdf::{HKDF_SHA256};
131//!
132//! // setup variables
133//! let salt = KeyMaterial256::from_bytes_as_type(
134//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
135//! KeyType::MACKey).unwrap();
136//!
137//! let ikm = KeyMaterial256::from_bytes_as_type(
138//! b"\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00",
139//! KeyType::MACKey).unwrap();
140//!
141//! let info = b"some extra context info";
142//!
143//! // Use the one-shot API to derive an output key of length 200 bytes.
144//! let mut okm = KeyMaterialInternal::<200>::new();
145//! let _bytes_written = HKDF_SHA256::extract_and_expand_out(&salt, &ikm, info, 200, &mut okm).unwrap();
146//! ```
147
148
149#![forbid(unsafe_code)]
150
151use std::marker::PhantomData;
152use bouncycastle_core_interface::errors::{KDFError, MACError};
153use bouncycastle_core_interface::key_material::{KeyMaterial0, KeyMaterial512, KeyMaterialInternal, KeyType};
154use bouncycastle_core_interface::traits::{Hash, KDF, KeyMaterial, MAC, HashAlgParams, SecurityStrength};
155use bouncycastle_hmac::HMAC;
156use bouncycastle_sha2::{SHA256, SHA512};
157use bouncycastle_utils::{max, min};
158
159// Imports needed only for docs
160#[allow(unused_imports)]
161use bouncycastle_core_interface::traits::XOF;
162// end doc-only imports
163
164
165/*** Constants ***/
166// Slightly hacky, but set this to accomodate the underlying hash primitive with the largest output size.
167// Would be better to somehow pull that at compile time from H, but I'm not sure how to do that.
168const HMAC_BLOCK_LEN: usize = 64;
169
170/*** String constants ***/
171
172pub const HKDF_SHA256_NAME: &str = "HKDF-SHA256";
173pub const HKDF_SHA512_NAME: &str = "HKDF-SHA512";
174
175
176/*** Types ***/
177#[allow(non_camel_case_types)]
178pub type HKDF_SHA256 = HKDF<SHA256>;
179#[allow(non_camel_case_types)]
180pub type HKDF_SHA512 = HKDF<SHA512>;
181
182pub struct HKDF<H: Hash + HashAlgParams + Default> {
183 hmac: Option<HMAC<H>>, // Optional because we can't construct an HMAC until they give us a key
184 // to initialize it with.
185 // None should correspond to a state of Uninitialized.
186 entropy: HkdfEntropyTracker<H>,
187 state: HkdfStates,
188}
189
190// Note: does not need to impl Drop because HKDF itself does not hold any sensitive state data.
191
192#[derive(Clone, Debug, PartialOrd, PartialEq)]
193enum HkdfStates {
194 /// waiting for salt
195 Uninitialized,
196
197 /// Salt set, waiting for IKMs or do_final
198 Initialized,
199
200 /// [HKDF::do_extract_update_key] has been called, after which no more credited IKMs can be given.
201 /// This is in conformance with NIST SP 800-133 which requires all keys to come before other inputs.
202 TakingAdditionalInfo,
203}
204
205struct HkdfEntropyTracker<H: Hash + HashAlgParams + Default> {
206 _phantomhash: PhantomData<H>,
207 entropy: usize,
208 security_strength: SecurityStrength,
209}
210
211impl<H: Hash + HashAlgParams + Default> HkdfEntropyTracker<H> {
212 fn new() -> Self { Self { _phantomhash: PhantomData, entropy: 0, security_strength: SecurityStrength::None } }
213
214 /// Takes in a KeyMaterial that is being mixed and figures out how much entropy to credit.
215 /// Returns the amount of entropy credited.
216 fn credit_entropy(&mut self, key: &impl KeyMaterial) -> usize {
217 let additional_entropy = if key.is_full_entropy() { key.key_len() } else { 0 };
218 self.entropy += additional_entropy;
219 self.security_strength = max(&self.security_strength, &key.security_strength()).clone();
220 self.security_strength = min(&self.security_strength, &SecurityStrength::from_bytes(H::OUTPUT_LEN/2)).clone();
221 additional_entropy
222 }
223
224 pub fn get_entropy(&self) -> usize {
225 self.entropy
226 }
227
228 // According to NIST SP 800-56Cr2, a KDF is fully seeded when its underlying hash primitive has a full block.
229 pub fn is_fully_seeded(&self) -> bool {
230 self.entropy >= H::OUTPUT_LEN
231 }
232
233 /// Either [KeyMaterial::BytesLowEntropy] or [KeyMaterial::BytesFullEntropy] depending on
234 /// whether enough input key material was provided for the internal hash function to have a full block.
235 fn get_output_key_type(&self) -> KeyType {
236 if self.is_fully_seeded() {
237 KeyType::BytesFullEntropy
238 } else {
239 KeyType::BytesLowEntropy
240 }
241 }
242}
243
244// Since I don't want this struct to be public, the tests have to go here.
245#[test]
246fn test_entropy_tracker() {
247 let mut entropy = HkdfEntropyTracker::<SHA256>::new();
248
249 assert_eq!(entropy.get_entropy(), 0);
250 assert_eq!(entropy.get_output_key_type(), KeyType::BytesLowEntropy);
251
252 let key = KeyMaterial512::from_bytes_as_type(b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", KeyType::BytesFullEntropy).unwrap();
253 entropy.credit_entropy(&key);
254 assert_eq!(entropy.get_entropy(), 16);
255 assert_eq!(entropy.is_fully_seeded(), false);
256 assert_eq!(entropy.get_output_key_type(), KeyType::BytesLowEntropy);
257
258 entropy.credit_entropy(&key);
259 assert_eq!(entropy.get_entropy(), 32);
260 assert_eq!(entropy.is_fully_seeded(), true);
261 assert_eq!(entropy.get_output_key_type(), KeyType::BytesFullEntropy);
262}
263
264impl<H: Hash + HashAlgParams + Default> Default for HKDF<H> {
265 fn default() -> Self {
266 Self::new()
267 }
268}
269
270impl<H: Hash + HashAlgParams + Default> HKDF<H> {
271 pub fn new() -> Self {
272 Self { hmac: None, entropy: HkdfEntropyTracker::new(), state: HkdfStates::Uninitialized }
273 }
274
275 /// Returns the amount of entropy currently credited from the keys inputted so far.
276 pub fn get_entropy(&self) -> usize {
277 self.entropy.get_entropy()
278 }
279
280 /// Has the entropy input so far met the threshold for this object to be considered fully seeded?
281 pub fn is_fully_seeded(&self) -> bool {
282 self.entropy.is_fully_seeded()
283 }
284
285 /// HKDF-Extract(salt, IKM) -> PRK
286 /// Options:
287 /// Hash a hash function; HashLen denotes the length of the
288 /// hash function output in octets
289 ///
290 /// Inputs:
291 /// salt optional salt value (a non-secret random value);
292 /// if not provided, it is set to a string of HashLen zeros.
293 /// IKM input keying material
294 ///
295 /// Output:
296 /// PRK a pseudorandom key (of HashLen octets)
297 ///
298 /// The KeyMaterial input parameters can be of any [KeyType]; but the type of the output will be set accordingly.
299 /// The output KeyMaterial will be of fixed size, with a capacity large enough to cover any
300 /// underlying hash function, but the actual key length will be appropriate to the underlying hash function.
301 ///
302 /// Salt is optional, which is indicated by providing an uninitialized KeyMaterial object of length zero,
303 /// the capacity is irrelevant, so KeyMateriol256::new() or KeyMaterial_internal::<0>::new() would both count as an absent salt.
304 pub fn extract(
305 salt: &impl KeyMaterial,
306 ikm: &impl KeyMaterial,
307 ) -> Result<impl KeyMaterial, MACError> {
308 let mut prk = KeyMaterialInternal::<HMAC_BLOCK_LEN>::new();
309 Self::extract_out(salt, ikm, &mut prk)?;
310 Ok(prk)
311 }
312
313 /// Same as [HKDF::extract], but writes the output to a provided KeyMaterial buffer.
314 pub fn extract_out(
315 salt: &impl KeyMaterial,
316 ikm: &impl KeyMaterial,
317 prk: &mut impl KeyMaterial,
318 ) -> Result<usize, MACError> {
319 // PRK = HMAC-Hash(salt, IKM)
320
321 let mut hkdf = Self::new();
322 hkdf.do_extract_init(salt)?;
323 hkdf.do_extract_update_key(ikm)?;
324 let bytes_written = hkdf.do_extract_final_out(prk)?;
325
326 Ok(bytes_written)
327 }
328
329 /// The definition of HKDF-Expand from RFC5869 is as follows:
330 /// HKDF-Expand(PRK, info, L) -> OKM
331 /// Options:
332 /// Hash a hash function; HashLen denotes the length of the
333 /// hash function output in octets
334 /// Inputs:
335 /// PRK a pseudorandom key of at least HashLen octets
336 /// (usually, the output from the extract step)
337 /// info optional context and application specific information
338 /// (can be a zero-length string)
339 /// L length of output keying material in octets
340 /// (<= 255*HashLen)
341 ///
342 /// Output:
343 /// OKM output keying material (of L octets)
344 ///
345 /// Due to the details of the KeyMaterial object needing to compile to a known size, there is (currently)
346 /// no way (within a no_std context) to dynamically allocate a KeyMaterial object according to the given 'L',
347 /// therefore this function is provided only as expand_out(), filling the provided KeyMaterial object,
348 /// and no analogous expand() is provided.
349 ///
350 /// The KeyMaterial input parameters can be of any KeyType; but the type of the output will be set accordingly.
351 ///
352 /// L is the output length. This will throw a [MACError::InvalidLength] if the provided KeyMaterial is too small to hold the requested output.
353 ///
354 /// Returns the number of bytes written.
355 #[allow(non_snake_case)] // for L
356 pub fn expand_out(
357 prk: &impl KeyMaterial,
358 info: &[u8],
359 L: usize,
360 okm: &mut impl KeyMaterial,
361 ) -> Result<usize, KDFError> {
362 // From RFC5896
363 // N = ceil(L/HashLen)
364 // T = T(1) | T(2) | T(3) | ... | T(N)
365 // OKM = first L octets of T
366 //
367 // where:
368 // T(0) = empty string (zero length)
369 // T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
370 // T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
371 // T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
372 // ...
373 //
374 // (where the constant concatenated to the end of each T(n) is a
375 // single octet.)
376
377 let hash_len = H::OUTPUT_LEN;
378 if L > 255 * hash_len {
379 return Err(KDFError::InvalidLength(
380 "HMAC can not produce more than 255*HashLen bytes out output",
381 ));
382 }
383
384 if L > okm.capacity() {
385 return Err(KDFError::InvalidLength(
386 "Provided KeyMaterial is too small to hold the requested output length.",
387 ));
388 }
389
390 let mut entropy = HkdfEntropyTracker::<H>::new();
391 entropy.credit_entropy(prk);
392
393 #[allow(non_snake_case)]
394 let N = L.div_ceil(hash_len) as u8;
395 let mut bytes_written: usize = 0;
396
397 okm.allow_hazardous_operations();
398 let out: &mut [u8] = okm.mut_ref_to_bytes()?;
399 // Could potentially speed this up by unrolling T(0) and T(1)
400
401 // We're gonna have to kludge the prk key type to MACKey to make HMAC happy, but we'll set it back to the original value afterwards.
402 let prk_as_mac_key = KeyMaterialInternal::<HMAC_BLOCK_LEN>::from_bytes_as_type(prk.ref_to_bytes(), KeyType::MACKey)?;
403
404 #[allow(non_snake_case)]
405 let mut T = [0u8; HMAC_BLOCK_LEN];
406 let mut t_len: usize = 0;
407 let mut i = 1u8;
408 while i < N {
409 // todo: might need this to be new_allow_weak_key()
410 let mut hmac = HMAC::<H>::new(&prk_as_mac_key)?;
411 hmac.do_update(&T[..t_len]);
412 hmac.do_update(info);
413 hmac.do_update(&[i]);
414
415 t_len = hmac.do_final_out(&mut T)?;
416 debug_assert_eq!(t_len, hash_len); // this will be true for every iteration after T(0) / T(1)
417 out[bytes_written..bytes_written + t_len].copy_from_slice(&T[..t_len]);
418 bytes_written += t_len;
419 i += 1;
420 }
421
422 // On the last iteration, we don't take all of the output.
423 let remaining = L - bytes_written;
424 // todo: might need this to be new_allow_weak_key()
425 let mut hmac = HMAC::<H>::new(&prk_as_mac_key)?;
426 hmac.do_update(&T[..t_len]);
427 hmac.do_update(info);
428 hmac.do_update(&[i]);
429
430 t_len = hmac.do_final_out(&mut T[..remaining])?;
431 debug_assert_eq!(t_len, remaining); // this will be true for every iteration after T(0) / T(1)
432 out[bytes_written..bytes_written + t_len].copy_from_slice(&T[..t_len]);
433 bytes_written += t_len;
434
435 // set the KeyType of the output
436 // since we've done some computation, the result will not actually be zeroized, even if all input key material was zeroized.
437 if prk.key_type() == KeyType::Zeroized {
438 okm.set_key_type(KeyType::BytesLowEntropy)?;
439 } else {
440 okm.set_key_type(prk.key_type().clone())?;
441 }
442 okm.set_key_len(bytes_written)?;
443 if okm.key_type() <= KeyType::BytesLowEntropy {
444 okm.set_security_strength(SecurityStrength::None)?;
445 } else {
446 okm.set_security_strength(min(&SecurityStrength::from_bytes(okm.key_len()), &entropy.security_strength).clone())?;
447 }
448 okm.drop_hazardous_operations();
449 Ok(bytes_written)
450 }
451
452 /// Salt is optional, which is indicated by providing an uninitialized KeyMaterial object of length zero,
453 /// the capacity is irrelevant, so KeyMateriol256::new() or KeyMaterial_internal::<0>::new() would both count as an absent salt.
454 #[allow(non_snake_case)]
455 pub fn extract_and_expand_out(
456 salt: &impl KeyMaterial,
457 ikm: &impl KeyMaterial,
458 info: &[u8],
459 L: usize,
460 okm: &mut impl KeyMaterial,
461 ) -> Result<usize, KDFError> {
462 let prk = Self::extract(salt, ikm)?;
463 Self::expand_out(&prk, info, L, okm)
464 }
465
466 /// This, together with [HKDF::do_extract_update_key], [HKDF::do_extract_update_bytes] and [HKDF::do_extract_final]
467 /// provide a streaming interface for very long values of `ikm`.
468 /// In this mode, the entropy of `ikm` is untracked, and so only the entropy ef `salt` is taken into account
469 /// when computing the entropy of the output `prk`.
470 /// The KeyMaterial input parameters can be of any [KeyType]; but the type of the output will be set accordingly.
471 /// The output KeyMaterial will be of fixed size, with a capacity large enough to cover any
472 /// underlying hash function, but the actual key length will be appropriate to the underlying hash function.
473 ///
474 /// Salt is optional, which is indicated by providing an uninitialized KeyMaterial object of length zero,
475 /// the capacity is irrelevant, so KeyMateriol256::new() or KeyMaterial_internal::<0>::new() would both count as an absent salt.
476 ///
477 /// Returns the number of bits of entropy credited to this input key material.
478 pub fn do_extract_init(&mut self, salt: &impl KeyMaterial) -> Result<usize, MACError> {
479 if self.state >= HkdfStates::Initialized {
480 return Err(MACError::InvalidState("Initialized twice"));
481 };
482
483 // Often HMAC is initialized with a zero salt,
484 // So we're gonna ignore key strength errors here
485 // This will all be tabulated correctly via entropy.credit_entropy()
486 self.hmac = Some(HMAC::<H>::new_allow_weak_key(salt)?);
487
488 let additional_entropy = self.entropy.credit_entropy(salt);
489 self.state = HkdfStates::Initialized;
490
491 Ok(additional_entropy)
492 }
493
494 /// An update function that allows adding an IKM as a [KeyMaterial].
495 /// Credits the entropy contained in the IKM.
496 /// This function may be called zero or more times in a workflow.
497 /// In particular, this function may be called multiple times to add more than one IKM.
498 ///
499 /// Returns the number of bits of entropy credited to this input key material.
500 pub fn do_extract_update_key(&mut self, ikm: &impl KeyMaterial) -> Result<usize, MACError> {
501 if self.state == HkdfStates::Uninitialized {
502 return Err(MACError::InvalidState(
503 "Must call do_extract_init() before calling do_extract_update_key()",
504 ));
505 };
506
507 if self.state == HkdfStates::TakingAdditionalInfo {
508 return Err(MACError::InvalidState(
509 "Cannot accept more credited IKMs via do_extract_update_key(&KeyMaterial) after an uncredited key has been provided via do_extract_update(&[u8])",
510 ));
511 }
512 debug_assert_eq!(self.state, HkdfStates::Initialized);
513 debug_assert!(self.hmac.is_some());
514
515 let additional_entropy = self.entropy.credit_entropy(ikm);
516 let hmac_ref: &mut HMAC<H> = self.hmac.as_mut().unwrap();
517 hmac_ref.do_update(ikm.ref_to_bytes());
518 // self.hmac.as_mut().unwrap().do_update(ikm.ref_to_bytes());
519
520 Ok(additional_entropy)
521 }
522
523 /// An update function that allows streaming of the IKM as bytes.
524 /// Note that since this interface takes the IKM as raw bytes, it cannot track its entropy
525 /// and therefore any IKM material provided through this interface will not count towards
526 /// the entropy of the output key.
527 ///
528 /// State machine: this function must be called after [HKDF::do_extract_init], followed by
529 /// zero or more calls of [HKDF::do_extract_update_key], and before [HKDF::do_extract_final].
530 ///
531 /// Returns the number of bits of entropy credited to this input key material, which is always 0 for this function.
532 pub fn do_extract_update_bytes(&mut self, ikm_chunk: &[u8]) -> Result<usize, MACError> {
533 if self.state == HkdfStates::Uninitialized {
534 return Err(MACError::InvalidState(
535 "Must call do_extract_init() before calling do_extract_update()",
536 ));
537 };
538 self.state = HkdfStates::TakingAdditionalInfo;
539
540 self.hmac.as_mut().unwrap().do_update(ikm_chunk);
541 Ok(0)
542 }
543
544 #[allow(non_snake_case)]
545 pub fn do_extract_final(self) -> Result<impl KeyMaterial, MACError> {
546 let mut okm = KeyMaterialInternal::<HMAC_BLOCK_LEN>::new();
547 self.do_extract_final_out(&mut okm)?;
548 Ok(okm)
549 }
550
551 #[allow(non_snake_case)]
552 pub fn do_extract_final_out(self, okm: &mut impl KeyMaterial) -> Result<usize, MACError> {
553 if self.state == HkdfStates::Uninitialized {
554 return Err(MACError::InvalidState(
555 "Must call do_extract_init() before calling do_extract_complete().",
556 ));
557 };
558 debug_assert!(self.hmac.is_some());
559
560 let output_key_type = self.entropy.get_output_key_type(); // need to do this above self.hmac.do_final_out, which will consume self.
561
562 okm.allow_hazardous_operations(); // doing it here to get mut_ref_to_bytes
563 let bytes_written = self.hmac.unwrap().do_final_out(&mut okm.mut_ref_to_bytes().unwrap())?;
564 okm.set_key_len(bytes_written)?;
565 okm.set_key_type(output_key_type)?;
566 if output_key_type <= KeyType::BytesLowEntropy {
567 okm.set_security_strength(SecurityStrength::None)?;
568 } else {
569 okm.set_security_strength(min(&SecurityStrength::from_bytes(okm.key_len()), &self.entropy.security_strength).clone())?;
570 }
571 okm.drop_hazardous_operations();
572 Ok(bytes_written)
573 }
574}
575
576/// As per NIST SP 800-56Cr2 section 5.1, HKDF extract_and_expand can be used as a KDF.
577/// Additionally, section 4.1 says that when using HMAC as a KDF, the salt may be set to
578/// a string of HashLen zeros. All key material and additional_input is mapped to HKDF's ikm input.
579/// While this is not the only mode in which HKDF can be used as a KDF, this is considered the default mode
580/// that is exposed through [KDF::derive_key] and [KDF::derive_key_out].
581/// More advanced control of the inputs to HKDF can be achieved by using [KDF::derive_key_from_multiple] and
582/// [KDF::derive_key_from_multiple_out], or by using the [HKDF] impl directly.
583///
584/// Entropy tracking: this implementation will map entropy from the input keys to the output key.
585impl<H: Hash + HashAlgParams + Default> KDF for HKDF<H> {
586 /// This invokes [HKDF::extract_and_expand_out] with a zero salt and using the provided key as ikm.
587 /// This provides a fixed-length output, which may be truncated as needed.
588 fn derive_key(
589 self,
590 key: &impl KeyMaterial,
591 additional_input: &[u8],
592 ) -> Result<Box<dyn KeyMaterial>, KDFError> {
593 let mut output_key = KeyMaterial512::new();
594 _ = self.derive_key_out(key, additional_input, &mut output_key)?;
595 output_key.truncate(H::OUTPUT_LEN)?;
596 Ok(Box::new(output_key))
597 }
598
599 /// This invokes [HKDF::extract_and_expand_out] with a zero salt and using the provided key as ikm.
600 /// This fills the provided [KeyMaterial] object in place of exposing a Length parameter.
601 fn derive_key_out(
602 self,
603 key: &impl KeyMaterial,
604 additional_input: &[u8],
605 output_key: &mut impl KeyMaterial,
606 ) -> Result<usize, KDFError> {
607 let bytes_written = HKDF::<H>::extract_and_expand_out(&KeyMaterialInternal::<0>::new(), key, additional_input, output_key.capacity(), output_key)?;
608 Ok(bytes_written)
609 }
610
611 /// As with [KDF::derive_key] and [KDF::derive_key_out],
612 /// This invokes HKDF in the extract_and_expand mode and maps the provided keys in the following way:
613 /// - The first (0'th) key is used as the salt for HKDF.extract.
614 /// - The remaining keys are concatenated to form HKDF's ikm parameter.
615 /// - Entropy of all provided keys are tracked to determine the output key's entropy.
616 ///
617 /// Therefore, derive_key_from_multiple(&[KeyMaterial0::new(), &key], &info) is equivalent to derive_key(&key, &info).
618 ///
619 /// This provides a fixed-length output, which may be truncated as needed.
620 fn derive_key_from_multiple(
621 self,
622 keys: &[&impl KeyMaterial],
623 additional_input: &[u8],
624 ) -> Result<Box<dyn KeyMaterial>, KDFError> {
625 let mut output_key = KeyMaterial512::new();
626 _ = self.derive_key_from_multiple_out(keys, additional_input, &mut output_key)?;
627 output_key.truncate(*min(&output_key.key_len(), &H::OUTPUT_LEN))?;
628 Ok(Box::new(output_key))
629 }
630
631
632 /// This behaves the same as [KDF::derive_key_from_multiple], except that it fills the provided
633 /// [KeyMaterial] object in place of exposing a Length parameter.
634 fn derive_key_from_multiple_out(
635 self,
636 keys: &[&impl KeyMaterial],
637 additional_input: &[u8],
638 output_key: &mut impl KeyMaterial,
639 ) -> Result<usize, KDFError> {
640 let mut hkdf = HKDF::<H>::new();
641 let mut entropy = HkdfEntropyTracker::<H>::new();
642
643 if keys.len() >= 1 {
644 hkdf.do_extract_init(keys[0])?;
645 entropy.credit_entropy(keys[0]);
646 } else {
647 hkdf.do_extract_init(&KeyMaterial0::new())?;
648 };
649
650 if keys.len() != 0 {
651 for key in &keys[1..] {
652 hkdf.do_extract_update_bytes(key.ref_to_bytes())?;
653 entropy.credit_entropy(*key);
654 }
655 }
656 let mut prk = KeyMaterialInternal::<HMAC_BLOCK_LEN>::new();
657 _ = hkdf.do_extract_final_out(&mut prk)?;
658 let bytes_written = HKDF::<H>::expand_out(&prk, additional_input, output_key.capacity(), output_key)?;
659
660 output_key.allow_hazardous_operations();
661 output_key.set_key_type(entropy.get_output_key_type())?;
662 output_key.set_security_strength(min(&SecurityStrength::from_bytes(output_key.key_len()), &entropy.security_strength).clone())?;
663 output_key.drop_hazardous_operations();
664
665 Ok(bytes_written)
666 }
667
668 fn max_security_strength(&self) -> SecurityStrength {
669 H::default().max_security_strength()
670 }
671}