Skip to main content

hkdf/
lib.rs

1//! This crate implements the [RFC5869] hash based key derivation function using
2//! [`bitcoin_hashes`].
3//!
4//! [RFC5869]: https://www.rfc-editor.org/rfc/rfc5869
5//! [`bitcoin_hashes`]: https://docs.rs/bitcoin_hashes/latest/bitcoin_hashes/
6
7use std::cmp::min;
8
9pub use bitcoin_hashes;
10pub use bitcoin_hashes::Hash as BitcoinHash;
11use bitcoin_hashes::{HashEngine, Hmac, HmacEngine};
12
13pub mod hashes;
14
15/// Implements the [RFC5869] hash based key derivation function using the hash
16/// function `H`.
17///
18/// [RFC5869]: https://www.rfc-editor.org/rfc/rfc5869
19#[derive(Clone)]
20pub struct Hkdf<H: BitcoinHash> {
21    prk: Hmac<H>,
22}
23
24impl<H: BitcoinHash> Hkdf<H> {
25    /// Run HKDF-extract and keep the resulting pseudo random key as internal
26    /// state
27    ///
28    /// ## Inputs
29    /// * `ikm`: Input keying material, secret key material our keys will be
30    ///   derived from
31    /// * `salt`: Optional salt value, if not required set to `&[0; H::LEN]`. As
32    ///   noted in the RFC the salt value can also be a secret.
33    pub fn new(ikm: &[u8], salt: Option<&[u8]>) -> Self {
34        let mut engine = HmacEngine::new(salt.unwrap_or(&vec![0x00; H::LEN]));
35        engine.input(ikm);
36
37        Hkdf {
38            prk: Hmac::from_engine(engine),
39        }
40    }
41
42    /// Construct the HKDF from a pseudo random key that has the correct
43    /// distribution and length already (e.g. because it's the output of a
44    /// previous HKDF round), skipping the HKDF-extract step. **If in doubt,
45    /// please use `Hkdf::new` instead!**
46    ///
47    /// See also [`Hkdf::derive_hmac`].
48    pub fn from_prk(prk: Hmac<H>) -> Self {
49        Hkdf { prk }
50    }
51
52    /// Construct the HKDF from serialized PRK bytes.
53    pub fn from_prk_bytes(prk: H::Bytes) -> Self {
54        Hkdf {
55            prk: Hmac::from_byte_array(prk),
56        }
57    }
58
59    /// Serialize the PRK bytes backing this HKDF instance.
60    pub fn to_prk_bytes(&self) -> H::Bytes {
61        self.prk.to_byte_array()
62    }
63
64    /// Run HKDF-expand to generate new key material
65    ///
66    /// ## Inputs
67    /// * `info`: Defines which key to derive. Different values lead to
68    ///   different keys.
69    /// * `LEN`: Defines the length of the key material to generate in octets.
70    ///   Note that `LEN <= H::LEN * 255` has to be true.
71    ///
72    /// ## Panics
73    /// If `LEN > H::LEN * 255`.
74    pub fn derive<const LEN: usize>(&self, info: &[u8]) -> [u8; LEN] {
75        // TODO: make const once rust allows
76        let iterations = if LEN.is_multiple_of(H::LEN) {
77            LEN / H::LEN
78        } else {
79            LEN / H::LEN + 1
80        };
81
82        // Make sure we can cast iteration numbers to u8 later
83        assert!(
84            iterations <= 255,
85            "RFC5869 only supports output length of up to 255*HashLength"
86        );
87
88        let mut output = [0u8; LEN];
89        for iteration in 0..iterations {
90            let current_slice = (H::LEN * iteration)..min(H::LEN * (iteration + 1), LEN);
91            let last_slice = if iteration == 0 {
92                0..0
93            } else {
94                (H::LEN * (iteration - 1))..(H::LEN * iteration)
95            };
96
97            // TODO: re-use midstate
98            let mut engine = HmacEngine::<H>::new(&self.prk[..]);
99            engine.input(&output[last_slice]);
100            engine.input(info);
101            engine.input(&[(iteration + 1) as u8]);
102            let output_bytes = Hmac::from_engine(engine);
103
104            let bytes_to_copy = current_slice.end - current_slice.start;
105            output[current_slice].copy_from_slice(&output_bytes[0..bytes_to_copy]);
106        }
107
108        output
109    }
110
111    /// Run HKDF-expand to generate new key material with `L = H::LEN`
112    ///
113    /// See [`Hkdf::derive`] for more information.
114    pub fn derive_hmac(&self, info: &[u8]) -> Hmac<H> {
115        let mut engine = HmacEngine::<H>::new(&self.prk[..]);
116        engine.input(info);
117        engine.input(&[1u8]);
118        Hmac::from_engine(engine)
119    }
120}
121
122#[cfg(test)]
123mod tests;