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#[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        // normalize key to block size
25        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        // inner pad = key ^ 0x36
36        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        // outer pad = key ^ 0x5c
42        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        // initialize inner hash: create a fresh instance and feed inner pad
48        let mut hash = H::new();
49        hash.update(&inner_key[..H::BLOCK_SIZE]);
50
51        Hmac {
52            hash,
53            opad,
54        }
55    }
56
57    /// Feed message data to HMAC (can be called multiple times)
58    pub fn update(&mut self, data: &[u8]) {
59        self.hash.update(data);
60    }
61
62    /// Finalize and return HMAC tag. This consumes the Hmac state.
63    pub fn finalize(self) -> Hash {
64        let inner_sum = self.hash.sum();
65
66        // compute outer hash using a fresh instance
67        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        // RFC 4231 TC1
99        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        // RFC 4231 TC2
110        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        // RFC 4231 TC3
118        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        // RFC 4231 TC4
132        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        // RFC 4231 TC6 (TC5 is truncated-output only)
146        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        // RFC 4231 TC7
157        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    // --- Wycheproof test vectors ---
226
227    #[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}