Skip to main content

crypto/
hmac.rs

1#[cfg(feature = "zeroize")]
2use zeroize::Zeroize;
3
4use crate::{Hash, Hasher, MAX_HASH_BLOCK_SIZE};
5
6/// HMAC (Hash-based Message Authentication Code) implementation.
7///
8/// Uses a generic hash function implementing the [`Hasher`] trait (e.g.
9/// [`Sha256`](crate::sha2::Sha256) or [`Sha512`](crate::sha2::Sha512)).
10///
11/// # One-shot API
12///
13/// ```ignore
14/// use crypto::hmac::Hmac;
15/// use crypto::sha2::Sha256;
16///
17/// let tag = Hmac::<Sha256>::mac(b"key", b"message");
18/// ```
19///
20/// # Incremental API
21///
22/// ```ignore
23/// use crypto::hmac::Hmac;
24/// use crypto::sha2::Sha256;
25///
26/// let mut mac = Hmac::<Sha256>::new(b"key");
27/// mac.update(b"hello ");
28/// mac.update(b"world");
29/// let tag = mac.finalize();
30/// ```
31#[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    /// One-shot HMAC: computes `HMAC(key, data)` in a single call.
40    ///
41    /// This is a convenience wrapper around [`new`](Self::new) +
42    /// [`update`](Self::update) + [`finalize`](Self::finalize).
43    #[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        // normalize key to block size
54        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        // inner pad = key ^ 0x36
65        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        // outer pad = key ^ 0x5c
71        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        // initialize inner hash: create a fresh instance and feed inner pad
77        let mut hash = H::new();
78        hash.update(&inner_key[..H::BLOCK_SIZE]);
79
80        Hmac {
81            hash,
82            opad,
83        }
84    }
85
86    /// Feed message data to HMAC (can be called multiple times)
87    pub fn update(&mut self, data: &[u8]) {
88        self.hash.update(data);
89    }
90
91    /// Finalize and return HMAC tag. This consumes the Hmac state.
92    pub fn finalize(self) -> Hash {
93        let inner_sum = self.hash.sum();
94
95        // compute outer hash using a fresh instance
96        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        // RFC 4231 TC1
128        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        // RFC 4231 TC2
139        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        // RFC 4231 TC3
147        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        // RFC 4231 TC4
161        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        // RFC 4231 TC6 (TC5 is truncated-output only)
175        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        // RFC 4231 TC7
186        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    // --- Wycheproof test vectors ---
255
256    #[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}