Skip to main content

Crate bouncycastle_hkdf

Crate bouncycastle_hkdf 

Source
Expand description

HMAC-based Extract-and-Expand Key Derivation Function (HKDF) as per RFC5859, as allowed by NIST SP 800-56Cr2.

§Usage

Since HKDF uses HMAC<HASH> as its underlying primitive, most of what is said in the HMAC crate docs about instantiating HMAC objects applies here as well. Unlike HMAC, an HKDF object is created without an initial key, and will self-initialize the internal HMAC object as part of the HKDF::extract phase.

§Examples

§Constructing an object

HMAC objects can be constructed with any underlying hash function that implements Hash. Type aliases are provided for the common HKDF-HASH algorithms.

The following object instantiations are equivalent:

use bouncycastle_hkdf::HKDF_SHA256;

let hkdf = HKDF_SHA256::new();

and

use bouncycastle_hkdf::HKDF;
use bouncycastle_sha2::SHA256;

let hkdf = HKDF::<SHA256>::new();

§Deriving a key via the KDF trait

Being a Key Derivation Function (KDF), the objective of HKDF is to take input key material which is not directly usable for its intended purpose and transform into a suitable output key. Typically, this takes one or both of the following forms:

  • 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.
  • 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.

The simplest usage is via the one-shot functions provided by the KDF trait.

use bouncycastle_core::key_material::{KeyMaterial256, KeyType};
use bouncycastle_core::traits::{KDF };
use bouncycastle_hkdf::HKDF_SHA256;

let key = KeyMaterial256::from_bytes_as_type(
            b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
            KeyType::Seed).unwrap();

let hkdf = HKDF_SHA256::new();
let key = hkdf.derive_key(&key, b"extra input").unwrap();

KDF::derive_key will produce a key the same length as the underlying hash function. Longer output can be requested by instead using KDF::derive_key_out and providing a larger output buffer, which will be filled.

As with other uses of KeyMaterialTrait, the KDF::derive_key function will track the entropy of the input key material, and will set the entropy of the output key material accordingly.

The KDF trait also provides the KDF::derive_key_from_multiple and KDF::derive_key_from_multiple_out functions, which allows for multiple inputs to be mixed into a single output key, and which allows for some advanced control of the underlying HKDF primitive.

§HKDF Extract-and-Expand

The HKDF algorithm defined in RFC5896 and SP 800-56Cr2 is a two-step KDF, broken into an Extract step which essentially absorbs entropy from the input key material, and an Expand step which produces the output key material of any requested size. This interface is essentially a pre-cursor to the XOF API which was introduced with SHA3; the main difference being that HKDF-Expand needs to be told up-front how much output to produce, whereas XOFs can stream output as needed.

Naturally, the full two-step HKDF-Extract and HKDF-Expand interface is provided by the HKDF struct, and exposes additional HKDF-specific parameters beyond what is exposed by the functions of the KDF trait.

The usage pattern here is flexible, but generally follows the pattern of first calling HKDF::extract with a salt and an input key material ikm, which produces a pseudorandom key prk. The prk will have a KeyType and SecurityStrength that results from combining the two provided input keys, The prk may be! used directly as a full-entropy cryptographic key.

Since the extract step may be called with any number of input keys, a streaming interface is provided whereby streaming mode in initialized with a call to HKDF::do_extract_init, and then repeated calls to HKDF::do_extract_update_key and HKDF::do_extract_update_bytes may be made. Entropy from the inputs keys provided via HKDF::do_extract_update_key are credited towards the output key, while bytes provided via HKDF::do_extract_update_bytes are not. One restriction here is that once you start provided un-credited bytes via HKDF::do_extract_update_bytes, no more calls to HKDF::do_extract_update_key may be made. The streaming API is completed with a call to either HKDF::do_extract_final or HKDF::do_extract_final_out.

The second stage, HKDF::expand_out stretches the prk into a longer output key, still of the same KeyType and SecurityStrength.

A typical flow looks like this:

use bouncycastle_core::key_material::{KeyMaterialTrait, KeyMaterial256, KeyMaterial, KeyType};
use bouncycastle_core::traits::KDF;
use bouncycastle_hkdf::{HKDF, HKDF_SHA256};
use bouncycastle_sha2::{SHA256};

// setup variables
let salt = KeyMaterial256::from_bytes_as_type(
            b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
            KeyType::MACKey).unwrap();

 let ikm = KeyMaterial256::from_bytes_as_type(
            b"\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00",
            KeyType::MACKey).unwrap();

