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