Skip to main content

cryptography/hash/
hmac.rs

1//! Hash-based Message Authentication Code (HMAC).
2//!
3//! This is the standard HMAC construction from FIPS 198-1 / RFC 2104, layered
4//! over any fixed-output hash that implements [`crate::hash::Digest`].
5
6use super::Digest;
7
8/// Streaming HMAC state over an arbitrary in-tree digest.
9pub struct Hmac<H: Digest> {
10    inner: H,
11    outer: H,
12}
13
14impl<H: Digest> Hmac<H> {
15    /// Build the RFC 2104 / FIPS 198-1 keyed inner and outer hash states.
16    #[must_use]
17    pub fn new(key: &[u8]) -> Self {
18        let mut key_block = vec![0u8; H::BLOCK_LEN];
19        if key.len() > H::BLOCK_LEN {
20            // HMAC hashes oversize keys down to one digest-width block first so
21            // the actual ipad/opad processing always starts from exactly one
22            // block of key material, regardless of caller input length.
23            let mut digest = H::digest(key);
24            key_block[..H::OUTPUT_LEN].copy_from_slice(&digest);
25            crate::ct::zeroize_slice(digest.as_mut_slice());
26        } else {
27            key_block[..key.len()].copy_from_slice(key);
28        }
29
30        let mut ipad = key_block.clone();
31        let mut opad = key_block;
32        for b in &mut ipad {
33            *b ^= 0x36;
34        }
35        for b in &mut opad {
36            *b ^= 0x5c;
37        }
38
39        let mut inner = H::new();
40        inner.update(&ipad);
41        let mut outer = H::new();
42        outer.update(&opad);
43
44        crate::ct::zeroize_slice(ipad.as_mut_slice());
45        crate::ct::zeroize_slice(opad.as_mut_slice());
46
47        Self { inner, outer }
48    }
49
50    /// Absorb more message bytes into the keyed inner hash.
51    pub fn update(&mut self, data: &[u8]) {
52        self.inner.update(data);
53    }
54
55    #[must_use]
56    /// Finalize the MAC and return the authentication tag.
57    pub fn finalize(mut self) -> Vec<u8> {
58        let mut inner_digest = vec![0u8; H::OUTPUT_LEN];
59        // `finalize_reset` is used here for two reasons: it produces the
60        // standard inner digest and it actively wipes the live keyed hash state
61        // instead of leaving the ipad-derived chaining value behind until drop.
62        self.inner.finalize_reset(&mut inner_digest);
63        self.outer.update(&inner_digest);
64        let mut out = vec![0u8; H::OUTPUT_LEN];
65        self.outer.finalize_reset(&mut out);
66        crate::ct::zeroize_slice(inner_digest.as_mut_slice());
67        out
68    }
69
70    #[must_use]
71    /// Compute an HMAC tag in one shot.
72    pub fn compute(key: &[u8], data: &[u8]) -> Vec<u8> {
73        let mut mac = Self::new(key);
74        mac.update(data);
75        mac.finalize()
76    }
77
78    #[must_use]
79    /// Compute and compare the tag in constant time.
80    pub fn verify(key: &[u8], data: &[u8], tag: &[u8]) -> bool {
81        crate::ct::constant_time_eq_mask(&Self::compute(key, data), tag) == u8::MAX
82    }
83}
84
85impl<H: Digest> Drop for Hmac<H> {
86    fn drop(&mut self) {
87        self.inner.zeroize();
88        self.outer.zeroize();
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use crate::{Sha256, Sha3_256, Sha3_512};
96    fn hex(bytes: &[u8]) -> String {
97        let mut out = String::with_capacity(bytes.len() * 2);
98        for b in bytes {
99            use core::fmt::Write;
100            let _ = write!(&mut out, "{b:02x}");
101        }
102        out
103    }
104
105    #[test]
106    fn hmac_sha3_256_known_vector() {
107        let tag = Hmac::<Sha3_256>::compute(b"key", b"The quick brown fox jumps over the lazy dog");
108        assert_eq!(
109            hex(&tag),
110            "8c6e0683409427f8931711b10ca92a50".to_owned() + "6eb1fafa48fadd66d76126f47ac2c333"
111        );
112    }
113
114    #[test]
115    fn hmac_sha3_512_known_vector() {
116        let tag = Hmac::<Sha3_512>::compute(b"key", b"The quick brown fox jumps over the lazy dog");
117        assert_eq!(
118            hex(&tag),
119            "237a35049c40b3ef5ddd960b3dc893d8".to_owned()
120                + "284953b9a4756611b1b61bffcf53edd9"
121                + "79f93547db714b06ef0a692062c609b7"
122                + "0208ab8d4a280ceee40ed8100f293063"
123        );
124    }
125
126    #[test]
127    fn hmac_sha3_256_streaming_matches_one_shot() {
128        let key = (0u8..32).collect::<Vec<_>>();
129        let expected = Hmac::<Sha3_256>::compute(&key, b"abc");
130
131        let mut mac = Hmac::<Sha3_256>::new(&key);
132        mac.update(b"a");
133        mac.update(b"b");
134        mac.update(b"c");
135        let got = mac.finalize();
136
137        assert_eq!(got, expected);
138        assert_eq!(
139            hex(&got),
140            "632f618ac17ba24355d9ee1fd187cf75".to_owned() + "bb5b68e6948804bf6674bf5ee7f1c345"
141        );
142        assert!(Hmac::<Sha3_256>::verify(&key, b"abc", &got));
143    }
144
145    #[test]
146    fn hmac_sha3_256_matches_openssl() {
147        let key = b"key";
148        let msg = b"The quick brown fox jumps over the lazy dog";
149        let Some(expected) = crate::test_utils::run_openssl(
150            &[
151                "dgst",
152                "-sha3-256",
153                "-mac",
154                "HMAC",
155                "-macopt",
156                "hexkey:6b6579",
157                "-binary",
158            ],
159            msg,
160        ) else {
161            return;
162        };
163
164        let tag = Hmac::<Sha3_256>::compute(key, msg);
165        assert_eq!(tag, expected);
166    }
167
168    #[test]
169    fn hmac_sha256_rfc4231_case1() {
170        let key = [0x0bu8; 20];
171        let tag = Hmac::<Sha256>::compute(&key, b"Hi There");
172        assert_eq!(
173            hex(&tag),
174            "b0344c61d8db38535ca8afceaf0bf12b".to_owned() + "881dc200c9833da726e9376c2e32cff7"
175        );
176    }
177
178    #[test]
179    fn hmac_sha256_rfc4231_case2() {
180        let tag = Hmac::<Sha256>::compute(b"Jefe", b"what do ya want for nothing?");
181        assert_eq!(
182            hex(&tag),
183            "5bdcc146bf60754e6a042426089575c7".to_owned() + "5a003f089d2739839dec58b964ec3843"
184        );
185    }
186
187    #[test]
188    fn hmac_sha256_rfc4231_case3() {
189        let key = [0xaau8; 20];
190        let data = [0xddu8; 50];
191        let tag = Hmac::<Sha256>::compute(&key, &data);
192        assert_eq!(
193            hex(&tag),
194            "773ea91e36800e46854db8ebd09181a7".to_owned() + "2959098b3ef8c122d9635514ced565fe"
195        );
196    }
197
198    #[test]
199    fn hmac_sha256_rfc4231_case4() {
200        let key = [
201            0x01u8, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
202            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
203        ];
204        let data = [0xcdu8; 50];
205        let tag = Hmac::<Sha256>::compute(&key, &data);
206        assert_eq!(
207            hex(&tag),
208            "82558a389a443c0ea4cc819899f2083a".to_owned() + "85f0faa3e578f8077a2e3ff46729665b"
209        );
210    }
211
212    #[test]
213    fn hmac_sha256_rfc4231_case5_truncated() {
214        let key = [0x0cu8; 20];
215        let tag = Hmac::<Sha256>::compute(&key, b"Test With Truncation");
216        assert_eq!(hex(&tag[..16]), "a3b6167473100ee06e0c796c2955552b");
217    }
218
219    #[test]
220    fn hmac_sha256_rfc4231_case6() {
221        let key = [0xaau8; 131];
222        let tag = Hmac::<Sha256>::compute(
223            &key,
224            b"Test Using Larger Than Block-Size Key - Hash Key First",
225        );
226        assert_eq!(
227            hex(&tag),
228            "60e431591ee0b67f0d8a26aacbf5b77f".to_owned() + "8e0bc6213728c5140546040f0ee37f54"
229        );
230    }
231
232    #[test]
233    fn hmac_sha256_rfc4231_case7() {
234        let key = [0xaau8; 131];
235        let data = 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.";
236        let tag = Hmac::<Sha256>::compute(&key, data);
237        assert_eq!(
238            hex(&tag),
239            "9b09ffa71b942fcb27635fbcd5b0e944".to_owned() + "bfdc63644f0713938a7f51535c3a35e2"
240        );
241    }
242
243    #[test]
244    fn hmac_sha256_matches_openssl() {
245        let key = b"key";
246        let msg = b"The quick brown fox jumps over the lazy dog";
247        let Some(expected) = crate::test_utils::run_openssl(
248            &[
249                "dgst",
250                "-sha256",
251                "-mac",
252                "HMAC",
253                "-macopt",
254                "hexkey:6b6579",
255                "-binary",
256            ],
257            msg,
258        ) else {
259            return;
260        };
261
262        let tag = Hmac::<Sha256>::compute(key, msg);
263        assert_eq!(tag.as_slice(), expected.as_slice());
264    }
265}