1#[cfg(feature = "zeroize")]
4use zeroize::{Zeroize, ZeroizeOnDrop};
5
6use crate::{Hash, Hasher, MAX_HASH_BLOCK_SIZE};
7
8#[derive(Clone)]
34#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
35pub struct Hmac<H: Hasher> {
36 hash: H,
37 opad: [u8; MAX_HASH_BLOCK_SIZE],
38}
39
40impl<H: Hasher> Hmac<H> {
41 #[inline]
46 pub fn mac(key: &[u8], data: &[u8]) -> Hash {
47 let mut mac = Self::new(key);
48 mac.update(data);
49 return mac.finalize();
50 }
51
52 pub fn new(key: &[u8]) -> Self {
53 let mut key_block = [0u8; MAX_HASH_BLOCK_SIZE];
54
55 if key.len() > H::BLOCK_SIZE {
57 let mut h = H::new();
58 h.update(key);
59 let hashed = h.sum();
60 let hashed_bytes = hashed.as_ref();
61 key_block[..hashed_bytes.len()].copy_from_slice(hashed_bytes);
62 } else {
63 key_block[..key.len()].copy_from_slice(key);
64 }
65
66 let mut inner_key = [0u8; MAX_HASH_BLOCK_SIZE];
68 for i in 0..H::BLOCK_SIZE {
69 inner_key[i] = key_block[i] ^ 0x36;
70 }
71
72 let mut opad = [0u8; MAX_HASH_BLOCK_SIZE];
74 for i in 0..H::BLOCK_SIZE {
75 opad[i] = key_block[i] ^ 0x5c;
76 }
77
78 let mut hash = H::new();
80 hash.update(&inner_key[..H::BLOCK_SIZE]);
81
82 Hmac {
83 hash,
84 opad,
85 }
86 }
87
88 pub fn update(&mut self, data: &[u8]) {
90 self.hash.update(data);
91 }
92
93 pub fn finalize(mut self) -> Hash {
95 let inner_hash = core::mem::replace(&mut self.hash, H::new());
99 let inner_sum = inner_hash.sum();
100
101 let mut outer = H::new();
103 outer.update(&self.opad[..H::BLOCK_SIZE]);
104 outer.update(inner_sum.as_ref());
105 outer.sum()
106 }
107}
108
109#[cfg(test)]
110mod hmac_tests {
111 use crate::{
112 hmac::Hmac,
113 sha2::{Sha256, Sha384, Sha512},
114 sha3::{Sha3_256, Sha3_512},
115 };
116
117 #[cfg(feature = "zeroize")]
118 #[test]
119 fn hmac_zeroize_clears_opad() {
120 use zeroize::Zeroize;
121
122 let mut mac = Hmac::<Sha256>::new(b"secret key");
123 assert_ne!(mac.opad, [0u8; crate::MAX_HASH_BLOCK_SIZE]);
124 mac.zeroize();
125 assert_eq!(mac.opad, [0u8; crate::MAX_HASH_BLOCK_SIZE]);
126 }
127
128 #[derive(Clone, Copy)]
129 enum TestInput {
130 Bytes(&'static [u8]),
131 Repeated { byte: u8, len: usize },
132 RangeInclusive { start: u8, end: u8 },
133 }
134
135 #[derive(Clone, Copy)]
136 struct HmacTestVector {
137 source: &'static str,
138 key: TestInput,
139 data: TestInput,
140 expected_sha256: &'static str,
141 expected_sha512: &'static str,
142 }
143
144 const HMAC_TEST_VECTORS: [HmacTestVector; 6] = [
145 HmacTestVector {
147 source: "RFC 4231 TC1",
148 key: TestInput::Repeated {
149 byte: 0x0b,
150 len: 20,
151 },
152 data: TestInput::Bytes(b"Hi There"),
153 expected_sha256: "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7",
154 expected_sha512: "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854",
155 },
156 HmacTestVector {
158 source: "RFC 4231 TC2",
159 key: TestInput::Bytes(b"Jefe"),
160 data: TestInput::Bytes(b"what do ya want for nothing?"),
161 expected_sha256: "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843",
162 expected_sha512: "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737",
163 },
164 HmacTestVector {
166 source: "RFC 4231 TC3",
167 key: TestInput::Repeated {
168 byte: 0xaa,
169 len: 20,
170 },
171 data: TestInput::Repeated {
172 byte: 0xdd,
173 len: 50,
174 },
175 expected_sha256: "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe",
176 expected_sha512: "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb",
177 },
178 HmacTestVector {
180 source: "RFC 4231 TC4",
181 key: TestInput::RangeInclusive {
182 start: 0x01,
183 end: 0x19,
184 },
185 data: TestInput::Repeated {
186 byte: 0xcd,
187 len: 50,
188 },
189 expected_sha256: "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b",
190 expected_sha512: "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd",
191 },
192 HmacTestVector {
194 source: "RFC 4231 TC6",
195 key: TestInput::Repeated {
196 byte: 0xaa,
197 len: 131,
198 },
199 data: TestInput::Bytes(b"Test Using Larger Than Block-Size Key - Hash Key First"),
200 expected_sha256: "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54",
201 expected_sha512: "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598",
202 },
203 HmacTestVector {
205 source: "RFC 4231 TC7",
206 key: TestInput::Repeated {
207 byte: 0xaa,
208 len: 131,
209 },
210 data: TestInput::Bytes(
211 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.",
212 ),
213 expected_sha256: "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2",
214 expected_sha512: "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58",
215 },
216 ];
217
218 fn materialize(input: TestInput) -> Vec<u8> {
219 match input {
220 TestInput::Bytes(bytes) => bytes.to_vec(),
221 TestInput::Repeated {
222 byte,
223 len,
224 } => vec![byte; len],
225 TestInput::RangeInclusive {
226 start,
227 end,
228 } => (start..=end).collect(),
229 }
230 }
231
232 fn hmac256(key: &[u8], data: &[u8]) -> String {
233 let mut mac = Hmac::<Sha256>::new(key);
234 mac.update(data);
235 hex::encode(mac.finalize().as_ref())
236 }
237
238 fn hmac512(key: &[u8], data: &[u8]) -> String {
239 let mut mac = Hmac::<Sha512>::new(key);
240 mac.update(data);
241 hex::encode(mac.finalize().as_ref())
242 }
243
244 #[test]
245 fn hmac_vectors() {
246 for vector in HMAC_TEST_VECTORS {
247 let key = materialize(vector.key);
248 let data = materialize(vector.data);
249
250 let single256 = hmac256(&key, &data);
251 let single512 = hmac512(&key, &data);
252
253 assert_eq!(single256, vector.expected_sha256, "{}", vector.source);
254 assert_eq!(single512, vector.expected_sha512, "{}", vector.source);
255
256 let mut mac256 = Hmac::<Sha256>::new(&key);
257 for chunk in data.chunks(7) {
258 mac256.update(chunk);
259 }
260 let incremental256 = hex::encode(mac256.finalize().as_ref());
261 assert_eq!(incremental256, single256, "{} incremental sha256", vector.source);
262
263 let mut mac512 = Hmac::<Sha512>::new(&key);
264 for chunk in data.chunks(13) {
265 mac512.update(chunk);
266 }
267 let incremental512 = hex::encode(mac512.finalize().as_ref());
268 assert_eq!(incremental512, single512, "{} incremental sha512", vector.source);
269 }
270 }
271
272 #[test]
275 fn hmac_sha256_wycheproof() {
276 let data: serde_json::Value =
277 serde_json::from_str(include_str!("../testdata/wycheproof/testvectors_v1/hmac_sha256_test.json")).unwrap();
278 let mut valid_tested = 0u64;
279 let mut invalid_tested = 0u64;
280 for group in data["testGroups"].as_array().unwrap() {
281 let tag_size_bits = group["tagSize"].as_u64().unwrap();
282 let tag_size_bytes = (tag_size_bits / 8) as usize;
283 for test in group["tests"].as_array().unwrap() {
284 let key_hex = test["key"].as_str().unwrap();
285 let msg_hex = test["msg"].as_str().unwrap();
286 let expected_tag_hex = test["tag"].as_str().unwrap();
287 let result = test["result"].as_str().unwrap();
288
289 let key = hex::decode(key_hex).unwrap();
290 let msg = hex::decode(msg_hex).unwrap();
291
292 let computed = Hmac::<Sha256>::mac(&key, &msg);
293 let computed_tag = hex::encode(&computed.as_ref()[..tag_size_bytes]);
294
295 if result == "valid" {
296 assert_eq!(
297 computed_tag, expected_tag_hex,
298 "wycheproof HMAC-SHA-256 tcId={} tagSize={}",
299 test["tcId"], tag_size_bits
300 );
301 valid_tested += 1;
302 } else {
303 assert_ne!(
304 computed_tag, expected_tag_hex,
305 "wycheproof HMAC-SHA-256 tcId={} ModifiedTag not detected",
306 test["tcId"]
307 );
308 invalid_tested += 1;
309 }
310 }
311 }
312 assert!(valid_tested > 0, "no valid HMAC-SHA-256 wycheproof tests were run");
313 assert!(invalid_tested > 0, "no invalid HMAC-SHA-256 wycheproof tests were run");
314 }
315
316 #[test]
317 fn hmac_sha512_wycheproof() {
318 let data: serde_json::Value =
319 serde_json::from_str(include_str!("../testdata/wycheproof/testvectors_v1/hmac_sha512_test.json")).unwrap();
320 let mut valid_tested = 0u64;
321 let mut invalid_tested = 0u64;
322 for group in data["testGroups"].as_array().unwrap() {
323 let tag_size_bits = group["tagSize"].as_u64().unwrap();
324 let tag_size_bytes = (tag_size_bits / 8) as usize;
325 for test in group["tests"].as_array().unwrap() {
326 let key_hex = test["key"].as_str().unwrap();
327 let msg_hex = test["msg"].as_str().unwrap();
328 let expected_tag_hex = test["tag"].as_str().unwrap();
329 let result = test["result"].as_str().unwrap();
330
331 let key = hex::decode(key_hex).unwrap();
332 let msg = hex::decode(msg_hex).unwrap();
333
334 let computed = Hmac::<Sha512>::mac(&key, &msg);
335 let computed_tag = hex::encode(&computed.as_ref()[..tag_size_bytes]);
336
337 if result == "valid" {
338 assert_eq!(
339 computed_tag, expected_tag_hex,
340 "wycheproof HMAC-SHA-512 tcId={} tagSize={}",
341 test["tcId"], tag_size_bits
342 );
343 valid_tested += 1;
344 } else {
345 assert_ne!(
346 computed_tag, expected_tag_hex,
347 "wycheproof HMAC-SHA-512 tcId={} ModifiedTag not detected",
348 test["tcId"]
349 );
350 invalid_tested += 1;
351 }
352 }
353 }
354 assert!(valid_tested > 0, "no valid HMAC-SHA-512 wycheproof tests were run");
355 assert!(invalid_tested > 0, "no invalid HMAC-SHA-512 wycheproof tests were run");
356 }
357
358 #[test]
359 fn hmac_sha384_wycheproof() {
360 let data: serde_json::Value =
361 serde_json::from_str(include_str!("../testdata/wycheproof/testvectors_v1/hmac_sha384_test.json")).unwrap();
362 let mut valid_tested = 0u64;
363 let mut invalid_tested = 0u64;
364 for group in data["testGroups"].as_array().unwrap() {
365 let tag_size_bits = group["tagSize"].as_u64().unwrap();
366 let tag_size_bytes = (tag_size_bits / 8) as usize;
367 for test in group["tests"].as_array().unwrap() {
368 let key_hex = test["key"].as_str().unwrap();
369 let msg_hex = test["msg"].as_str().unwrap();
370 let expected_tag_hex = test["tag"].as_str().unwrap();
371 let result = test["result"].as_str().unwrap();
372
373 let key = hex::decode(key_hex).unwrap();
374 let msg = hex::decode(msg_hex).unwrap();
375
376 let computed = Hmac::<Sha384>::mac(&key, &msg);
377 let computed_tag = hex::encode(&computed.as_ref()[..tag_size_bytes]);
378
379 if result == "valid" {
380 assert_eq!(
381 computed_tag, expected_tag_hex,
382 "wycheproof HMAC-SHA-384 tcId={} tagSize={}",
383 test["tcId"], tag_size_bits
384 );
385 valid_tested += 1;
386 } else {
387 assert_ne!(
388 computed_tag, expected_tag_hex,
389 "wycheproof HMAC-SHA-384 tcId={} ModifiedTag not detected",
390 test["tcId"]
391 );
392 invalid_tested += 1;
393 }
394 }
395 }
396 assert!(valid_tested > 0, "no valid HMAC-SHA-384 wycheproof tests were run");
397 assert!(invalid_tested > 0, "no invalid HMAC-SHA-384 wycheproof tests were run");
398 }
399
400 #[test]
401 fn hmac_sha3_256_wycheproof() {
402 let data: serde_json::Value =
403 serde_json::from_str(include_str!("../testdata/wycheproof/testvectors_v1/hmac_sha3_256_test.json"))
404 .unwrap();
405 let mut valid_tested = 0u64;
406 let mut invalid_tested = 0u64;
407 for group in data["testGroups"].as_array().unwrap() {
408 let tag_size_bits = group["tagSize"].as_u64().unwrap();
409 let tag_size_bytes = (tag_size_bits / 8) as usize;
410 for test in group["tests"].as_array().unwrap() {
411 let key_hex = test["key"].as_str().unwrap();
412 let msg_hex = test["msg"].as_str().unwrap();
413 let expected_tag_hex = test["tag"].as_str().unwrap();
414 let result = test["result"].as_str().unwrap();
415
416 let key = hex::decode(key_hex).unwrap();
417 let msg = hex::decode(msg_hex).unwrap();
418
419 let computed = Hmac::<Sha3_256>::mac(&key, &msg);
420 let computed_tag = hex::encode(&computed.as_ref()[..tag_size_bytes]);
421
422 if result == "valid" {
423 assert_eq!(
424 computed_tag, expected_tag_hex,
425 "wycheproof HMAC-SHA3-256 tcId={} tagSize={}",
426 test["tcId"], tag_size_bits
427 );
428 valid_tested += 1;
429 } else {
430 assert_ne!(
431 computed_tag, expected_tag_hex,
432 "wycheproof HMAC-SHA3-256 tcId={} ModifiedTag not detected",
433 test["tcId"]
434 );
435 invalid_tested += 1;
436 }
437 }
438 }
439 assert!(valid_tested > 0, "no valid HMAC-SHA3-256 wycheproof tests were run");
440 assert!(invalid_tested > 0, "no invalid HMAC-SHA3-256 wycheproof tests were run");
441 }
442
443 #[test]
444 fn hmac_sha3_512_wycheproof() {
445 let data: serde_json::Value =
446 serde_json::from_str(include_str!("../testdata/wycheproof/testvectors_v1/hmac_sha3_512_test.json"))
447 .unwrap();
448 let mut valid_tested = 0u64;
449 let mut invalid_tested = 0u64;
450 for group in data["testGroups"].as_array().unwrap() {
451 let tag_size_bits = group["tagSize"].as_u64().unwrap();
452 let tag_size_bytes = (tag_size_bits / 8) as usize;
453 for test in group["tests"].as_array().unwrap() {
454 let key_hex = test["key"].as_str().unwrap();
455 let msg_hex = test["msg"].as_str().unwrap();
456 let expected_tag_hex = test["tag"].as_str().unwrap();
457 let result = test["result"].as_str().unwrap();
458
459 let key = hex::decode(key_hex).unwrap();
460 let msg = hex::decode(msg_hex).unwrap();
461
462 let computed = Hmac::<Sha3_512>::mac(&key, &msg);
463 let computed_tag = hex::encode(&computed.as_ref()[..tag_size_bytes]);
464
465 if result == "valid" {
466 assert_eq!(
467 computed_tag, expected_tag_hex,
468 "wycheproof HMAC-SHA3-512 tcId={} tagSize={}",
469 test["tcId"], tag_size_bits
470 );
471 valid_tested += 1;
472 } else {
473 assert_ne!(
474 computed_tag, expected_tag_hex,
475 "wycheproof HMAC-SHA3-512 tcId={} ModifiedTag not detected",
476 test["tcId"]
477 );
478 invalid_tested += 1;
479 }
480 }
481 }
482 assert!(valid_tested > 0, "no valid HMAC-SHA3-512 wycheproof tests were run");
483 assert!(invalid_tested > 0, "no invalid HMAC-SHA3-512 wycheproof tests were run");
484 }
485}