diem_crypto/hkdf.rs
1// Copyright (c) The Diem Core Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4//! An implementation of HKDF, the HMAC-based Extract-and-Expand Key Derivation Function for the
5//! Diem project based on [RFC 5869](https://tools.ietf.org/html/rfc5869).
6//!
7//! The key derivation function (KDF) is intended to support a wide range of applications and
8//! requirements, and is conservative in its use of cryptographic hash functions. In particular,
9//! this implementation is compatible with hash functions that output 256 bits or more, such as
10//! SHA256, SHA3-256 and SHA512.
11//!
12//! HKDF follows the "extract-then-expand" paradigm, where the KDF logically consists of two
13//! modules: the first stage takes the input keying material (the seed) and "extracts" from it a
14//! fixed-length pseudorandom key, and then the second stage "expands" this key into several
15//! additional pseudorandom keys (the output of the KDF). For convenience, a function that runs both
16//! steps in a single call is provided. Note that along with an initial high-entropy seed, a user
17//! can optionally provide salt and app-info byte-arrays for extra security guarantees and domain
18//! separation.
19//!
20//! # Applications
21//!
22//! HKDF is intended for use in a wide variety of KDF applications (see [Key derivation function](https://en.wikipedia.org/wiki/Key_derivation_function)), including:
23//! a) derivation of keys from an origin high-entropy master seed. This is the recommended approach
24//! for generating keys in Diem, especially when a True Random Generator is not available.
25//! b) derivation of session keys from a shared Diffie-Hellman value in a key-agreement protocol.
26//! c) combining entropy from multiple sources of randomness, such as entropy collected
27//! from system events, user's keystrokes, /dev/urandom etc. The combined seed can then be used to
28//! generate cryptographic keys for account, network and transaction signing keys among the others.
29//! d) hierarchical private key derivation, similarly to Bitcoin's BIP32 protocol for easier key
30//! management.
31//! e) hybrid key generation that combines a master seed with a PRNG output for extra security
32//! guarantees against a master seed leak or low PRNG entropy.
33//!
34//! # Recommendations
35//!
36//! **Salt**
37//! HKDF can operate with and without random 'salt'. The use of salt adds to the strength of HKDF,
38//! ensuring independence between different uses of the hash function, supporting
39//! "source-independent" extraction, and strengthening the HKDF use. The salt value should be a
40//! random string of the same length as the hash output. A shorter or less random salt value can
41//! still make a contribution to the security of the output key material. Salt values should be
42//! independent of the input keying material. In particular, an application needs to make sure that
43//! salt values are not chosen or manipulated by an attacker.
44//!
45//! *Application info*
46//! Key expansion accepts an optional 'info' value to which the application assigns some meaning.
47//! Its objective is to bind the derived key material to application- and context-specific
48//! information. For example, 'info' may contain a protocol number, algorithm identifier,
49//! child key number (similarly to [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)), etc. The only technical requirement for 'info' is that
50//! it be independent of the seed.
51//!
52//! **Which function to use: extract, expand or both?**
53//! Unless absolutely sure of what they are doing, applications should use both steps — if only for
54//! the sake of compatibility with the general case.
55//!
56//! # Example
57//!
58//! Run HKDF extract-then-expand so as to return 64 bytes, using 'salt', 'seed' and 'info' as
59//! inputs.
60//! ```
61//! use diem_crypto::hkdf::Hkdf;
62//! use sha2::Sha256;
63//!
64//! // some bytes required for this example.
65//! let raw_bytes = [2u8; 10];
66//! // define salt
67//! let salt = Some(&raw_bytes[0..4]);
68//! // define seed - in production this is recommended to be a 32 bytes or longer random seed.
69//! let seed = [3u8; 32];
70//! // define application info
71//! let info = Some(&raw_bytes[4..10]);
72//!
73//! // HKDF extract-then-expand 64-bytes output
74//! let derived_bytes = Hkdf::<Sha256>::extract_then_expand(salt, &seed, info, 64);
75//! assert_eq!(derived_bytes.unwrap().len(), 64)
76//! ```
77
78use digest::{
79 generic_array::{self, ArrayLength},
80 BlockInput, FixedOutput, Reset, Update,
81};
82
83use generic_array::typenum::{IsGreaterOrEqual, True, U32};
84
85use std::marker::PhantomData;
86use thiserror::Error;
87
88/// Hash function is not supported if its output is less than 32 bits.
89type DMinimumSize = U32;
90
91/// Seed (ikm = initial key material) is not accepted if its size is less than 16 bytes. This is a
92/// precautionary measure to prevent HKDF misuse. 128 bits is the minimum accepted seed entropy
93/// length in the majority of today's applications to avoid brute forcing.
94/// Note that for Ed25519 keys, random seeds of at least 32 bytes are recommended.
95const MINIMUM_SEED_LENGTH: usize = 16;
96
97/// Structure representing the HKDF, capable of HKDF-Extract and HKDF-Expand operations, as defined
98/// in RFC 5869.
99#[derive(Clone, Debug)]
100pub struct Hkdf<D>
101where
102 D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
103 D::BlockSize: ArrayLength<u8>,
104 D::OutputSize: ArrayLength<u8>,
105 D::OutputSize: IsGreaterOrEqual<DMinimumSize, Output = True>,
106{
107 _marker: PhantomData<D>,
108}
109
110impl<D> Hkdf<D>
111where
112 D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
113 D::BlockSize: ArrayLength<u8> + Clone,
114 D::OutputSize: ArrayLength<u8>,
115 D::OutputSize: IsGreaterOrEqual<DMinimumSize, Output = True>,
116{
117 /// The RFC5869 HKDF-Extract operation.
118 pub fn extract(salt: Option<&[u8]>, ikm: &[u8]) -> Result<Vec<u8>, HkdfError> {
119 if ikm.len() < MINIMUM_SEED_LENGTH {
120 return Err(HkdfError::InvalidSeedLengthError);
121 }
122 Ok(Hkdf::<D>::extract_no_ikm_check(salt, ikm))
123 }
124
125 fn extract_no_ikm_check(salt: Option<&[u8]>, ikm: &[u8]) -> Vec<u8> {
126 let (arr, _hkdf) = hkdf::Hkdf::<D>::extract(salt, ikm);
127 arr.to_vec()
128 }
129
130 /// The RFC5869 HKDF-Expand operation.
131 pub fn expand(prk: &[u8], info: Option<&[u8]>, length: usize) -> Result<Vec<u8>, HkdfError> {
132 // According to RFC5869, MAX_OUTPUT_LENGTH <= 255 * HashLen — which is
133 // checked below.
134 // We specifically exclude a zero size length as well.
135 if length == 0 {
136 return Err(HkdfError::InvalidOutputLengthError);
137 }
138
139 let hkdf =
140 hkdf::Hkdf::<D>::from_prk(prk).map_err(|_| HkdfError::WrongPseudorandomKeyError)?;
141 let mut okm = vec![0u8; length];
142 hkdf.expand(info.unwrap_or_else(|| &[]), &mut okm)
143 // length > D::OutputSize::to_usize() * 255
144 .map_err(|_| HkdfError::InvalidOutputLengthError)?;
145 Ok(okm)
146 }
147
148 /// HKDF Extract then Expand operation as a single step.
149 pub fn extract_then_expand(
150 salt: Option<&[u8]>,
151 ikm: &[u8],
152 info: Option<&[u8]>,
153 length: usize,
154 ) -> Result<Vec<u8>, HkdfError> {
155 let prk = Hkdf::<D>::extract(salt, ikm)?;
156 Hkdf::<D>::expand(&prk, info, length)
157 }
158
159 /// CAUTION: This is not recommended because it does not take an ikm (seed) as an input and
160 /// thus, it is not fully compliant with the HKDF RFC (which always expects a non-zero ikm).
161 /// Please use `extract_then_expand` instead, unless you know what you are doing.
162 ///
163 /// This api is currently required by the Noise protocol [HKDF specs](https://noiseprotocol.org/noise.html#hash-functions).
164 ///
165 /// HKDF Extract then Expand operation as a single step, but without an ikm input.
166 pub fn extract_then_expand_no_ikm(
167 salt: Option<&[u8]>,
168 info: Option<&[u8]>,
169 length: usize,
170 ) -> Result<Vec<u8>, HkdfError> {
171 let prk = Hkdf::<D>::extract_no_ikm_check(salt, &[]);
172 Hkdf::<D>::expand(&prk, info, length)
173 }
174}
175
176/// An error type for HKDF key derivation issues.
177///
178/// This enum reflects there are various causes of HKDF failures, including:
179/// a) requested HKDF output size exceeds the maximum allowed or is zero.
180/// b) hash functions outputting less than 32 bits are not supported (i.e., SHA1 is not supported).
181/// c) small PRK value in HKDF-Expand according to RFC 5869.
182/// d) any other underlying HMAC error.
183#[derive(Clone, Debug, PartialEq, Eq, Error)]
184pub enum HkdfError {
185 /// HKDF expand output exceeds the maximum allowed or is zero.
186 #[error("HKDF expand error - requested output size exceeds the maximum allowed or is zero")]
187 InvalidOutputLengthError,
188 /// PRK on HKDF-Expand should not be less than the underlying hash output bits.
189 #[error(
190 "HKDF expand error - the pseudorandom key input ('prk' in RFC 5869) \
191 is less than the underlying hash output bits"
192 )]
193 WrongPseudorandomKeyError,
194 /// HMAC key related error; unlikely to happen because every key size is accepted in HMAC.
195 #[error("HMAC key error")]
196 MACKeyError,
197 /// HKDF extract input seed should not be less than the minimum accepted.
198 #[error("HKDF extract error - input seed is too small")]
199 InvalidSeedLengthError,
200}