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::key_material::{KeyMaterial256, KeyType};
44//! use bouncycastle_core::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 [`KeyMaterialTrait`], 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::key_material::{KeyMaterialTrait, KeyMaterial256, KeyMaterial, KeyType};
100//! use bouncycastle_core::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 = KeyMaterial::<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::key_material::{KeyMaterialTrait, KeyMaterial256, KeyMaterial, 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 = KeyMaterial::<200>::new();
145//! let _bytes_written = HKDF_SHA256::extract_and_expand_out(&salt, &ikm, info, 200, &mut okm).unwrap();
146//! ```
147//!
148//! # Suspending and resuming execution
149//!
150//! The *HKDF-Extract* phase supports a streaming API whereby any amount of additional input keying
151//! material can be provided either via [`HKDF::do_extract_update_key`] -- which will
152//! credit the entropy of the provided [`KeyMaterial`] -- or as raw uncredited bytes via
153//! [`HKDF::do_extract_update_bytes`].
154//!
155//! As such, The *HKDF-Extract* phase can be suspended to a cache and resumed later via the
156//! [`SuspendableKeyed`] trait.
157//!
158//! The HKDF algorithm is keyed by a `salt`, which is required twice: once at initialization and again
159//! during finalization. Suspension and resumption are supported via the [`SuspendableKeyed`] trait
160//! which requires the caller to store the salt securely and provide it again during resumption.
161//! Note that providing a different salt during resumption cannot be detected by the library and
162//! would silently produce a different PRK.
163//!
164//! ```rust
165//! use bouncycastle_hkdf::HKDF_SHA256;
166//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType};
167//! use bouncycastle_core::traits::SuspendableKeyed;
168//!
169//! let salt = KeyMaterial256::from_bytes_as_type(
170//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
171//! KeyType::MACKey).unwrap();
172//! let ikm_part1 = b"input keying material part 1";
173//! let ikm_part2 = b" ...and part 2";
174//!
175//! let mut hkdf = HKDF_SHA256::new();
176//! hkdf.do_extract_init(&salt).unwrap();
177//! hkdf.do_extract_update_bytes(ikm_part1).unwrap();
178//!
179//! // suspend the in-progress extract (the salt is NOT included in the serialized state)
180//! let serialized_state = hkdf.suspend();
181//!
182//! // ...
183//! // do other things in the meantime
184//! // ...
185//!
186//! // ... later, possibly on another host: resume from the serialized state by re-supplying
187//! // the same salt (make sure you store it securely!).
188//! let mut hkdf = HKDF_SHA256::from_suspended(serialized_state, &salt).unwrap();
189//! hkdf.do_extract_update_bytes(ikm_part2).unwrap();
190//! let _prk = hkdf.do_extract_final().unwrap();
191//! ```
192
193#![forbid(unsafe_code)]
194#![forbid(missing_docs)]
195
196use bouncycastle_core::errors::{KDFError, KeyMaterialError, MACError, SuspendableError};
197use bouncycastle_core::key_material;
198use bouncycastle_core::key_material::{
199 KeyMaterial, KeyMaterial0, KeyMaterial512, KeyMaterialTrait, KeyType,
200};
201use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver};
202use bouncycastle_core::traits::{
203 Hash, HashAlgParams, KDF, MAC, SecurityStrength, SuspendableKeyed,
204};
205use bouncycastle_hmac::{HMAC, SUSPENDED_HMAC_SHA256_STATE_LEN, SUSPENDED_HMAC_SHA512_STATE_LEN};
206use bouncycastle_sha2::{SHA256, SHA512};
207use bouncycastle_utils::{max, min};
208use std::marker::PhantomData;
209// Imports needed only for docs
210#[allow(unused_imports)]
211use bouncycastle_core::traits::XOF;
212// end doc-only imports
213
214/*** Constants ***/
215/// The size of the output key material from the HKDF-Extract phase `prk`, in bytes.
216/// This has been sized so that the output KeyMaterial has enough capacity to accommodate the
217/// underlying hash primitive with the largest output size.
218/// If the given hash function has a smaller output size, then the output KeyMaterial will be
219/// under-full (ie have a key_len that does not use its full capacity).
220/// TODO: This is a dirty dirty hack because correctly sizing the output key
221/// really requires the generic_const_exprs feature, which is currently only available on
222/// nightly Rust, and not on stable. Once they merge that feature, we will be able to get rid of this
223/// and declare `prk: &mut KeyMaterial<H::OUTPUT_LEN>` instead of this hack.
224pub const MAX_HMAC_OUTPUT_LEN: usize = 64;
225
226/*** String constants ***/
227
228///
229pub const HKDF_SHA256_NAME: &str = "HKDF-SHA256";
230///
231pub const HKDF_SHA512_NAME: &str = "HKDF-SHA512";
232
233/*** Types ***/
234/// Public type for HKDF using SHA256.
235#[allow(non_camel_case_types)]
236pub type HKDF_SHA256 = HKDF<SHA256>;
237/// Public type for HKDF using SHA512.
238#[allow(non_camel_case_types)]
239pub type HKDF_SHA512 = HKDF<SHA512>;
240
241/// Internal struct for HKDF.
242/// Can, in theory, be instantiated with hash functions other than the ones provided by this crate (even custom ones).
243#[derive(Clone)]
244pub struct HKDF<H: Hash + HashAlgParams + Default> {
245 // Optional because an HMAC cannot be constructed until a key is provided
246 // to initialize it with.
247 // None must correspond to a state of Uninitialized.
248 hmac: Option<HMAC<H>>,
249 entropy: HkdfEntropyTracker<H>,
250 state: HkdfStates,
251}
252
253// Note: does not need to impl Drop because HKDF itself does not hold any sensitive state data.
254
255#[derive(Clone, Debug, PartialOrd, PartialEq)]
256#[repr(u8)]
257enum HkdfStates {
258 /// waiting for salt
259 Uninitialized = 0,
260
261 /// Salt set, waiting for IKMs or do_final
262 Initialized = 1,
263
264 /// [`HKDF::do_extract_update_key`] has been called, after which no more credited IKMs can be given.
265 /// This is in conformance with NIST SP 800-133 which requires all keys to come before other inputs.
266 TakingAdditionalInfo = 2,
267}
268
269impl TryFrom<u8> for HkdfStates {
270 type Error = SuspendableError;
271
272 /// Inverse of `self as u8`; rejects unrecognized discriminants with [`SuspendableError::InvalidData`].
273 fn try_from(value: u8) -> Result<Self, Self::Error> {
274 Ok(match value {
275 0 => Self::Uninitialized,
276 1 => Self::Initialized,
277 2 => Self::TakingAdditionalInfo,
278 _ => return Err(SuspendableError::InvalidData),
279 })
280 }
281}
282
283#[derive(Clone)]
284struct HkdfEntropyTracker<H: Hash + HashAlgParams + Default> {
285 _phantomhash: PhantomData<H>,
286 entropy: usize,
287 security_strength: SecurityStrength,
288}
289
290impl<H: Hash + HashAlgParams + Default> HkdfEntropyTracker<H> {
291 fn new() -> Self {
292 Self { _phantomhash: PhantomData, entropy: 0, security_strength: SecurityStrength::None }
293 }
294
295 /// Takes in a KeyMaterial that is being mixed and figures out how much entropy to credit.
296 /// Returns the amount of entropy credited.
297 fn credit_entropy(&mut self, key: &impl KeyMaterialTrait) -> usize {
298 let additional_entropy = if key.is_full_entropy() { key.key_len() } else { 0 };
299 self.entropy += additional_entropy;
300 self.security_strength = max(&self.security_strength, &key.security_strength()).clone();
301 self.security_strength =
302 min(&self.security_strength, &SecurityStrength::from_bytes(H::OUTPUT_LEN / 2)).clone();
303 additional_entropy
304 }
305
306 pub fn get_entropy(&self) -> usize {
307 self.entropy
308 }
309
310 // According to NIST SP 800-56Cr2, a KDF is fully seeded when its underlying hash primitive has a full block.
311 pub fn is_fully_seeded(&self) -> bool {
312 self.entropy >= H::OUTPUT_LEN
313 }
314
315 /// Either [`KeyMaterialTrait::BytesLowEntropy`] or [`KeyMaterialTrait::BytesFullEntropy`] depending on
316 /// whether enough input key material was provided for the internal hash function to have a full block.
317 fn get_output_key_type(&self) -> KeyType {
318 if self.is_fully_seeded() { KeyType::CryptographicRandom } else { KeyType::Unknown }
319 }
320}
321
322// Because this struct is not public, the tests have to go here.
323#[test]
324fn test_entropy_tracker() {
325 let mut entropy = HkdfEntropyTracker::<SHA256>::new();
326
327 assert_eq!(entropy.get_entropy(), 0);
328 assert_eq!(entropy.get_output_key_type(), KeyType::Unknown);
329
330 let key = KeyMaterial512::from_bytes_as_type(
331 b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
332 KeyType::CryptographicRandom,
333 )
334 .unwrap();
335 entropy.credit_entropy(&key);
336 assert_eq!(entropy.get_entropy(), 16);
337 assert_eq!(entropy.is_fully_seeded(), false);
338 assert_eq!(entropy.get_output_key_type(), KeyType::Unknown);
339
340 entropy.credit_entropy(&key);
341 assert_eq!(entropy.get_entropy(), 32);
342 assert_eq!(entropy.is_fully_seeded(), true);
343 assert_eq!(entropy.get_output_key_type(), KeyType::CryptographicRandom);
344}
345
346impl<H: Hash + HashAlgParams + Default> Default for HKDF<H> {
347 fn default() -> Self {
348 Self::new()
349 }
350}
351
352impl<H: Hash + HashAlgParams + Default> HKDF<H> {
353 /// Get a new, uninstantiated HKDF object.
354 pub fn new() -> Self {
355 Self { hmac: None, entropy: HkdfEntropyTracker::new(), state: HkdfStates::Uninitialized }
356 }
357
358 /// Returns the amount of entropy currently credited from the keys inputted so far.
359 pub fn get_entropy(&self) -> usize {
360 self.entropy.get_entropy()
361 }
362
363 /// Check whether the entropy input so far met the threshold for this object to be considered fully seeded
364 pub fn is_fully_seeded(&self) -> bool {
365 self.entropy.is_fully_seeded()
366 }
367
368 /// HKDF-Extract(salt, IKM) -> PRK
369 /// Options:
370 /// Hash a hash function; HashLen denotes the length of the
371 /// hash function output in octets
372 ///
373 /// Inputs:
374 /// salt optional salt value (a non-secret random value);
375 /// if not provided, it is set to a string of HashLen zeros.
376 /// IKM input keying material
377 ///
378 /// Output:
379 /// PRK a pseudorandom key (of HashLen octets)
380 ///
381 /// The KeyMaterial input parameters can be of any [`KeyType`]; but the type of the output will be set accordingly.
382 /// The output KeyMaterial will be of fixed size, with a capacity large enough to cover any
383 /// underlying hash function, but the actual key length will be appropriate to the underlying hash function.
384 ///
385 /// Salt is optional, which is indicated by providing an uninitialized KeyMaterial object of length zero,
386 /// the capacity is irrelevant, so KeyMateriol256::new() or KeyMaterial_internal::<0>::new() would both count as an absent salt.
387 pub fn extract(
388 salt: &impl KeyMaterialTrait,
389 ikm: &impl KeyMaterialTrait,
390 ) -> Result<impl KeyMaterialTrait, MACError> {
391 let mut prk = KeyMaterial::<MAX_HMAC_OUTPUT_LEN>::new();
392 Self::extract_out(salt, ikm, &mut prk)?;
393 Ok(prk)
394 }
395
396 /// Same as [`HKDF::extract`], but writes the output to a provided KeyMaterial buffer.
397 /// Note that the provided KeyMaterial must be correctly sized to the hash function output length.
398 pub fn extract_out(
399 salt: &impl KeyMaterialTrait,
400 ikm: &impl KeyMaterialTrait,
401 prk: &mut KeyMaterial<MAX_HMAC_OUTPUT_LEN>,
402 ) -> Result<usize, MACError> {
403 // PRK = HMAC-Hash(salt, IKM)
404
405 let mut hkdf = Self::new();
406 hkdf.do_extract_init(salt)?;
407 hkdf.do_extract_update_key(ikm)?;
408 let bytes_written = hkdf.do_extract_final_out(prk)?;
409
410 Ok(bytes_written)
411 }
412
413 /// The definition of HKDF-Expand from RFC5869 is as follows:
414 /// HKDF-Expand(PRK, info, L) -> OKM
415 /// Options:
416 /// Hash a hash function; HashLen denotes the length of the
417 /// hash function output in octets
418 /// Inputs:
419 /// PRK a pseudorandom key of at least HashLen octets
420 /// (usually, the output from the extract step)
421 /// info optional context and application specific information
422 /// (can be a zero-length string)
423 /// L length of output keying material in octets
424 /// (<= 255*HashLen)
425 ///
426 /// Output:
427 /// OKM output keying material (of L octets)
428 ///
429 /// Due to the details of the KeyMaterial object needing to compile to a known size, there is (currently)
430 /// no way (within a no_std context) to dynamically allocate a KeyMaterial object according to the given 'L',
431 /// therefore this function is provided only as expand_out(), filling the provided KeyMaterial object,
432 /// and no analogous expand() is provided.
433 ///
434 /// The KeyMaterial input parameters can be of any KeyType; but the type of the output will be set accordingly.
435 ///
436 /// L is the output length. This will throw a [`MACError::InvalidLength`] if the provided KeyMaterial is too small to hold the requested output.
437 ///
438 /// Returns the number of bytes written.
439 #[allow(non_snake_case)] // for L
440 pub fn expand_out(
441 prk: &impl KeyMaterialTrait,
442 info: &[u8],
443 L: usize,
444 okm: &mut impl KeyMaterialTrait,
445 ) -> Result<usize, KDFError> {
446 // From RFC5896
447 // N = ceil(L/HashLen)
448 // T = T(1) | T(2) | T(3) | ... | T(N)
449 // OKM = first L octets of T
450 //
451 // where:
452 // T(0) = empty string (zero length)
453 // T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
454 // T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
455 // T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
456 // ...
457 //
458 // (where the constant concatenated to the end of each T(n) is a
459 // single octet.)
460
461 let hash_len = H::OUTPUT_LEN;
462 if L > 255 * hash_len {
463 return Err(KDFError::InvalidLength(
464 "HMAC can not produce more than 255*HashLen bytes out output",
465 ));
466 }
467
468 if L > okm.capacity() {
469 return Err(KDFError::InvalidLength(
470 "Provided KeyMaterial is too small to hold the requested output length.",
471 ));
472 }
473
474 let mut entropy = HkdfEntropyTracker::<H>::new();
475 entropy.credit_entropy(prk);
476
477 #[allow(non_snake_case)]
478 let N = L.div_ceil(hash_len) as u8;
479 let mut bytes_written: usize = 0;
480
481 // Could potentially speed this up by unrolling T(0) and T(1)
482
483 // The prk key type must be temporarily changed to MACKey to satisfy HMAC, then restored afterwards.
484 let prk_as_mac_key = KeyMaterial::<MAX_HMAC_OUTPUT_LEN>::from_bytes_as_type(
485 prk.ref_to_bytes(),
486 KeyType::MACKey,
487 )?;
488
489 #[allow(non_snake_case)]
490 let mut T = [0u8; MAX_HMAC_OUTPUT_LEN];
491 let mut t_len: usize = 0;
492 let mut i = 1u8;
493
494 key_material::do_hazardous_operations(okm, |okm| {
495 let out = okm.ref_to_bytes_mut()?;
496 while i < N {
497 let mut hmac = HMAC::<H>::new(&prk_as_mac_key)
498 .map_err(|_| KeyMaterialError::GenericError("HMAC initialization failed"))?;
499 hmac.do_update(&T[..t_len]);
500 hmac.do_update(info);
501 hmac.do_update(&[i]);
502
503 t_len = hmac
504 .do_final_out(&mut T)
505 .map_err(|_| KeyMaterialError::GenericError("HMAC finalization failed"))?;
506 debug_assert_eq!(t_len, hash_len); // this will be true for every iteration after T(0) / T(1)
507 out[bytes_written..bytes_written + t_len].copy_from_slice(&T[..t_len]);
508 bytes_written += t_len;
509 i += 1;
510 }
511 Ok(())
512 })?;
513
514 // Part of the output is not taken on the last iteration
515 let remaining = L - bytes_written;
516 let mut hmac = HMAC::<H>::new(&prk_as_mac_key)?;
517 hmac.do_update(&T[..t_len]);
518 hmac.do_update(info);
519 hmac.do_update(&[i]);
520
521 t_len = hmac.do_final_out(&mut T[..remaining])?;
522 debug_assert_eq!(t_len, remaining); // this will be true for every iteration after T(0) / T(1)
523
524 key_material::do_hazardous_operations(okm, |okm| {
525 let out = okm.ref_to_bytes_mut()?;
526 out[bytes_written..bytes_written + t_len].copy_from_slice(&T[..t_len]);
527 Ok(())
528 })?;
529 bytes_written += t_len;
530
531 // Set the KeyType of the output
532 // Since some computation has been performed, the result will not actually be zeroized, even if all input key material was zeroized.
533 key_material::do_hazardous_operations(okm, |okm| {
534 if prk.key_type() == KeyType::Zeroized {
535 okm.set_key_type(KeyType::Unknown)?;
536 } else {
537 okm.set_key_type(prk.key_type().clone())?;
538 }
539 okm.set_key_len(bytes_written)?;
540 if okm.key_type() <= KeyType::Unknown {
541 okm.set_security_strength(SecurityStrength::None)
542 } else {
543 okm.set_security_strength(
544 min(&SecurityStrength::from_bytes(okm.key_len()), &entropy.security_strength)
545 .clone(),
546 )
547 }
548 })?;
549
550 Ok(bytes_written)
551 }
552
553 /// Salt is optional, which is indicated by providing an uninitialized KeyMaterial object of length zero,
554 /// the capacity is irrelevant, so KeyMateriol256::new() or KeyMaterial_internal::<0>::new() would both count as an absent salt.
555 #[allow(non_snake_case)]
556 pub fn extract_and_expand_out(
557 salt: &impl KeyMaterialTrait,
558 ikm: &impl KeyMaterialTrait,
559 info: &[u8],
560 L: usize,
561 okm: &mut impl KeyMaterialTrait,
562 ) -> Result<usize, KDFError> {
563 let prk = Self::extract(salt, ikm)?;
564 Self::expand_out(&prk, info, L, okm)
565 }
566
567 /// This, together with [`HKDF::do_extract_update_key`], [`HKDF::do_extract_update_bytes`] and [`HKDF::do_extract_final`]
568 /// provide a streaming interface for very long values of `ikm`.
569 /// In this mode, the entropy of `ikm` is untracked, and so only the entropy ef `salt` is taken into account
570 /// when computing the entropy of the output `prk`.
571 /// The KeyMaterial input parameters can be of any [`KeyType`]; but the type of the output will be set accordingly.
572 /// The output KeyMaterial will be of fixed size, with a capacity large enough to cover any
573 /// underlying hash function, but the actual key length will be appropriate to the underlying hash function.
574 ///
575 /// Salt is optional; to omit it, provide a KeyMaterial0, which will cause HKDF to use the default all-zero salt.
576 ///
577 /// Returns the number of bits of entropy credited to this input key material.
578 pub fn do_extract_init(&mut self, salt: &impl KeyMaterialTrait) -> Result<usize, MACError> {
579 if self.state >= HkdfStates::Initialized {
580 return Err(MACError::InvalidState("Initialized twice"));
581 };
582
583 // Often HMAC is initialized with a zero salt,
584 // Key strength errors are ignored here.
585 // This will all be tabulated correctly via entropy.credit_entropy()
586 self.hmac = Some(HMAC::<H>::new_allow_weak_key(salt)?);
587
588 let additional_entropy = self.entropy.credit_entropy(salt);
589 self.state = HkdfStates::Initialized;
590
591 Ok(additional_entropy)
592 }
593
594 /// An update function that allows adding an IKM as a [`KeyMaterialTrait`].
595 /// Credits the entropy contained in the IKM.
596 /// This function may be called zero or more times in a workflow.
597 /// In particular, this function may be called multiple times to add more than one IKM.
598 ///
599 /// Returns the number of bits of entropy credited to this input key material.
600 pub fn do_extract_update_key(
601 &mut self,
602 ikm: &impl KeyMaterialTrait,
603 ) -> Result<usize, MACError> {
604 if self.state == HkdfStates::Uninitialized {
605 return Err(MACError::InvalidState(
606 "Must call do_extract_init() before calling do_extract_update_key()",
607 ));
608 };
609
610 if self.state == HkdfStates::TakingAdditionalInfo {
611 return Err(MACError::InvalidState(
612 "Cannot accept more credited IKMs via do_extract_update_key(&KeyMaterial) after an uncredited key has been provided via do_extract_update(&[u8])",
613 ));
614 }
615 debug_assert_eq!(self.state, HkdfStates::Initialized);
616 debug_assert!(self.hmac.is_some());
617
618 let additional_entropy = self.entropy.credit_entropy(ikm);
619 let hmac_ref: &mut HMAC<H> = self.hmac.as_mut().unwrap();
620 hmac_ref.do_update(ikm.ref_to_bytes());
621 // self.hmac.as_mut().unwrap().do_update(ikm.ref_to_bytes());
622
623 Ok(additional_entropy)
624 }
625
626 /// An update function that allows streaming of the IKM as bytes.
627 /// Note that since this interface takes the IKM as raw bytes, it cannot track its entropy
628 /// and therefore any IKM material provided through this interface will not count towards
629 /// the entropy of the output key.
630 ///
631 /// State machine: this function must be called after [`HKDF::do_extract_init`], followed by
632 /// zero or more calls of [`HKDF::do_extract_update_key`], and before [`HKDF::do_extract_final`].
633 ///
634 /// Returns the number of bits of entropy credited to this input key material, which is always 0 for this function.
635 pub fn do_extract_update_bytes(&mut self, ikm_chunk: &[u8]) -> Result<usize, MACError> {
636 if self.state == HkdfStates::Uninitialized {
637 return Err(MACError::InvalidState(
638 "Must call do_extract_init() before calling do_extract_update()",
639 ));
640 };
641 self.state = HkdfStates::TakingAdditionalInfo;
642
643 self.hmac.as_mut().unwrap().do_update(ikm_chunk);
644 Ok(0)
645 }
646
647 /// Finish the HKDF-Extract phase and produce the output `prk`.
648 #[allow(non_snake_case)]
649 pub fn do_extract_final(self) -> Result<KeyMaterial<MAX_HMAC_OUTPUT_LEN>, MACError> {
650 let mut prk = KeyMaterial::<MAX_HMAC_OUTPUT_LEN>::new();
651 self.do_extract_final_out(&mut prk)?;
652 Ok(prk)
653 }
654
655 /// Finish the HKDF-Extract phase and fill the provided `prk`.
656 /// Note that the provided KeyMaterial must be correctly sized to the HMAC block length.
657 #[allow(non_snake_case)]
658 pub fn do_extract_final_out(
659 self,
660 prk: &mut KeyMaterial<MAX_HMAC_OUTPUT_LEN>,
661 ) -> Result<usize, MACError> {
662 if self.state == HkdfStates::Uninitialized {
663 return Err(MACError::InvalidState(
664 "Must call do_extract_init() before calling do_extract_final().",
665 ));
666 };
667 debug_assert!(self.hmac.is_some());
668
669 let output_key_type = self.entropy.get_output_key_type(); // need to do this above self.hmac.do_final_out, which will consume self.
670
671 let mut bytes_written = 0;
672 key_material::do_hazardous_operations(prk, |okm| {
673 bytes_written = self
674 .hmac
675 .unwrap()
676 .do_final_out(&mut okm.ref_to_bytes_mut()?)
677 .map_err(|_| KeyMaterialError::GenericError("HMAC do_final_out failed"))?;
678 okm.set_key_len(bytes_written)?;
679 okm.set_key_type(output_key_type)?;
680 if output_key_type <= KeyType::Unknown {
681 okm.set_security_strength(SecurityStrength::None)
682 } else {
683 okm.set_security_strength(
684 min(
685 &SecurityStrength::from_bytes(okm.key_len()),
686 &self.entropy.security_strength,
687 )
688 .clone(),
689 )
690 }
691 })?;
692 // By RFC5869, the output size of prk is HashLen denotes the length of the
693 // hash function output in octets
694 debug_assert_eq!(prk.key_len(), H::OUTPUT_LEN);
695 Ok(bytes_written)
696 }
697}
698
699/// As per NIST SP 800-56Cr2 section 5.1, HKDF extract_and_expand can be used as a KDF.
700/// Additionally, section 4.1 says that when using HMAC as a KDF, the salt may be set to
701/// a string of HashLen zeros. All key material and additional_input is mapped to HKDF's ikm input.
702/// While this is not the only mode in which HKDF can be used as a KDF, this is considered the default mode
703/// that is exposed through [`KDF::derive_key`] and [`KDF::derive_key_out`].
704/// More advanced control of the inputs to HKDF can be achieved by using [`KDF::derive_key_from_multiple`] and
705/// [`KDF::derive_key_from_multiple_out`], or by using the [`HKDF`] impl directly.
706///
707/// Entropy tracking: this implementation will map entropy from the input keys to the output key.
708impl<H: Hash + HashAlgParams + Default> KDF for HKDF<H> {
709 /// This invokes [`HKDF::extract_and_expand_out`] with a zero salt and using the provided key as ikm.
710 /// This provides a fixed-length output, which may be truncated as needed.
711 fn derive_key(
712 self,
713 key: &impl KeyMaterialTrait,
714 additional_input: &[u8],
715 ) -> Result<Box<dyn KeyMaterialTrait>, KDFError> {
716 let mut output_key = KeyMaterial512::new();
717 _ = self.derive_key_out(key, additional_input, &mut output_key)?;
718 output_key.set_key_len(H::OUTPUT_LEN)?;
719 Ok(Box::new(output_key))
720 }
721
722 /// This invokes [`HKDF::extract_and_expand_out`] with a zero salt and using the provided key as ikm.
723 /// This fills the provided [`KeyMaterialTrait`] object in place of exposing a Length parameter.
724 fn derive_key_out(
725 self,
726 key: &impl KeyMaterialTrait,
727 additional_input: &[u8],
728 output_key: &mut impl KeyMaterialTrait,
729 ) -> Result<usize, KDFError> {
730 let bytes_written = HKDF::<H>::extract_and_expand_out(
731 &KeyMaterial::<0>::new(),
732 key,
733 additional_input,
734 output_key.capacity(),
735 output_key,
736 )?;
737 Ok(bytes_written)
738 }
739
740 /// As with [`KDF::derive_key`] and [`KDF::derive_key_out`],
741 /// This invokes HKDF in the extract_and_expand mode and maps the provided keys in the following way:
742 /// - The first (0'th) key is used as the salt for HKDF.extract.
743 /// - The remaining keys are concatenated to form HKDF's ikm parameter.
744 /// - Entropy of all provided keys are tracked to determine the output key's entropy.
745 ///
746 /// Therefore, derive_key_from_multiple(&[KeyMaterial0::new(), &key], &info) is equivalent to derive_key(&key, &info).
747 ///
748 /// This provides a fixed-length output, which may be truncated as needed.
749 fn derive_key_from_multiple(
750 self,
751 keys: &[&impl KeyMaterialTrait],
752 additional_input: &[u8],
753 ) -> Result<Box<dyn KeyMaterialTrait>, KDFError> {
754 let mut output_key = KeyMaterial512::new();
755 _ = self.derive_key_from_multiple_out(keys, additional_input, &mut output_key)?;
756 output_key.set_key_len(*min(&output_key.key_len(), &H::OUTPUT_LEN))?;
757 Ok(Box::new(output_key))
758 }
759
760 /// This behaves the same as [`KDF::derive_key_from_multiple`], except that it fills the provided
761 /// [`KeyMaterialTrait`] object in place of exposing a Length parameter.
762 fn derive_key_from_multiple_out(
763 self,
764 keys: &[&impl KeyMaterialTrait],
765 additional_input: &[u8],
766 output_key: &mut impl KeyMaterialTrait,
767 ) -> Result<usize, KDFError> {
768 let mut hkdf = HKDF::<H>::new();
769 let mut entropy = HkdfEntropyTracker::<H>::new();
770
771 if keys.len() >= 1 {
772 hkdf.do_extract_init(keys[0])?;
773 entropy.credit_entropy(keys[0]);
774 } else {
775 hkdf.do_extract_init(&KeyMaterial0::new())?;
776 };
777
778 if keys.len() != 0 {
779 for key in &keys[1..] {
780 hkdf.do_extract_update_bytes(key.ref_to_bytes())?;
781 entropy.credit_entropy(*key);
782 }
783 }
784 let mut prk = KeyMaterial::<MAX_HMAC_OUTPUT_LEN>::new();
785 _ = hkdf.do_extract_final_out(&mut prk)?;
786 let bytes_written =
787 HKDF::<H>::expand_out(&prk, additional_input, output_key.capacity(), output_key)?;
788
789 key_material::do_hazardous_operations(output_key, |output_key| {
790 output_key.set_key_type(entropy.get_output_key_type())?;
791 output_key.set_security_strength(
792 min(
793 &SecurityStrength::from_bytes(output_key.key_len()),
794 &entropy.security_strength,
795 )
796 .clone(),
797 )
798 })?;
799
800 Ok(bytes_written)
801 }
802
803 fn max_security_strength(&self) -> SecurityStrength {
804 H::default().max_security_strength()
805 }
806}
807
808/// Length in bytes of the serialized state of [`HKDF_SHA256`].
809pub const SUSPENDED_HKDF_SHA256_STATE_LEN: usize = SUSPENDED_HMAC_SHA256_STATE_LEN + 14;
810/// Length in bytes of the serialized state of [`HKDF_SHA512`].
811pub const SUSPENDED_HKDF_SHA512_STATE_LEN: usize = SUSPENDED_HMAC_SHA512_STATE_LEN + 14;
812
813/// HKDF is *keyed by its salt* -- the salt keys the extract-phase HMAC -- so it implements
814/// [`SuspendableKeyed`] (not [`SerializableState`]). An in-progress
815/// extract operation can be suspended and resumed, but the salt is NOT written into the serialized
816/// state and must be re-supplied to [`SuspendableKeyed::from_serialized_state`].
817///
818/// Only the extract phase carries resumable state (expand is a one-shot static operation). As with
819/// HMAC, resuming with the wrong salt cannot be detected and will silently produce a wrong PRK.
820///
821/// Serialized layout: HKDF writes its own 3-byte library version header first and checks it before
822/// parsing anything else. This matters because the inner HMAC blob (which carries its own header) is
823/// absent before extract is initialized -- without HKDF's own header, a pre-init state would have no
824/// version tag at all. Using `B` = the inner HMAC blob length:
825/// [0 .. 3) HKDF library version header (checked on resume)
826/// [3] inner-HMAC present flag (0 = extract not yet initialized)
827/// [4 .. 4 + B) the inner HMAC's SuspendableKeyed blob (salt excluded); zeroed when absent
828/// [4 + B] state-machine tag (see `HkdfStates`)
829/// [5 + B .. 13 + B) entropy counter (usize serialized as u64, little-endian)
830/// [13 + B] accumulated security strength (1-byte tag)
831/// So the total per HKDF variant is the 3-byte version header + 11 bytes of HKDF bookkeeping
832/// (present flag, state tag, entropy counter, security strength) + the inner HMAC's blob = `B + 14`.
833macro_rules! impl_suspendable_keyed_state_for_hkdf {
834 // $hash: the concrete hash; $serialized_hmac_len: the inner HMAC's serialized-state length for that
835 // hash; $serialized_hkdf_len: the full HKDF serialized-state length (= 3 + 11 + $serialized_hmac_len).
836 ($hash:ty, $serialized_hmac_len:expr, $serialized_hkdf_len:expr) => {
837 impl SuspendableKeyed<{ $serialized_hkdf_len }> for HKDF<$hash> {
838 // HMAC accepts any key material, so the key type is the trait object `dyn KeyMaterialTrait`
839 // rather than a single concrete key type. The key is only used (by reference) to reload the key
840 // bytes at from_serialized_state, so dynamic dispatch here is negligible.
841 type Key = dyn KeyMaterialTrait;
842
843 fn suspend(self) -> [u8; $serialized_hkdf_len] {
844 debug_assert_eq!($serialized_hkdf_len, $serialized_hmac_len + 14);
845 let mut state = [0u8; $serialized_hkdf_len];
846
847 // HKDF's own library version header comes first: the inner HMAC blob is absent before
848 // extract is initialized, so we can't rely on its header being present.
849 add_lib_ver(&mut state);
850
851 // The present flag, then (when present) the inner salt-keyed HMAC blob right after it.
852 if let Some(hmac) = self.hmac {
853 state[3] = 1; // present flag
854 state[4..4 + $serialized_hmac_len].copy_from_slice(&hmac.suspend());
855 }
856 // else None:
857 // the presence flag = 0
858 // the content = [u8; 0]
859 // which is how it already is, so nothing to do.
860
861 state[4 + $serialized_hmac_len] = self.state as u8;
862 state[5 + $serialized_hmac_len..13 + $serialized_hmac_len]
863 .copy_from_slice(&(self.entropy.entropy as u64).to_le_bytes());
864 state[13 + $serialized_hmac_len] = self.entropy.security_strength as u8;
865
866 state
867 }
868
869 fn from_suspended(
870 state: [u8; $serialized_hkdf_len],
871 salt: &Self::Key,
872 ) -> Result<Self, SuspendableError> {
873 // Check HKDF's own version header before parsing anything else.
874 check_lib_ver(&state, None)?;
875
876 // Rebuild the salt-keyed HMAC (when present) by re-supplying the salt.
877 let hmac = match state[3] {
878 0 => None,
879 // infallible: the sub-slice is exactly $serialized_hmac_len bytes by const construction.
880 1 => Some(HMAC::<$hash>::from_suspended(
881 state[4..4 + $serialized_hmac_len].try_into().unwrap(),
882 salt,
883 )?),
884 _ => return Err(SuspendableError::InvalidData),
885 };
886
887 let hkdf_state = HkdfStates::try_from(state[4 + $serialized_hmac_len])?;
888
889 // check that the hkdf_state aligns with the presence of an hmac
890 if
891 // an hmac object should not be present in the init state.
892 (hmac.is_some() && hkdf_state == HkdfStates::Uninitialized) ||
893 // any other state must have an hmac object.
894 (hmac.is_none() && hkdf_state != HkdfStates::Uninitialized)
895 {
896 return Err(SuspendableError::InvalidData);
897 }
898
899 // infallible: the sub-slice is exactly 8 bytes by const construction.
900 let entropy = u64::from_le_bytes(
901 state[5 + $serialized_hmac_len..13 + $serialized_hmac_len].try_into().unwrap(),
902 ) as usize;
903 let security_strength =
904 SecurityStrength::try_from(state[13 + $serialized_hmac_len])?;
905
906 Ok(HKDF {
907 hmac,
908 entropy: HkdfEntropyTracker {
909 _phantomhash: PhantomData,
910 entropy,
911 security_strength,
912 },
913 state: hkdf_state,
914 })
915 }
916 }
917 };
918}
919
920impl_suspendable_keyed_state_for_hkdf!(
921 SHA256,
922 SUSPENDED_HMAC_SHA256_STATE_LEN,
923 SUSPENDED_HKDF_SHA256_STATE_LEN
924);
925impl_suspendable_keyed_state_for_hkdf!(
926 SHA512,
927 SUSPENDED_HMAC_SHA512_STATE_LEN,
928 SUSPENDED_HKDF_SHA512_STATE_LEN
929);