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}