let info = b"some extra context info";

 // Use the streaming API to derive an output key of length 200 bytes.
 let mut okm = KeyMaterial::<200>::new();
 let mut hkdf = HKDF::<SHA256>::default();
 hkdf.do_extract_init(&salt).unwrap();
 hkdf.do_extract_update_bytes(ikm.ref_to_bytes()).unwrap();
 let prk = hkdf.do_extract_final().unwrap();
 HKDF_SHA256::expand_out(&prk, info, 200, &mut okm).unwrap();

Various convenience wrapper functions are provided which can reduce the amount of boilerplate code for common cases. For example, the above code can be condensed to:

use bouncycastle_core::key_material::{KeyMaterialTrait, KeyMaterial256, KeyMaterial, KeyType};
use bouncycastle_hkdf::{HKDF_SHA256};

// setup variables
let salt = KeyMaterial256::from_bytes_as_type(
            b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
            KeyType::MACKey).unwrap();

 let ikm = KeyMaterial256::from_bytes_as_type(
            b"\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00",
            KeyType::MACKey).unwrap();

let info = b"some extra context info";

// Use the one-shot API to derive an output key of length 200 bytes.
let mut okm = KeyMaterial::<200>::new();
let _bytes_written = HKDF_SHA256::extract_and_expand_out(&salt, &ikm, info, 200, &mut okm).unwrap();

§Suspending and resuming execution

The HKDF-Extract phase supports a streaming API whereby any amount of additional input keying material can be provided either via HKDF::do_extract_update_key – which will credit the entropy of the provided KeyMaterial – or as raw uncredited bytes via HKDF::do_extract_update_bytes.

As such, The HKDF-Extract phase can be suspended to a cache and resumed later via the SuspendableKeyed trait.

The HKDF algorithm is keyed by a salt, which is required twice: once at initialization and again during finalization. Suspension and resumption are supported via the SuspendableKeyed trait which requires the caller to store the salt securely and provide it again during resumption. Note that providing a different salt during resumption cannot be detected by the library and would silently produce a different PRK.

use bouncycastle_hkdf::HKDF_SHA256;
use bouncycastle_core::key_material::{KeyMaterial256, KeyType};
use bouncycastle_core::traits::SuspendableKeyed;

let salt = KeyMaterial256::from_bytes_as_type(
            b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
            KeyType::MACKey).unwrap();
let ikm_part1 = b"input keying material part 1";
let ikm_part2 = b" ...and part 2";

let mut hkdf = HKDF_SHA256::new();
hkdf.do_extract_init(&salt).unwrap();
hkdf.do_extract_update_bytes(ikm_part1).unwrap();

// suspend the in-progress extract (the salt is NOT included in the serialized state)
let serialized_state = hkdf.suspend();

// ...
// do other things in the meantime
// ...

// ... later, possibly on another host: resume from the serialized state by re-supplying
// the same salt (make sure you store it securely!).
let mut hkdf = HKDF_SHA256::from_suspended(serialized_state, &salt).unwrap();
hkdf.do_extract_update_bytes(ikm_part2).unwrap();
let _prk = hkdf.do_extract_final().unwrap();

Structs§

HKDF
Internal struct for HKDF. Can, in theory, be instantiated with hash functions other than the ones provided by this crate (even custom ones).

Constants§

HKDF_SHA256_NAME
HKDF_SHA512_NAME
MAX_HMAC_OUTPUT_LEN
The size of the output key material from the HKDF-Extract phase prk, in bytes. This has been sized so that the output KeyMaterial has enough capacity to accommodate the underlying hash primitive with the largest output size. If the given hash function has a smaller output size, then the output KeyMaterial will be under-full (ie have a key_len that does not use its full capacity). TODO: This is a dirty dirty hack because correctly sizing the output key really requires the generic_const_exprs feature, which is currently only available on nightly Rust, and not on stable. Once they merge that feature, we will be able to get rid of this and declare prk: &mut KeyMaterial<H::OUTPUT_LEN> instead of this hack.
SUSPENDED_HKDF_SHA256_STATE_LEN
Length in bytes of the serialized state of HKDF_SHA256.
SUSPENDED_HKDF_SHA512_STATE_LEN
Length in bytes of the serialized state of HKDF_SHA512.

Type Aliases§

HKDF_SHA256
Public type for HKDF using SHA256.
HKDF_SHA512
Public type for HKDF using SHA512.