1#[cfg(feature = "zeroize")]
2use zeroize::Zeroize;
3
4use crate::{Hash, Hasher, MAX_HASH_BLOCK_SIZE};
5
6#[derive(Clone)]
7#[cfg_attr(feature = "zeroize", derive(Zeroize))]
8pub struct Hmac<H: Hasher> {
9 hash: H,
10 opad: [u8; MAX_HASH_BLOCK_SIZE],
11}
12
13impl<H: Hasher> Hmac<H> {
14 #[inline]
15 pub fn mac(key: &[u8], data: &[u8]) -> Hash {
16 let mut mac = Self::new(key);
17 mac.update(data);
18 return mac.finalize();
19 }
20
21 pub fn new(key: &[u8]) -> Self {
22 let mut key_block = [0u8; MAX_HASH_BLOCK_SIZE];
23
24 if key.len() > H::BLOCK_SIZE {
26 let mut h = H::new();
27 h.update(key);
28 let hashed = h.sum();
29 let hashed_bytes = hashed.as_ref();
30 key_block[..hashed_bytes.len()].copy_from_slice(hashed_bytes);
31 } else {
32 key_block[..key.len()].copy_from_slice(key);
33 }
34
35 let mut inner_key = [0u8; MAX_HASH_BLOCK_SIZE];
37 for i in 0..H::BLOCK_SIZE {
38 inner_key[i] = key_block[i] ^ 0x36;
39 }
40
41 let mut opad = [0u8; MAX_HASH_BLOCK_SIZE];
43 for i in 0..H::BLOCK_SIZE {
44 opad[i] = key_block[i] ^ 0x5c;
45 }
46
47 let mut hash = H::new();
49 hash.update(&inner_key[..H::BLOCK_SIZE]);
50
51 Hmac {
52 hash,
53 opad,
54 }
55 }
56
57 pub fn update(&mut self, data: &[u8]) {
59 self.hash.update(data);
60 }
61
62 pub fn finalize(self) -> Hash {
64 let inner_sum = self.hash.sum();
65
66 let mut outer = H::new();
68 outer.update(&self.opad[..H::BLOCK_SIZE]);
69 outer.update(inner_sum.as_ref());
70 outer.sum()
71 }
72}
73
74#[cfg(test)]
75mod hmac_tests {
76 use crate::{
77 hmac::Hmac,
78 sha2::{Sha256, Sha512},
79 };
80
81 #[derive(Clone, Copy)]
82 enum TestInput {
83 Bytes(&'static [u8]),
84 Repeated { byte: u8, len: usize },
85 RangeInclusive { start: u8, end: u8 },
86 }
87
88 #[derive(Clone, Copy)]
89 struct HmacTestVector {
90 source: &'static str,
91 key: TestInput,
92 data: TestInput,
93 expected_sha256: &'static str,
94 expected_sha512: &'static str,
95 }
96
97 const HMAC_TEST_VECTORS: [HmacTestVector; 6] = [
98 HmacTestVector {
100 source: "RFC 4231 TC1",
101 key: TestInput::Repeated {
102 byte: 0x0b,
103 len: 20,
104 },
105 data: TestInput::Bytes(b"Hi There"),
106 expected_sha256: "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7",
107 expected_sha512: "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854",
108 },
109 HmacTestVector {
111 source: "RFC 4231 TC2",
112 key: TestInput::Bytes(b"Jefe"),
113 data: TestInput::Bytes(b"what do ya want for nothing?"),
114 expected_sha256: "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843",
115 expected_sha512: "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737",
116 },
117 HmacTestVector {
119 source: "RFC 4231 TC3",
120 key: TestInput::Repeated {
121 byte: 0xaa,
122 len: 20,
123 },
124 data: TestInput::Repeated {
125 byte: 0xdd,
126 len: 50,
127 },
128 expected_sha256: "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe",
129 expected_sha512: "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb",
130 },
131 HmacTestVector {
133 source: "RFC 4231 TC4",
134 key: TestInput::RangeInclusive {
135 start: 0x01,
136 end: 0x19,
137 },
138 data: TestInput::Repeated {
139 byte: 0xcd,
140 len: 50,
141 },
142 expected_sha256: "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b",
143 expected_sha512: "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd",
144 },
145 HmacTestVector {
147 source: "RFC 4231 TC6",
148 key: TestInput::Repeated {
149 byte: 0xaa,
150 len: 131,
151 },
152 data: TestInput::Bytes(b"Test Using Larger Than Block-Size Key - Hash Key First"),
153 expected_sha256: "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54",
154 expected_sha512: "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598",
155 },
156 HmacTestVector {
158 source: "RFC 4231 TC7",
159 key: TestInput::Repeated {
160 byte: 0xaa,
161 len: 131,
162 },
163 data: TestInput::Bytes(
164 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.",
165 ),
166 expected_sha256: "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2",
167 expected_sha512: "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58",
168 },
169 ];
170
171 fn materialize(input: TestInput) -> Vec<u8> {
172 match input {
173 TestInput::Bytes(bytes) => bytes.to_vec(),
174 TestInput::Repeated {
175 byte,
176 len,
177 } => vec![byte; len],
178 TestInput::RangeInclusive {
179 start,
180 end,
181 } => (start..=end).collect(),
182 }
183 }
184
185 fn hmac256(key: &[u8], data: &[u8]) -> String {
186 let mut mac = Hmac::<Sha256>::new(key);
187 mac.update(data);
188 hex::encode(mac.finalize().as_ref())
189 }
190
191 fn hmac512(key: &[u8], data: &[u8]) -> String {
192 let mut mac = Hmac::<Sha512>::new(key);
193 mac.update(data);
194 hex::encode(mac.finalize().as_ref())
195 }
196
197 #[test]
198 fn hmac_vectors() {
199 for vector in HMAC_TEST_VECTORS {
200 let key = materialize(vector.key);
201 let data = materialize(vector.data);
202
203 let single256 = hmac256(&key, &data);
204 let single512 = hmac512(&key, &data);
205
206 assert_eq!(single256, vector.expected_sha256, "{}", vector.source);
207 assert_eq!(single512, vector.expected_sha512, "{}", vector.source);
208
209 let mut mac256 = Hmac::<Sha256>::new(&key);
210 for chunk in data.chunks(7) {
211 mac256.update(chunk);
212 }
213 let incremental256 = hex::encode(mac256.finalize().as_ref());
214 assert_eq!(incremental256, single256, "{} incremental sha256", vector.source);
215
216 let mut mac512 = Hmac::<Sha512>::new(&key);
217 for chunk in data.chunks(13) {
218 mac512.update(chunk);
219 }
220 let incremental512 = hex::encode(mac512.finalize().as_ref());
221 assert_eq!(incremental512, single512, "{} incremental sha512", vector.source);
222 }
223 }
224
225 #[test]
228 fn hmac_sha256_wycheproof() {
229 let data: serde_json::Value =
230 serde_json::from_str(include_str!("../testdata/wycheproof/testvectors_v1/hmac_sha256_test.json")).unwrap();
231 let mut valid_tested = 0u64;
232 let mut invalid_tested = 0u64;
233 for group in data["testGroups"].as_array().unwrap() {
234 let tag_size_bits = group["tagSize"].as_u64().unwrap();
235 let tag_size_bytes = (tag_size_bits / 8) as usize;
236 for test in group["tests"].as_array().unwrap() {
237 let key_hex = test["key"].as_str().unwrap();
238 let msg_hex = test["msg"].as_str().unwrap();
239 let expected_tag_hex = test["tag"].as_str().unwrap();
240 let result = test["result"].as_str().unwrap();
241
242 let key = hex::decode(key_hex).unwrap();
243 let msg = hex::decode(msg_hex).unwrap();
244
245 let computed = Hmac::<Sha256>::mac(&key, &msg);
246 let computed_tag = hex::encode(&computed.as_ref()[..tag_size_bytes]);
247
248 if result == "valid" {
249 assert_eq!(
250 computed_tag, expected_tag_hex,
251 "wycheproof HMAC-SHA-256 tcId={} tagSize={}",
252 test["tcId"], tag_size_bits
253 );
254 valid_tested += 1;
255 } else {
256 assert_ne!(
257 computed_tag, expected_tag_hex,
258 "wycheproof HMAC-SHA-256 tcId={} ModifiedTag not detected",
259 test["tcId"]
260 );
261 invalid_tested += 1;
262 }
263 }
264 }
265 assert!(valid_tested > 0, "no valid HMAC-SHA-256 wycheproof tests were run");
266 assert!(invalid_tested > 0, "no invalid HMAC-SHA-256 wycheproof tests were run");
267 }
268
269 #[test]
270 fn hmac_sha512_wycheproof() {
271 let data: serde_json::Value =
272 serde_json::from_str(include_str!("../testdata/wycheproof/testvectors_v1/hmac_sha512_test.json")).unwrap();
273 let mut valid_tested = 0u64;
274 let mut invalid_tested = 0u64;
275 for group in data["testGroups"].as_array().unwrap() {
276 let tag_size_bits = group["tagSize"].as_u64().unwrap();
277 let tag_size_bytes = (tag_size_bits / 8) as usize;
278 for test in group["tests"].as_array().unwrap() {
279 let key_hex = test["key"].as_str().unwrap();
280 let msg_hex = test["msg"].as_str().unwrap();
281 let expected_tag_hex = test["tag"].as_str().unwrap();
282 let result = test["result"].as_str().unwrap();
283
284 let key = hex::decode(key_hex).unwrap();
285 let msg = hex::decode(msg_hex).unwrap();
286
287 let computed = Hmac::<Sha512>::mac(&key, &msg);
288 let computed_tag = hex::encode(&computed.as_ref()[..tag_size_bytes]);
289
290 if result == "valid" {
291 assert_eq!(
292 computed_tag, expected_tag_hex,
293 "wycheproof HMAC-SHA-512 tcId={} tagSize={}",
294 test["tcId"], tag_size_bits
295 );
296 valid_tested += 1;
297 } else {
298 assert_ne!(
299 computed_tag, expected_tag_hex,
300 "wycheproof HMAC-SHA-512 tcId={} ModifiedTag not detected",
301 test["tcId"]
302 );
303 invalid_tested += 1;
304 }
305 }
306 }
307 assert!(valid_tested > 0, "no valid HMAC-SHA-512 wycheproof tests were run");
308 assert!(invalid_tested > 0, "no invalid HMAC-SHA-512 wycheproof tests were run");
309 }
310}