dcrypt_algorithms/mac/hmac/
mod.rs1use crate::error::{Error, Result};
8use crate::hash::HashFunction;
9use dcrypt_common::security::{SecretBuffer, SecureZeroingType};
10use subtle::ConstantTimeEq;
11use zeroize::{Zeroize, ZeroizeOnDrop};
12
13const MAX_BLOCK: usize = 144; #[derive(Clone, Zeroize, ZeroizeOnDrop)]
17pub struct Hmac<H: HashFunction + Clone> {
18 #[zeroize(skip)] hash: H,
20 ipad: SecretBuffer<MAX_BLOCK>,
21 opad: SecretBuffer<MAX_BLOCK>,
22 block_size: usize,
23 is_finalized: bool,
24}
25
26impl<H> Hmac<H>
27where
28 H: HashFunction + Clone,
29 H::Output: AsRef<[u8]> + Clone,
30{
31 const IPAD_BYTE: u8 = 0x36;
32 const OPAD_BYTE: u8 = 0x5c;
33
34 pub fn new(key: &[u8]) -> Result<Self> {
40 let bs = H::block_size();
41 debug_assert!(bs <= MAX_BLOCK);
42
43 let mut hk = H::new();
47 hk.update(key)?;
48 let hashed = hk.finalize()?; let mut k_prime = [0u8; MAX_BLOCK];
52 let long = (key.len() > bs) as u8; let mask = long.wrapping_neg(); #[allow(clippy::needless_range_loop)] for i in 0..bs {
56 let k = *key.get(i).unwrap_or(&0);
57 let hk = hashed.as_ref().get(i).copied().unwrap_or(0);
58 k_prime[i] = (hk & mask) | (k & !mask);
59 }
60
61 let mut ipad_bytes = [0u8; MAX_BLOCK];
63 let mut opad_bytes = [0u8; MAX_BLOCK];
64 #[allow(clippy::needless_range_loop)] for i in 0..bs {
66 ipad_bytes[i] = k_prime[i] ^ Self::IPAD_BYTE;
67 opad_bytes[i] = k_prime[i] ^ Self::OPAD_BYTE;
68 }
69
70 for b in k_prime.iter_mut().take(bs) {
72 *b = 0;
73 }
74
75 let mut hash = H::new();
77 hash.update(&ipad_bytes[..bs])?;
78
79 Ok(Self {
80 hash,
81 ipad: SecretBuffer::new(ipad_bytes),
82 opad: SecretBuffer::new(opad_bytes),
83 block_size: bs,
84 is_finalized: false,
85 })
86 }
87
88 pub fn update(&mut self, data: &[u8]) -> Result<()> {
94 if self.is_finalized {
95 let mut dummy = H::new();
100 dummy.update(data)?;
101 let _ = dummy.finalize();
102 return Err(Error::param(
103 "hmac_state",
104 "Cannot update after finalization",
105 ));
106 }
107
108 self.hash.update(data).map(|_| ())
109 }
110
111 pub fn finalize(&mut self) -> Result<Vec<u8>> {
113 if self.is_finalized {
114 let inner_dummy = [0u8; 64]; let mut outer = H::new();
117 outer.update(&self.opad.as_ref()[..self.block_size])?;
118 outer.update(&inner_dummy[..H::output_size()])?;
119 let _ = outer.finalize();
120 return Err(Error::param("hmac_state", "HMAC already finalized"));
121 }
122
123 self.is_finalized = true;
124
125 let inner_hash = self.hash.finalize()?;
126
127 let mut outer = H::new();
128 outer.update(&self.opad.as_ref()[..self.block_size])?;
129 outer.update(inner_hash.as_ref())?;
130
131 outer.finalize().map(|out| out.as_ref().to_vec())
132 }
133
134 pub fn mac(key: &[u8], data: &[u8]) -> Result<Vec<u8>> {
140 let mut h = Self::new(key)?;
141 h.update(data)?;
142 h.finalize()
143 }
144
145 pub fn verify(key: &[u8], data: &[u8], tag: &[u8]) -> Result<bool> {
147 let expected = Self::mac(key, data)?;
148
149 let mut diff = 0u8;
152 #[allow(clippy::needless_range_loop)] for i in 0..H::output_size() {
154 let a = expected.get(i).copied().unwrap_or(0);
155 let b = tag.get(i).copied().unwrap_or(0);
156 diff |= a ^ b;
157 }
158 diff |= (tag.len() ^ H::output_size()) as u8;
160
161 Ok(diff.ct_eq(&0u8).unwrap_u8() == 1)
162 }
163}
164
165impl<H> SecureZeroingType for Hmac<H>
166where
167 H: HashFunction + Default + Clone,
168{
169 fn zeroed() -> Self {
170 Self {
171 hash: H::default(),
172 ipad: SecretBuffer::zeroed(),
173 opad: SecretBuffer::zeroed(),
174 block_size: 0,
175 is_finalized: false,
176 }
177 }
178
179 fn secure_clone(&self) -> Self {
180 Self {
181 hash: self.hash.clone(),
182 ipad: self.ipad.secure_clone(),
183 opad: self.opad.secure_clone(),
184 block_size: self.block_size,
185 is_finalized: self.is_finalized,
186 }
187 }
188}
189
190#[cfg(test)]
191mod tests;