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;