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;