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