1#[cfg(feature = "zeroize")]
2use zeroize::Zeroize;
3
4use crate::{Hash, Hasher, MAX_HASH_BLOCK_SIZE};
5
6#[derive(Clone)]
32#[cfg_attr(feature = "zeroize", derive(Zeroize))]
33pub struct Hmac<H: Hasher> {
34 hash: H,
35 opad: [u8; MAX_HASH_BLOCK_SIZE],
36}
37
38impl<H: Hasher> Hmac<H> {
39 #[inline]
44 pub fn mac(key: &[u8], data: &[u8]) -> Hash {
45 let mut mac = Self::new(key);
46 mac.update(data);
47 return mac.finalize();
48 }
49
50 pub fn new(key: &[u8]) -> Self {
51 let mut key_block = [0u8; MAX_HASH_BLOCK_SIZE];
52
53 if key.len() > H::BLOCK_SIZE {
55 let mut h = H::new();
56 h.update(key);
57 let hashed = h.sum();
58 let hashed_bytes = hashed.as_ref();
59 key_block[..hashed_bytes.len()].copy_from_slice(hashed_bytes);
60 } else {
61 key_block[..key.len()].copy_from_slice(key);
62 }
63
64 let mut inner_key = [0u8; MAX_HASH_BLOCK_SIZE];
66 for i in 0..H::BLOCK_SIZE {
67 inner_key[i] = key_block[i] ^ 0x36;
68 }
69
70 let mut opad = [0u8; MAX_HASH_BLOCK_SIZE];
72 for i in 0..H::BLOCK_SIZE {
73 opad[i] = key_block[i] ^ 0x5c;
74 }
75
76 let mut hash = H::new();
78 hash.update(&inner_key[..H::BLOCK_SIZE]);
79
80 Hmac {
81 hash,
82 opad,
83 }
84 }
85
86 pub fn update(&mut self, data: &[u8]) {
88 self.hash.update(data);
89 }
90
91 pub fn finalize(self) -> Hash {
93 let inner_sum = self.hash.sum();
94
95 let mut outer = H::new();
97 outer.update(&self.opad[..H::BLOCK_SIZE]);
98 outer.update(inner_sum.as_ref());
99 outer.sum()
100 }
101}
102
103#[cfg(test)]
104mod hmac_tests {
105 use crate::{
106 hmac::Hmac,
107 sha2::{Sha256, Sha384, Sha512},
108 };
109
110 #[derive(Clone, Copy)]
111 enum TestInput {
112 Bytes(&'static [u8]),
113 Repeated { byte: u8, len: usize },
114 RangeInclusive { start: u8, end: u8 },
115 }
116
117 #[derive(Clone, Copy)]
118 struct HmacTestVector {
119 source: &'static str,
120 key: TestInput,
121 data: TestInput,
122 expected_sha256: &'static str,
123 expected_sha512: &'static str,
124 }
125
126 const HMAC_TEST_VECTORS: [HmacTestVector; 6] = [
127 HmacTestVector {
129 source: "RFC 4231 TC1",
130 key: TestInput::Repeated {
131 byte: 0x0b,
132 len: 20,
133 },
134 data: TestInput::Bytes(b"Hi There"),
135 expected_sha256: "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7",
136 expected_sha512: "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854",
137 },
138 HmacTestVector {
140 source: "RFC 4231 TC2",
141 key: TestInput::Bytes(b"Jefe"),
142 data: TestInput::Bytes(b"what do ya want for nothing?"),
143 expected_sha256: "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843",
144 expected_sha512: "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737",
145 },
146 HmacTestVector {
148 source: "RFC 4231 TC3",
149 key: TestInput::Repeated {
150 byte: 0xaa,
151 len: 20,
152 },
153 data: TestInput::Repeated {
154 byte: 0xdd,
155 len: 50,
156 },
157 expected_sha256: "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe",
158 expected_sha512: "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb",
159 },
160 HmacTestVector {
162 source: "RFC 4231 TC4",
163 key: TestInput::RangeInclusive {
164 start: 0x01,
165 end: 0x19,
166 },
167 data: TestInput::Repeated {
168 byte: 0xcd,
169 len: 50,
170 },
171 expected_sha256: "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b",
172 expected_sha512: "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd",
173 },
174 HmacTestVector {
176 source: "RFC 4231 TC6",
177 key: TestInput::Repeated {
178 byte: 0xaa,
179 len: 131,
180 },
181 data: TestInput::Bytes(b"Test Using Larger Than Block-Size Key - Hash Key First"),
182 expected_sha256: "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54",
183 expected_sha512: "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598",
184 },
185 HmacTestVector {
187 source: "RFC 4231 TC7",
188 key: TestInput::Repeated {
189 byte: 0xaa,
190 len: 131,
191 },
192 data: TestInput::Bytes(
193 b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.",
194 ),
195 expected_sha256: "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2",
196 expected_sha512: "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58",
197 },
198 ];
199
200 fn materialize(input: TestInput) -> Vec<u8> {
201 match input {
202 TestInput::Bytes(bytes) => bytes.to_vec(),
203 TestInput::Repeated {
204 byte,
205 len,
206 } => vec![byte; len],
207 TestInput::RangeInclusive {
208 start,
209 end,
210 } => (start..=end).collect(),
211 }
212 }
213
214 fn hmac256(key: &[u8], data: &[u8]) -> String {
215 let mut mac = Hmac::<Sha256>::new(key);
216 mac.update(data);
217 hex::encode(mac.finalize().as_ref())
218 }
219
220 fn hmac512(key: &[u8], data: &[u8]) -> String {
221 let mut mac = Hmac::<Sha512>::new(key);
222 mac.update(data);
223 hex::encode(mac.finalize().as_ref())
224 }
225
226 #[test]
227 fn hmac_vectors() {
228 for vector in HMAC_TEST_VECTORS {
229 let key = materialize(vector.key);
230 let data = materialize(vector.data);
231
232 let single256 = hmac256(&key, &data);
233 let single512 = hmac512(&key, &data);
234
235 assert_eq!(single256, vector.expected_sha256, "{}", vector.source);
236 assert_eq!(single512, vector.expected_sha512, "{}", vector.source);
237
238 let mut mac256 = Hmac::<Sha256>::new(&key);
239 for chunk in data.chunks(7) {
240 mac256.update(chunk);
241 }
242 let incremental256 = hex::encode(mac256.finalize().as_ref());
243 assert_eq!(incremental256, single256, "{} incremental sha256", vector.source);
244
245 let mut mac512 = Hmac::<Sha512>::new(&key);
246 for chunk in data.chunks(13) {
247 mac512.update(chunk);
248 }
249 let incremental512 = hex::encode(mac512.finalize().as_ref());
250 assert_eq!(incremental512, single512, "{} incremental sha512", vector.source);
251 }
252 }
253
254 #[test]
257 fn hmac_sha256_wycheproof() {
258 let data: serde_json::Value =
259 serde_json::from_str(include_str!("../testdata/wycheproof/testvectors_v1/hmac_sha256_test.json")).unwrap();
260 let mut valid_tested = 0u64;
261 let mut invalid_tested = 0u64;
262 for group in data["testGroups"].as_array().unwrap() {
263 let tag_size_bits = group["tagSize"].as_u64().unwrap();
264 let tag_size_bytes = (tag_size_bits / 8) as usize;
265 for test in group["tests"].as_array().unwrap() {
266 let key_hex = test["key"].as_str().unwrap();
267 let msg_hex = test["msg"].as_str().unwrap();
268 let expected_tag_hex = test["tag"].as_str().unwrap();
269 let result = test["result"].as_str().unwrap();
270
271 let key = hex::decode(key_hex).unwrap();
272 let msg = hex::decode(msg_hex).unwrap();
273
274 let computed = Hmac::<Sha256>::mac(&key, &msg);
275 let computed_tag = hex::encode(&computed.as_ref()[..tag_size_bytes]);
276
277 if result == "valid" {
278 assert_eq!(
279 computed_tag, expected_tag_hex,
280 "wycheproof HMAC-SHA-256 tcId={} tagSize={}",
281 test["tcId"], tag_size_bits
282 );
283 valid_tested += 1;
284 } else {
285 assert_ne!(
286 computed_tag, expected_tag_hex,
287 "wycheproof HMAC-SHA-256 tcId={} ModifiedTag not detected",
288 test["tcId"]
289 );
290 invalid_tested += 1;
291 }
292 }
293 }
294 assert!(valid_tested > 0, "no valid HMAC-SHA-256 wycheproof tests were run");
295 assert!(invalid_tested > 0, "no invalid HMAC-SHA-256 wycheproof tests were run");
296 }
297
298 #[test]
299 fn hmac_sha512_wycheproof() {
300 let data: serde_json::Value =
301 serde_json::from_str(include_str!("../testdata/wycheproof/testvectors_v1/hmac_sha512_test.json")).unwrap();
302 let mut valid_tested = 0u64;
303 let mut invalid_tested = 0u64;
304 for group in data["testGroups"].as_array().unwrap() {
305 let tag_size_bits = group["tagSize"].as_u64().unwrap();
306 let tag_size_bytes = (tag_size_bits / 8) as usize;
307 for test in group["tests"].as_array().unwrap() {
308 let key_hex = test["key"].as_str().unwrap();
309 let msg_hex = test["msg"].as_str().unwrap();
310 let expected_tag_hex = test["tag"].as_str().unwrap();
311 let result = test["result"].as_str().unwrap();
312
313 let key = hex::decode(key_hex).unwrap();
314 let msg = hex::decode(msg_hex).unwrap();
315
316 let computed = Hmac::<Sha512>::mac(&key, &msg);
317 let computed_tag = hex::encode(&computed.as_ref()[..tag_size_bytes]);
318
319 if result == "valid" {
320 assert_eq!(
321 computed_tag, expected_tag_hex,
322 "wycheproof HMAC-SHA-512 tcId={} tagSize={}",
323 test["tcId"], tag_size_bits
324 );
325 valid_tested += 1;
326 } else {
327 assert_ne!(
328 computed_tag, expected_tag_hex,
329 "wycheproof HMAC-SHA-512 tcId={} ModifiedTag not detected",
330 test["tcId"]
331 );
332 invalid_tested += 1;
333 }
334 }
335 }
336 assert!(valid_tested > 0, "no valid HMAC-SHA-512 wycheproof tests were run");
337 assert!(invalid_tested > 0, "no invalid HMAC-SHA-512 wycheproof tests were run");
338 }
339
340 #[test]
341 fn hmac_sha384_wycheproof() {
342 let data: serde_json::Value =
343 serde_json::from_str(include_str!("../testdata/wycheproof/testvectors_v1/hmac_sha384_test.json")).unwrap();
344 let mut valid_tested = 0u64;
345 let mut invalid_tested = 0u64;
346 for group in data["testGroups"].as_array().unwrap() {
347 let tag_size_bits = group["tagSize"].as_u64().unwrap();
348 let tag_size_bytes = (tag_size_bits / 8) as usize;
349 for test in group["tests"].as_array().unwrap() {
350 let key_hex = test["key"].as_str().unwrap();
351 let msg_hex = test["msg"].as_str().unwrap();
352 let expected_tag_hex = test["tag"].as_str().unwrap();
353 let result = test["result"].as_str().unwrap();
354
355 let key = hex::decode(key_hex).unwrap();
356 let msg = hex::decode(msg_hex).unwrap();
357
358 let computed = Hmac::<Sha384>::mac(&key, &msg);
359 let computed_tag = hex::encode(&computed.as_ref()[..tag_size_bytes]);
360
361 if result == "valid" {
362 assert_eq!(
363 computed_tag, expected_tag_hex,
364 "wycheproof HMAC-SHA-384 tcId={} tagSize={}",
365 test["tcId"], tag_size_bits
366 );
367 valid_tested += 1;
368 } else {
369 assert_ne!(
370 computed_tag, expected_tag_hex,
371 "wycheproof HMAC-SHA-384 tcId={} ModifiedTag not detected",
372 test["tcId"]
373 );
374 invalid_tested += 1;
375 }
376 }
377 }
378 assert!(valid_tested > 0, "no valid HMAC-SHA-384 wycheproof tests were run");
379 assert!(invalid_tested > 0, "no invalid HMAC-SHA-384 wycheproof tests were run");
380 }
381}