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