dcrypt_algorithms/mac/hmac/
mod.rs1use crate::error::{Error, Result};
8use crate::hash::HashFunction;
9use dcrypt_common::security::{SecretBuffer, SecureZeroingType};
10use dcrypt_internal::constant_time::ConstantTimeEq;
11use dcrypt_internal::zeroing::{
12 zeroizing_bytes_from_slice, Zeroize, ZeroizeOnDrop, ZeroizingBytes,
13};
14
15const MAX_BLOCK: usize = 144; #[derive(Clone)]
19pub struct Hmac<H: HashFunction + Clone + Zeroize> {
20 hash: H,
22 ipad: SecretBuffer<MAX_BLOCK>,
23 opad: SecretBuffer<MAX_BLOCK>,
24 block_size: usize,
25 is_finalized: bool,
26}
27
28impl<H: HashFunction + Clone + Zeroize> Zeroize for Hmac<H> {
29 fn zeroize(&mut self) {
30 self.hash.zeroize();
31 self.ipad.zeroize();
32 self.opad.zeroize();
33 self.block_size.zeroize();
34 self.is_finalized.zeroize();
35 }
36}
37
38impl<H: HashFunction + Clone + Zeroize> Drop for Hmac<H> {
39 fn drop(&mut self) {
40 self.zeroize();
41 }
42}
43
44impl<H: HashFunction + Clone + Zeroize> ZeroizeOnDrop for Hmac<H> {}
45
46impl<H> Hmac<H>
47where
48 H: HashFunction + Clone + Zeroize,
49 H::Output: AsRef<[u8]> + Clone + Zeroize,
50{
51 const IPAD_BYTE: u8 = 0x36;
52 const OPAD_BYTE: u8 = 0x5c;
53
54 pub fn new(key: &[u8]) -> Result<Self> {
60 let bs = H::block_size();
61 debug_assert!(bs <= MAX_BLOCK);
62
63 let mut hk = H::new();
67 if let Err(error) = hk.update(key) {
68 hk.zeroize();
69 return Err(error);
70 }
71 let mut hashed = match hk.finalize() {
72 Ok(output) => output,
73 Err(error) => {
74 hk.zeroize();
75 return Err(error);
76 }
77 }; hk.zeroize();
79
80 let mut k_prime = SecretBuffer::<MAX_BLOCK>::zeroed();
82 let long = (key.len() > bs) as u8; let mask = long.wrapping_neg(); #[allow(clippy::needless_range_loop)] for i in 0..bs {
86 let k = *key.get(i).unwrap_or(&0);
87 let hk = hashed.as_ref().get(i).copied().unwrap_or(0);
88 k_prime.as_mut()[i] = (hk & mask) | (k & !mask);
89 }
90 hashed.zeroize();
91
92 let mut ipad = SecretBuffer::<MAX_BLOCK>::zeroed();
94 let mut opad = SecretBuffer::<MAX_BLOCK>::zeroed();
95 #[allow(clippy::needless_range_loop)] for i in 0..bs {
97 ipad.as_mut()[i] = k_prime.as_ref()[i] ^ Self::IPAD_BYTE;
98 opad.as_mut()[i] = k_prime.as_ref()[i] ^ Self::OPAD_BYTE;
99 }
100
101 k_prime.zeroize();
103
104 let mut hash = H::new();
106 if let Err(error) = hash.update(&ipad.as_ref()[..bs]) {
107 hash.zeroize();
108 return Err(error);
109 }
110
111 Ok(Self {
112 hash,
113 ipad,
114 opad,
115 block_size: bs,
116 is_finalized: false,
117 })
118 }
119
120 pub fn update(&mut self, data: &[u8]) -> Result<()> {
126 if self.is_finalized {
127 return Err(Error::param(
128 "hmac_state",
129 "Cannot update after finalization",
130 ));
131 }
132
133 self.hash.update(data).map(|_| ())
134 }
135
136 pub fn finalize(&mut self) -> Result<ZeroizingBytes> {
138 if self.is_finalized {
139 return Err(Error::param("hmac_state", "HMAC already finalized"));
140 }
141
142 self.is_finalized = true;
143
144 let mut inner_hash = match self.hash.finalize() {
145 Ok(output) => {
146 self.hash.zeroize();
147 output
148 }
149 Err(error) => {
150 self.hash.zeroize();
151 return Err(error);
152 }
153 };
154
155 let mut outer = H::new();
156 if let Err(error) = outer.update(&self.opad.as_ref()[..self.block_size]) {
157 inner_hash.zeroize();
158 outer.zeroize();
159 return Err(error);
160 }
161 if let Err(error) = outer.update(inner_hash.as_ref()) {
162 inner_hash.zeroize();
163 outer.zeroize();
164 return Err(error);
165 }
166 inner_hash.zeroize();
167
168 let mut output = match outer.finalize() {
169 Ok(output) => output,
170 Err(error) => {
171 outer.zeroize();
172 return Err(error);
173 }
174 };
175 outer.zeroize();
176 let tag = zeroizing_bytes_from_slice(output.as_ref());
177 output.zeroize();
178 Ok(tag)
179 }
180
181 pub fn mac(key: &[u8], data: &[u8]) -> Result<ZeroizingBytes> {
187 let mut h = Self::new(key)?;
188 h.update(data)?;
189 h.finalize()
190 }
191
192 pub fn verify(key: &[u8], data: &[u8], tag: &[u8]) -> Result<bool> {
198 let expected = Self::mac(key, data)?;
199
200 let mut diff = 0u8;
203 #[allow(clippy::needless_range_loop)] for i in 0..H::output_size() {
205 let a = expected.get(i).copied().unwrap_or(0);
206 let b = tag.get(i).copied().unwrap_or(0);
207 diff |= a ^ b;
208 }
209 diff |= tag.len().ct_eq(&H::output_size()).unwrap_u8() ^ 1;
212
213 Ok(diff.ct_eq(&0u8).unwrap_u8() == 1)
214 }
215}
216
217impl<H> SecureZeroingType for Hmac<H>
218where
219 H: HashFunction + Default + Clone + Zeroize,
220{
221 fn zeroed() -> Self {
222 Self {
223 hash: H::default(),
224 ipad: SecretBuffer::zeroed(),
225 opad: SecretBuffer::zeroed(),
226 block_size: 0,
227 is_finalized: false,
228 }
229 }
230
231 fn secure_clone(&self) -> Self {
232 Self {
233 hash: self.hash.clone(),
234 ipad: self.ipad.secure_clone(),
235 opad: self.opad.secure_clone(),
236 block_size: self.block_size,
237 is_finalized: self.is_finalized,
238 }
239 }
240}
241
242#[cfg(test)]
243mod tests;