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    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    /// Construct the HKDF from serialized PRK bytes.
61    pub fn from_prk_bytes(prk: H::Bytes) -> Self {
62        Hkdf {
63            prk: Hmac::from_byte_array(prk),
64        }
65    }
66
67    /// Serialize the PRK bytes backing this HKDF instance.
68    pub fn to_prk_bytes(&self) -> H::Bytes {
69        self.prk.to_byte_array()
70    }
71
72    /// Run HKDF-expand to generate new key material
73    ///
74    /// ## Inputs
75    /// * `info`: Defines which key to derive. Different values lead to
76    ///   different keys.
77    /// * `LEN`: Defines the length of the key material to generate in octets.
78    ///   Note that `LEN <= H::LEN * 255` has to be true.
79    ///
80    /// ## Panics
81    /// If `LEN > H::LEN * 255`.
82    pub fn derive<const LEN: usize>(&self, info: &[u8]) -> [u8; LEN] {
83        // TODO: make const once rust allows
84        let iterations = if LEN.is_multiple_of(H::LEN) {
85            LEN / H::LEN
86        } else {
87            LEN / H::LEN + 1
88        };
89
90        // Make sure we can cast iteration numbers to u8 later
91        assert!(
92            iterations <= 255,
93            "RFC5869 only supports output length of up to 255*HashLength"
94        );
95
96        let mut output = [0u8; LEN];
97        for iteration in 0..iterations {
98            let current_slice = (H::LEN * iteration)..min(H::LEN * (iteration + 1), LEN);
99            let last_slice = if iteration == 0 {
100                0..0
101            } else {
102                (H::LEN * (iteration - 1))..(H::LEN * iteration)
103            };
104
105            // TODO: re-use midstate
106            let mut engine = HmacEngine::<H>::new(&self.prk[..]);
107            engine.input(&output[last_slice]);
108            engine.input(info);
109            engine.input(&[(iteration + 1) as u8]);
110            let output_bytes = Hmac::from_engine(engine);
111
112            let bytes_to_copy = current_slice.end - current_slice.start;
113            output[current_slice].copy_from_slice(&output_bytes[0..bytes_to_copy]);
114        }
115
116        output
117    }
118
119    /// Run HKDF-expand to generate new key material with `L = H::LEN`
120    ///
121    /// See [`Hkdf::derive`] for more information.
122    pub fn derive_hmac(&self, info: &[u8]) -> Hmac<H> {
123        let mut engine = HmacEngine::<H>::new(&self.prk[..]);
124        engine.input(info);
125        engine.input(&[1u8]);
126        Hmac::from_engine(engine)
127    }
128}
129
130#[cfg(test)]
131mod tests;