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
// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0
// Copyright 2021 Conflux Foundation. All rights reserved.
// Conflux is free software and distributed under GNU General Public License.
// See http://www.gnu.org/licenses/
//! 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 are not supported if their output is less than 32 bits.
type DMinimumSize = U32;
/// 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> {
let (arr, _hkdf) = hkdf::Hkdf::<D>::extract(salt, ikm);
Ok(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_else(|| &[]), &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)
}
}
/// 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,
}