dcrypt_algorithms/kdf/hkdf/
mod.rs1#[cfg(feature = "alloc")]
9use crate::alloc_prelude::*;
10
11use crate::error::{validate, Error, Result};
12use crate::hash::HashFunction;
13use crate::kdf::{KdfAlgorithm, KdfOperation, KeyDerivationFunction, ParamProvider, SecurityLevel};
14use crate::mac::hmac::Hmac;
15use crate::types::salt::HkdfCompatible;
16use crate::types::Salt;
17
18use dcrypt_common::security::SecureZeroingType;
20
21use dcrypt_internal::random::{CryptoRng, RngCore};
22use dcrypt_internal::zeroing::{
23 boxed_bytes_zeroed, Zeroize, ZeroizeOnDrop, Zeroizing, ZeroizingBytes,
24};
25
26#[cfg(not(feature = "std"))]
27use alloc::vec::Vec;
28use core::marker::PhantomData;
29
30pub enum HkdfAlgorithm<H: HashFunction> {
32 _Hash(PhantomData<H>),
34}
35
36impl<H: HashFunction> KdfAlgorithm for HkdfAlgorithm<H> {
37 const MIN_SALT_SIZE: usize = 16;
38 const DEFAULT_OUTPUT_SIZE: usize = 32;
39 const ALGORITHM_ID: &'static str = "HKDF";
40
41 fn name() -> String {
42 format!("{}-{}", Self::ALGORITHM_ID, H::name())
43 }
44
45 fn security_level() -> SecurityLevel {
46 match H::output_size() * 8 {
47 bits if bits >= 512 => SecurityLevel::L256,
48 bits if bits >= 384 => SecurityLevel::L192,
49 bits if bits >= 256 => SecurityLevel::L128,
50 bits => SecurityLevel::Custom(bits as u32 / 2),
51 }
52 }
53}
54
55#[derive(Clone, Debug)]
57pub struct HkdfParams<const S: usize = 16> {
58 pub salt: Option<Salt<S>>,
60 pub info: Option<Vec<u8>>,
62}
63
64impl<const S: usize> Zeroize for HkdfParams<S> {
65 fn zeroize(&mut self) {
66 self.salt.zeroize();
67 self.info.zeroize();
68 }
69}
70
71impl<const S: usize> Default for HkdfParams<S> {
72 fn default() -> Self {
73 Self {
74 salt: None,
75 info: None,
76 }
77 }
78}
79
80#[derive(Clone)]
82pub struct Hkdf<H: HashFunction, const S: usize = 16> {
83 _hash_type: PhantomData<H>,
84 params: HkdfParams<S>,
85}
86
87impl<H: HashFunction, const S: usize> Zeroize for Hkdf<H, S> {
88 fn zeroize(&mut self) {
89 self.params.zeroize();
90 }
91}
92
93impl<H: HashFunction, const S: usize> Drop for Hkdf<H, S> {
94 fn drop(&mut self) {
95 self.zeroize();
96 }
97}
98
99impl<H: HashFunction, const S: usize> ZeroizeOnDrop for Hkdf<H, S> {}
100
101pub struct HkdfOperation<'a, H: HashFunction, const S: usize = 16> {
103 #[allow(dead_code)] kdf: &'a Hkdf<H, S>,
105 ikm: Option<&'a [u8]>,
106 salt: Option<&'a [u8]>,
107 info: Option<&'a [u8]>,
108 length: usize,
109}
110
111impl<'a, H: HashFunction + Clone, const S: usize> KdfOperation<'a, HkdfAlgorithm<H>>
112 for HkdfOperation<'a, H, S>
113where
114 Salt<S>: HkdfCompatible,
115{
116 fn with_ikm(mut self, ikm: &'a [u8]) -> Self {
117 self.ikm = Some(ikm);
118 self
119 }
120
121 fn with_salt(mut self, salt: &'a [u8]) -> Self {
122 self.salt = Some(salt);
123 self
124 }
125
126 fn with_info(mut self, info: &'a [u8]) -> Self {
127 self.info = Some(info);
128 self
129 }
130
131 fn with_output_length(mut self, length: usize) -> Self {
132 self.length = length;
133 self
134 }
135
136 fn derive(self) -> Result<ZeroizingBytes> {
137 let ikm = self
138 .ikm
139 .ok_or_else(|| Error::param("ikm", "Input keying material is required"))?;
140
141 let salt_bytes = self.salt;
142 let info_bytes = self.info;
143
144 Hkdf::<H, S>::derive(salt_bytes, ikm, info_bytes, self.length)
145 }
146
147 fn derive_array<const N: usize>(self) -> Result<Zeroizing<[u8; N]>> {
148 validate::length("HKDF output", self.length, N)?;
150
151 let vec = self.derive()?;
152
153 let mut array = Zeroizing::new([0u8; N]);
155 array.copy_from_slice(&vec);
156 Ok(array)
157 }
158}
159
160impl<H: HashFunction + Clone, const S: usize> Hkdf<H, S>
161where
162 Salt<S>: HkdfCompatible,
163{
164 pub fn extract(salt: Option<&[u8]>, ikm: &[u8]) -> Result<ZeroizingBytes> {
166 Hmac::<H>::mac(salt.unwrap_or(&[]), ikm)
167 }
168
169 pub fn expand(prk: &[u8], info: Option<&[u8]>, length: usize) -> Result<ZeroizingBytes> {
171 let hash_len = H::output_size();
172 let max_len = 255 * hash_len;
173
174 validate::max_length("HKDF-Expand output", length, max_len)?;
176
177 validate::min_length("PRK for HKDF-Expand", prk.len(), hash_len)?;
179
180 let n = length.div_ceil(hash_len);
182
183 let mut okm = Zeroizing::new(boxed_bytes_zeroed(length));
185 let mut t_buf = Zeroizing::new(boxed_bytes_zeroed(hash_len));
186 let info_bytes = info.unwrap_or(&[]);
187
188 for i in 1..=n {
189 let mut hmac = Hmac::<H>::new(prk)?;
190 if i > 1 {
191 hmac.update(&t_buf)?;
193 }
194 hmac.update(info_bytes)?;
195 hmac.update(&[i as u8])?;
196 let block = hmac.finalize()?;
197 t_buf.copy_from_slice(&block);
198 let start = (i - 1) * hash_len;
199 let end = core::cmp::min(start + hash_len, length);
200 okm[start..end].copy_from_slice(&t_buf[..end - start]);
201 }
202
203 Ok(okm)
204 }
205
206 pub fn derive(
208 salt: Option<&[u8]>,
209 ikm: &[u8],
210 info: Option<&[u8]>,
211 length: usize,
212 ) -> Result<ZeroizingBytes> {
213 let _ = Hmac::<H>::new(&[])?; let prk = Self::extract(salt, ikm)?;
217
218 Self::expand(&prk, info, length)
220 }
221}
222
223impl<H: HashFunction, const S: usize> ParamProvider for Hkdf<H, S>
224where
225 Salt<S>: HkdfCompatible,
226{
227 type Params = HkdfParams<S>;
228 fn with_params(params: Self::Params) -> Self {
229 Hkdf {
230 _hash_type: PhantomData,
231 params,
232 }
233 }
234 fn params(&self) -> &Self::Params {
235 &self.params
236 }
237 fn set_params(&mut self, params: Self::Params) {
238 self.params = params;
239 }
240}
241
242impl<H: HashFunction + Clone, const S: usize> KeyDerivationFunction for Hkdf<H, S>
243where
244 Salt<S>: HkdfCompatible,
245{
246 type Algorithm = HkdfAlgorithm<H>;
247 type Salt = Salt<S>;
248
249 fn new() -> Self {
250 Hkdf {
251 _hash_type: PhantomData,
252 params: HkdfParams::default(),
253 }
254 }
255
256 fn derive_key(
257 &self,
258 input: &[u8],
259 salt: Option<&[u8]>,
260 info: Option<&[u8]>,
261 length: usize,
262 ) -> Result<ZeroizingBytes> {
263 let effective_salt = salt.or_else(|| self.params.salt.as_ref().map(|s| s.as_ref()));
264 let effective_info = info.or_else(|| self.params.info.as_deref());
265 Self::derive(effective_salt, input, effective_info, length)
266 }
267
268 fn builder(&self) -> impl KdfOperation<'_, Self::Algorithm> {
270 HkdfOperation::<H, S> {
271 kdf: self,
272 ikm: None,
273 salt: None,
274 info: None,
275 length: Self::Algorithm::DEFAULT_OUTPUT_SIZE,
276 }
277 }
278
279 fn generate_salt<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self::Salt> {
280 Salt::random_with_size(rng, Self::Algorithm::MIN_SALT_SIZE)
281 }
282
283 fn security_level() -> SecurityLevel {
285 match H::output_size() * 8 {
286 bits if bits >= 512 => SecurityLevel::L256,
287 bits if bits >= 384 => SecurityLevel::L192,
288 bits if bits >= 256 => SecurityLevel::L128,
289 bits => SecurityLevel::Custom(bits as u32 / 2),
290 }
291 }
292}
293
294impl<H: HashFunction + Clone, const S: usize> SecureZeroingType for Hkdf<H, S>
295where
296 Salt<S>: HkdfCompatible,
297{
298 fn zeroed() -> Self {
299 Self {
300 _hash_type: PhantomData,
301 params: HkdfParams::default(),
302 }
303 }
304
305 fn secure_clone(&self) -> Self {
306 self.clone()
307 }
308}
309
310#[cfg(test)]
311mod tests;