Skip to main content

hkdf/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3#![doc(
4    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
5    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
6)]
7#![cfg_attr(docsrs, feature(doc_cfg))]
8
9use hmac::{
10    Hmac, SimpleHmac,
11    digest::{Output, OutputSizeUser, array::typenum::Unsigned},
12};
13
14mod errors;
15mod hmac_impl;
16
17pub use errors::{InvalidLength, InvalidPrkLength};
18pub use hmac;
19pub use hmac_impl::HmacImpl;
20
21#[cfg(feature = "kdf")]
22pub use kdf::{self, Kdf};
23
24/// [`GenericHkdfExtract`] variant which uses [`Hmac`] for the underlying HMAC implementation.
25pub type HkdfExtract<H> = GenericHkdfExtract<Hmac<H>>;
26/// [`GenericHkdf`] variant which uses [`Hmac`] for the underlying HMAC implementation.
27pub type Hkdf<H> = GenericHkdf<Hmac<H>>;
28
29/// [`GenericHkdfExtract`] variant which uses [`SimpleHmac`] for the underlying HMAC implementation.
30pub type SimpleHkdfExtract<H> = GenericHkdfExtract<SimpleHmac<H>>;
31/// [`GenericHkdf`] variant which uses [`SimpleHmac`] for the underlying HMAC implementation.
32pub type SimpleHkdf<H> = GenericHkdf<SimpleHmac<H>>;
33
34/// Structure representing the streaming context of an HKDF-Extract operation.
35///
36/// This type is generic over HMAC implementation. Most users should use
37/// [`HkdfExtract`] or [`SimpleHkdfExtract`] type aliases.
38#[derive(Clone, Debug)]
39pub struct GenericHkdfExtract<H: HmacImpl> {
40    hmac: H,
41}
42
43impl<H: HmacImpl> GenericHkdfExtract<H> {
44    /// Initiates the HKDF-Extract context with the given optional salt
45    #[must_use]
46    pub fn new(salt: Option<&[u8]>) -> Self {
47        let default_salt = Output::<H>::default();
48        let salt = salt.unwrap_or(&default_salt);
49        let hmac = H::new_from_slice(salt);
50        Self { hmac }
51    }
52
53    /// Feeds in additional input key material to the HKDF-Extract context
54    pub fn input_ikm(&mut self, ikm: &[u8]) {
55        self.hmac.update(ikm);
56    }
57
58    /// Completes the HKDF-Extract operation, returning both the generated pseudorandom key and
59    /// `Hkdf` struct for expanding.
60    #[allow(clippy::missing_panics_doc, reason = "PRK size is correct")]
61    pub fn finalize(self) -> (Output<H>, GenericHkdf<H>) {
62        let prk = self.hmac.finalize();
63        let hkdf = GenericHkdf::<H>::from_prk(&prk).expect("PRK size is correct");
64        (prk, hkdf)
65    }
66}
67
68#[cfg(feature = "kdf")]
69impl<H: HmacImpl> Kdf for GenericHkdfExtract<H> {
70    fn derive_key(&self, secret: &[u8], info: &[u8], out: &mut [u8]) -> kdf::Result<()> {
71        let mut extract = self.clone();
72        extract.input_ikm(secret);
73        let (_, hkdf) = extract.finalize();
74        hkdf.expand(info, out).map_err(|_| kdf::Error)
75    }
76}
77
78/// Structure representing the HKDF, capable of HKDF-Expand and HKDF-Extract operations.
79/// Recommendations for the correct usage of the parameters can be found in the
80/// [crate root](index.html#usage).
81///
82/// This type is generic over HMAC implementation. Most users should use
83/// [`Hkdf`] or [`SimpleHkdf`] type aliases.
84#[derive(Clone, Debug)]
85pub struct GenericHkdf<H: HmacImpl> {
86    hmac: H,
87}
88
89impl<H: HmacImpl> GenericHkdf<H> {
90    /// Convenience method for [`extract`][Hkdf::extract] when the generated
91    /// pseudorandom key can be ignored and only HKDF-Expand operation is needed. This is the most
92    /// common constructor.
93    #[must_use]
94    pub fn new(salt: Option<&[u8]>, ikm: &[u8]) -> Self {
95        let (_, hkdf) = Self::extract(salt, ikm);
96        hkdf
97    }
98
99    /// Create `Hkdf` from an already cryptographically strong pseudorandom key
100    /// as per section 3.3 from RFC5869.
101    ///
102    /// # Errors
103    /// Returns [`InvalidPrkLength`] if `prk` is shorter than the output size of `H`.
104    pub fn from_prk(prk: &[u8]) -> Result<Self, InvalidPrkLength> {
105        // section 2.3 specifies that `prk` must be "at least HashLen octets"
106        let hash_len = <H as OutputSizeUser>::OutputSize::to_usize();
107        if prk.len() < hash_len {
108            return Err(InvalidPrkLength);
109        }
110        let hmac = H::new_from_slice(prk);
111        Ok(Self { hmac })
112    }
113
114    /// The RFC5869 HKDF-Extract operation returning both the generated
115    /// pseudorandom key and `Hkdf` struct for expanding.
116    #[must_use]
117    pub fn extract(salt: Option<&[u8]>, ikm: &[u8]) -> (Output<H>, Self) {
118        let mut extract_ctx = GenericHkdfExtract::<H>::new(salt);
119        extract_ctx.input_ikm(ikm);
120        extract_ctx.finalize()
121    }
122
123    /// The RFC5869 HKDF-Expand operation. This is equivalent to calling
124    /// [`expand`][Hkdf::extract] with the `info` argument set equal to the
125    /// concatenation of all the elements of `info_components`.
126    ///
127    /// # Errors
128    /// Returns [`InvalidLength`] in the event `okm` is too large.
129    #[allow(clippy::missing_panics_doc, reason = "expect should not fail")]
130    pub fn expand_multi_info(
131        &self,
132        info_components: &[&[u8]],
133        okm: &mut [u8],
134    ) -> Result<(), InvalidLength> {
135        let mut prev: Option<Output<H>> = None;
136
137        let chunk_len = <H as OutputSizeUser>::OutputSize::USIZE;
138        if okm.len() > chunk_len * 255 {
139            return Err(InvalidLength);
140        }
141
142        for (block_n, block) in okm.chunks_mut(chunk_len).enumerate() {
143            let mut hmac = self.hmac.clone();
144
145            if let Some(ref prev) = prev {
146                hmac.update(prev);
147            };
148
149            // Feed in the info components in sequence. This is equivalent to feeding in the
150            // concatenation of all the info components
151            for info in info_components {
152                hmac.update(info);
153            }
154
155            hmac.update(&[u8::try_from(block_n).expect("should convert") + 1]);
156
157            let output = hmac.finalize();
158
159            let block_len = block.len();
160            block.copy_from_slice(&output[..block_len]);
161
162            prev = Some(output);
163        }
164
165        Ok(())
166    }
167
168    /// The RFC5869 HKDF-Expand operation
169    ///
170    /// If you don't have any `info` to pass, use an empty slice.
171    ///
172    /// # Errors
173    /// Returns [`InvalidLength`] in the event `okm` is too large.
174    pub fn expand(&self, info: &[u8], okm: &mut [u8]) -> Result<(), InvalidLength> {
175        self.expand_multi_info(&[info], okm)
176    }
177}