Skip to main content

crypto/
hmac.rs

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