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;