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