Skip to main content

crypt_io/mac/
hmac_impl.rs

1//! HMAC-SHA256 / HMAC-SHA512 backend.
2//!
3//! Thin wrapper over the `hmac` crate (`RustCrypto`). HMAC accepts a key
4//! of any length — short keys are zero-padded, long keys are hashed down
5//! to the block size, both per [RFC 2104]. The wrapper preserves that
6//! contract; callers do not need to size their keys.
7//!
8//! [RFC 2104]: https://datatracker.ietf.org/doc/html/rfc2104
9
10use hmac::{Hmac, Mac};
11use sha2::{Sha256, Sha512};
12
13use super::{HMAC_SHA256_OUTPUT_LEN, HMAC_SHA512_OUTPUT_LEN};
14use crate::error::{Error, Result};
15
16type HmacSha256Inner = Hmac<Sha256>;
17type HmacSha512Inner = Hmac<Sha512>;
18
19/// Compute an HMAC-SHA256 tag over `data` under `key`.
20///
21/// # Errors
22///
23/// Returns [`Error::Mac`] if the upstream `hmac` crate refuses the key.
24/// In practice this never happens — HMAC accepts any key length — but the
25/// upstream API is fallible by signature, so the wrapper preserves it.
26///
27/// # Example
28///
29/// ```
30/// # #[cfg(feature = "mac-hmac")] {
31/// use crypt_io::mac;
32/// let tag = mac::hmac_sha256(b"shared key", b"message")?;
33/// assert_eq!(tag.len(), 32);
34/// # }
35/// # Ok::<(), crypt_io::Error>(())
36/// ```
37pub fn hmac_sha256(key: &[u8], data: &[u8]) -> Result<[u8; HMAC_SHA256_OUTPUT_LEN]> {
38    let mut mac =
39        HmacSha256Inner::new_from_slice(key).map_err(|_| Error::Mac("hmac-sha256 init"))?;
40    mac.update(data);
41    Ok(mac.finalize().into_bytes().into())
42}
43
44/// Verify an HMAC-SHA256 tag in constant time.
45///
46/// Computes the tag for `(key, data)` and compares it to `expected_tag`.
47/// Returns `Ok(true)` if the tags match, `Ok(false)` if they don't, and
48/// [`Error::Mac`] if the upstream MAC could not be constructed.
49///
50/// **Always** use this rather than `tag == expected`. The comparison
51/// inside is `subtle::ConstantTimeEq` (via the `hmac` crate's
52/// `verify_slice`), so timing does not leak how many leading bytes
53/// matched.
54///
55/// # Errors
56///
57/// Same as [`hmac_sha256`] — the upstream MAC construction is fallible
58/// by signature, unreachable in practice.
59///
60/// # Example
61///
62/// ```
63/// # #[cfg(feature = "mac-hmac")] {
64/// use crypt_io::mac;
65/// let key = b"shared";
66/// let tag = mac::hmac_sha256(key, b"data")?;
67/// assert!(mac::hmac_sha256_verify(key, b"data", &tag)?);
68/// assert!(!mac::hmac_sha256_verify(key, b"tampered", &tag)?);
69/// # }
70/// # Ok::<(), crypt_io::Error>(())
71/// ```
72pub fn hmac_sha256_verify(key: &[u8], data: &[u8], expected_tag: &[u8]) -> Result<bool> {
73    let mut mac =
74        HmacSha256Inner::new_from_slice(key).map_err(|_| Error::Mac("hmac-sha256 init"))?;
75    mac.update(data);
76    Ok(mac.verify_slice(expected_tag).is_ok())
77}
78
79/// Compute an HMAC-SHA512 tag over `data` under `key`.
80///
81/// # Errors
82///
83/// Same as [`hmac_sha256`].
84///
85/// # Example
86///
87/// ```
88/// # #[cfg(feature = "mac-hmac")] {
89/// use crypt_io::mac;
90/// let tag = mac::hmac_sha512(b"shared key", b"message")?;
91/// assert_eq!(tag.len(), 64);
92/// # }
93/// # Ok::<(), crypt_io::Error>(())
94/// ```
95pub fn hmac_sha512(key: &[u8], data: &[u8]) -> Result<[u8; HMAC_SHA512_OUTPUT_LEN]> {
96    let mut mac =
97        HmacSha512Inner::new_from_slice(key).map_err(|_| Error::Mac("hmac-sha512 init"))?;
98    mac.update(data);
99    Ok(mac.finalize().into_bytes().into())
100}
101
102/// Verify an HMAC-SHA512 tag in constant time. See [`hmac_sha256_verify`].
103///
104/// # Errors
105///
106/// Same as [`hmac_sha256_verify`].
107pub fn hmac_sha512_verify(key: &[u8], data: &[u8], expected_tag: &[u8]) -> Result<bool> {
108    let mut mac =
109        HmacSha512Inner::new_from_slice(key).map_err(|_| Error::Mac("hmac-sha512 init"))?;
110    mac.update(data);
111    Ok(mac.verify_slice(expected_tag).is_ok())
112}
113
114/// Streaming HMAC-SHA256 for inputs that don't fit in memory.
115///
116/// Construct with [`HmacSha256::new`], absorb data with
117/// [`update`](Self::update), finalise with [`finalize`](Self::finalize)
118/// (returns the 32-byte tag) or [`verify`](Self::verify) (constant-time
119/// compare against an expected tag).
120///
121/// # Example
122///
123/// ```
124/// # #[cfg(feature = "mac-hmac")] {
125/// use crypt_io::mac::HmacSha256;
126///
127/// let mut m = HmacSha256::new(b"shared key")?;
128/// m.update(b"first chunk ");
129/// m.update(b"second chunk");
130/// let tag = m.finalize();
131/// assert_eq!(tag.len(), 32);
132/// # }
133/// # Ok::<(), crypt_io::Error>(())
134/// ```
135#[derive(Debug, Clone)]
136pub struct HmacSha256 {
137    inner: HmacSha256Inner,
138}
139
140impl HmacSha256 {
141    /// Construct a fresh hasher under `key`.
142    ///
143    /// # Errors
144    ///
145    /// Returns [`Error::Mac`] if the upstream `hmac` crate refuses the key.
146    /// Unreachable in practice (HMAC accepts any key length).
147    pub fn new(key: &[u8]) -> Result<Self> {
148        let inner =
149            HmacSha256Inner::new_from_slice(key).map_err(|_| Error::Mac("hmac-sha256 init"))?;
150        Ok(Self { inner })
151    }
152
153    /// Absorb `data` into the running MAC. Returns `&mut Self` so calls
154    /// can chain.
155    pub fn update(&mut self, data: &[u8]) -> &mut Self {
156        self.inner.update(data);
157        self
158    }
159
160    /// Finalise the MAC and return the 32-byte tag. Consumes the hasher.
161    #[must_use]
162    pub fn finalize(self) -> [u8; HMAC_SHA256_OUTPUT_LEN] {
163        self.inner.finalize().into_bytes().into()
164    }
165
166    /// Finalise and verify against `expected_tag` in constant time.
167    /// Returns `true` iff the computed tag matches `expected_tag`.
168    /// Consumes the hasher.
169    #[must_use]
170    pub fn verify(self, expected_tag: &[u8]) -> bool {
171        self.inner.verify_slice(expected_tag).is_ok()
172    }
173}
174
175/// Streaming HMAC-SHA512. Same shape as [`HmacSha256`] with a 64-byte tag.
176///
177/// # Example
178///
179/// ```
180/// # #[cfg(feature = "mac-hmac")] {
181/// use crypt_io::mac::HmacSha512;
182///
183/// let mut m = HmacSha512::new(b"shared key")?;
184/// m.update(b"first chunk ");
185/// m.update(b"second chunk");
186/// let tag = m.finalize();
187/// assert_eq!(tag.len(), 64);
188/// # }
189/// # Ok::<(), crypt_io::Error>(())
190/// ```
191#[derive(Debug, Clone)]
192pub struct HmacSha512 {
193    inner: HmacSha512Inner,
194}
195
196impl HmacSha512 {
197    /// Construct a fresh hasher under `key`.
198    ///
199    /// # Errors
200    ///
201    /// See [`HmacSha256::new`].
202    pub fn new(key: &[u8]) -> Result<Self> {
203        let inner =
204            HmacSha512Inner::new_from_slice(key).map_err(|_| Error::Mac("hmac-sha512 init"))?;
205        Ok(Self { inner })
206    }
207
208    /// Absorb `data` into the running MAC. Returns `&mut Self` so calls
209    /// can chain.
210    pub fn update(&mut self, data: &[u8]) -> &mut Self {
211        self.inner.update(data);
212        self
213    }
214
215    /// Finalise the MAC and return the 64-byte tag. Consumes the hasher.
216    #[must_use]
217    pub fn finalize(self) -> [u8; HMAC_SHA512_OUTPUT_LEN] {
218        self.inner.finalize().into_bytes().into()
219    }
220
221    /// Finalise and verify against `expected_tag` in constant time.
222    /// Returns `true` iff the computed tag matches `expected_tag`.
223    /// Consumes the hasher.
224    #[must_use]
225    pub fn verify(self, expected_tag: &[u8]) -> bool {
226        self.inner.verify_slice(expected_tag).is_ok()
227    }
228}
229
230#[cfg(test)]
231#[allow(clippy::unwrap_used, clippy::expect_used, unused_results)]
232mod tests {
233    use super::*;
234
235    fn hex_to_bytes(s: &str) -> alloc::vec::Vec<u8> {
236        hex::decode(s).expect("valid hex")
237    }
238
239    // RFC 4231 test vectors for HMAC-SHA256 and HMAC-SHA512. The full
240    // set has 7 cases; we ship cases 1 and 2, which together cover the
241    // basics (short repeated-byte key + ASCII key with ASCII data) and
242    // are the most commonly cited.
243
244    // --- HMAC-SHA256 KATs ---
245
246    /// RFC 4231 Test Case 1: K = 0x0b × 20, data = "Hi There".
247    #[test]
248    fn hmac_sha256_rfc4231_case1() {
249        let key = hex_to_bytes("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
250        let data = b"Hi There";
251        let expected =
252            hex_to_bytes("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7");
253        assert_eq!(&hmac_sha256(&key, data).unwrap()[..], &expected[..]);
254        assert!(hmac_sha256_verify(&key, data, &expected).unwrap());
255    }
256
257    /// RFC 4231 Test Case 2: K = "Jefe" (4 bytes — shorter than block).
258    #[test]
259    fn hmac_sha256_rfc4231_case2() {
260        let key = b"Jefe";
261        let data = b"what do ya want for nothing?";
262        let expected =
263            hex_to_bytes("5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843");
264        assert_eq!(&hmac_sha256(key, data).unwrap()[..], &expected[..]);
265        assert!(hmac_sha256_verify(key, data, &expected).unwrap());
266    }
267
268    // --- HMAC-SHA512 KATs ---
269
270    /// RFC 4231 Test Case 1 (SHA-512 variant).
271    #[test]
272    fn hmac_sha512_rfc4231_case1() {
273        let key = hex_to_bytes("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
274        let data = b"Hi There";
275        let expected = hex_to_bytes(
276            "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cde\
277             daa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854",
278        );
279        assert_eq!(&hmac_sha512(&key, data).unwrap()[..], &expected[..]);
280        assert!(hmac_sha512_verify(&key, data, &expected).unwrap());
281    }
282
283    /// RFC 4231 Test Case 2 (SHA-512 variant).
284    #[test]
285    fn hmac_sha512_rfc4231_case2() {
286        let key = b"Jefe";
287        let data = b"what do ya want for nothing?";
288        let expected = hex_to_bytes(
289            "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea250554\
290             9758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737",
291        );
292        assert_eq!(&hmac_sha512(key, data).unwrap()[..], &expected[..]);
293        assert!(hmac_sha512_verify(key, data, &expected).unwrap());
294    }
295
296    // --- Verify-rejection tests ---
297
298    #[test]
299    fn hmac_sha256_verify_rejects_wrong_tag() {
300        let tag = hmac_sha256(b"key", b"data").unwrap();
301        let mut tampered = tag;
302        tampered[0] ^= 0x01;
303        assert!(!hmac_sha256_verify(b"key", b"data", &tampered).unwrap());
304    }
305
306    #[test]
307    fn hmac_sha256_verify_rejects_wrong_key() {
308        let tag = hmac_sha256(b"correct", b"data").unwrap();
309        assert!(!hmac_sha256_verify(b"wrong", b"data", &tag).unwrap());
310    }
311
312    #[test]
313    fn hmac_sha256_verify_rejects_wrong_data() {
314        let tag = hmac_sha256(b"key", b"original").unwrap();
315        assert!(!hmac_sha256_verify(b"key", b"tampered", &tag).unwrap());
316    }
317
318    #[test]
319    fn hmac_sha256_verify_rejects_truncated_tag() {
320        let tag = hmac_sha256(b"key", b"data").unwrap();
321        // upstream `verify_slice` rejects wrong-length tags; we propagate
322        // the boolean rejection without surfacing the length detail.
323        assert!(!hmac_sha256_verify(b"key", b"data", &tag[..16]).unwrap());
324    }
325
326    #[test]
327    fn hmac_sha512_verify_rejects_wrong_tag() {
328        let tag = hmac_sha512(b"key", b"data").unwrap();
329        let mut tampered = tag;
330        tampered[0] ^= 0x01;
331        assert!(!hmac_sha512_verify(b"key", b"data", &tampered).unwrap());
332    }
333
334    // --- Streaming-equivalence tests ---
335
336    #[test]
337    fn hmac_sha256_streaming_equals_one_shot() {
338        let key = b"shared secret";
339        let data = b"the quick brown fox jumps over the lazy dog";
340        let one_shot = hmac_sha256(key, data).unwrap();
341        let mut m = HmacSha256::new(key).unwrap();
342        m.update(&data[..10]);
343        m.update(&data[10..25]);
344        m.update(&data[25..]);
345        assert_eq!(m.finalize(), one_shot);
346    }
347
348    #[test]
349    fn hmac_sha512_streaming_equals_one_shot() {
350        let key = b"shared secret";
351        let data = b"the quick brown fox jumps over the lazy dog";
352        let one_shot = hmac_sha512(key, data).unwrap();
353        let mut m = HmacSha512::new(key).unwrap();
354        m.update(&data[..10]);
355        m.update(&data[10..25]);
356        m.update(&data[25..]);
357        assert_eq!(m.finalize(), one_shot);
358    }
359
360    #[test]
361    fn hmac_sha256_streaming_chain_returns_self() {
362        let mut m = HmacSha256::new(b"k").unwrap();
363        m.update(b"chain").update(b"-friendly");
364        assert_eq!(m.finalize(), hmac_sha256(b"k", b"chain-friendly").unwrap());
365    }
366
367    #[test]
368    fn hmac_sha512_streaming_chain_returns_self() {
369        let mut m = HmacSha512::new(b"k").unwrap();
370        m.update(b"chain").update(b"-friendly");
371        assert_eq!(m.finalize(), hmac_sha512(b"k", b"chain-friendly").unwrap());
372    }
373
374    // --- Streaming verify tests ---
375
376    #[test]
377    fn hmac_sha256_streaming_verify_accepts_correct_tag() {
378        let key = b"k";
379        let tag = hmac_sha256(key, b"message").unwrap();
380        let mut m = HmacSha256::new(key).unwrap();
381        m.update(b"message");
382        assert!(m.verify(&tag));
383    }
384
385    #[test]
386    fn hmac_sha256_streaming_verify_rejects_wrong_tag() {
387        let key = b"k";
388        let tag = hmac_sha256(key, b"message").unwrap();
389        let mut tampered = tag;
390        tampered[0] ^= 0xff;
391        let mut m = HmacSha256::new(key).unwrap();
392        m.update(b"message");
393        assert!(!m.verify(&tampered));
394    }
395
396    #[test]
397    fn hmac_sha512_streaming_verify_accepts_correct_tag() {
398        let key = b"k";
399        let tag = hmac_sha512(key, b"message").unwrap();
400        let mut m = HmacSha512::new(key).unwrap();
401        m.update(b"message");
402        assert!(m.verify(&tag));
403    }
404
405    // --- Key-length edge cases (HMAC accepts any length). ---
406
407    #[test]
408    fn hmac_sha256_accepts_empty_key() {
409        let tag = hmac_sha256(&[], b"data").unwrap();
410        assert!(hmac_sha256_verify(&[], b"data", &tag).unwrap());
411    }
412
413    #[test]
414    fn hmac_sha256_accepts_long_key() {
415        // Longer than the SHA-256 block size (64 bytes) — HMAC hashes it
416        // down internally.
417        let key = [0xaau8; 256];
418        let tag = hmac_sha256(&key, b"data").unwrap();
419        assert!(hmac_sha256_verify(&key, b"data", &tag).unwrap());
420    }
421}