Skip to main content

dryoc/
hmac.rs

1//! # HMAC authentication
2//!
3//! [`HmacSha256`], [`HmacSha512`], and [`HmacSha512256`] provide Rustaceous
4//! wrappers for libsodium's direct HMAC authentication variants.
5//!
6//! HMAC computes a fixed-size authentication tag for a message using a shared
7//! secret key. Anyone with the same key can recompute the tag and verify that
8//! the message was produced by someone who knew the key and that the message
9//! was not changed. HMAC does not encrypt the message.
10//!
11//! Use these types when:
12//!
13//! * you need one of libsodium's direct `crypto_auth_hmacsha*` variants
14//! * two parties already share the same secret key
15//! * the message can be public, but tampering must be detected
16//!
17//! [`HmacSha512256`] matches libsodium's default [`crypto_auth`](crate::auth)
18//! construction. [`HmacSha256`] and [`HmacSha512`] are available for protocol
19//! compatibility when those exact algorithms are required.
20//!
21//! # Rustaceous API example
22//!
23//! ```
24//! use dryoc::hmac::{HmacSha256, HmacSha256Key};
25//! use dryoc::types::*;
26//!
27//! let key = HmacSha256Key::generate();
28//! let message = b"Uneasy lies the head that wears a crown.";
29//!
30//! let mac = HmacSha256::compute_to_vec(key.clone(), message);
31//! HmacSha256::compute_and_verify(&mac, key, message).expect("verify failed");
32//! ```
33//!
34//! The concrete authenticators are type aliases over [`Hmac`] and can also be
35//! used through [`HmacVariant`] in generic code.
36//!
37//! # Incremental interface
38//!
39//! ```
40//! use dryoc::hmac::{HmacSha512256, HmacSha512256Key};
41//! use dryoc::types::*;
42//!
43//! let key = HmacSha512256Key::generate();
44//! let mut auth = HmacSha512256::new(key.clone());
45//! auth.update(b"Though she be but little, ");
46//! auth.update(b"she is fierce.");
47//! let mac = auth.finalize_to_vec();
48//!
49//! let mut verifier = HmacSha512256::new(key);
50//! verifier.update(b"Though she be but little, ");
51//! verifier.update(b"she is fierce.");
52//! verifier.verify(&mac).expect("verify failed");
53//! ```
54//!
55//! # Generic HMAC variants
56//!
57//! ```
58//! use dryoc::constants::{CRYPTO_AUTH_HMACSHA256_BYTES, CRYPTO_AUTH_HMACSHA256_KEYBYTES};
59//! use dryoc::hmac::{Hmac, HmacSha256, HmacSha256Key, HmacSha256Variant, HmacVariant};
60//! use dryoc::types::*;
61//!
62//! fn authenticate<Variant, const KEY_LENGTH: usize, const MAC_LENGTH: usize>(
63//!     key: StackByteArray<KEY_LENGTH>,
64//!     input: &[u8],
65//! ) -> Vec<u8>
66//! where
67//!     Variant: HmacVariant<KEY_LENGTH, MAC_LENGTH>,
68//! {
69//!     Hmac::<Variant, KEY_LENGTH, MAC_LENGTH>::compute_to_vec(key, input)
70//! }
71//!
72//! let key = HmacSha256Key::generate();
73//! let message = b"The quality of mercy is not strained.";
74//! let generic_mac = authenticate::<
75//!     HmacSha256Variant,
76//!     CRYPTO_AUTH_HMACSHA256_KEYBYTES,
77//!     CRYPTO_AUTH_HMACSHA256_BYTES,
78//! >(key.clone(), message);
79//! let concrete_mac = HmacSha256::compute_to_vec(key, message);
80//! assert_eq!(generic_mac, concrete_mac);
81//! ```
82
83use std::marker::PhantomData;
84
85use subtle::ConstantTimeEq;
86use zeroize::Zeroize;
87
88use crate::classic::crypto_auth_hmacsha256::{
89    HmacSha256State, crypto_auth_hmacsha256, crypto_auth_hmacsha256_final,
90    crypto_auth_hmacsha256_init, crypto_auth_hmacsha256_update, crypto_auth_hmacsha256_verify,
91};
92use crate::classic::crypto_auth_hmacsha512::{
93    HmacSha512State, crypto_auth_hmacsha512, crypto_auth_hmacsha512_final,
94    crypto_auth_hmacsha512_init, crypto_auth_hmacsha512_update, crypto_auth_hmacsha512_verify,
95};
96use crate::classic::crypto_auth_hmacsha512256::{
97    HmacSha512256State, crypto_auth_hmacsha512256, crypto_auth_hmacsha512256_final,
98    crypto_auth_hmacsha512256_init, crypto_auth_hmacsha512256_update,
99    crypto_auth_hmacsha512256_verify,
100};
101use crate::constants::{
102    CRYPTO_AUTH_HMACSHA256_BYTES, CRYPTO_AUTH_HMACSHA256_KEYBYTES, CRYPTO_AUTH_HMACSHA512_BYTES,
103    CRYPTO_AUTH_HMACSHA512_KEYBYTES, CRYPTO_AUTH_HMACSHA512256_BYTES,
104    CRYPTO_AUTH_HMACSHA512256_KEYBYTES,
105};
106use crate::error::Error;
107use crate::types::*;
108
109/// Stack-allocated key for HMAC-SHA-256.
110pub type HmacSha256Key = StackByteArray<CRYPTO_AUTH_HMACSHA256_KEYBYTES>;
111/// Stack-allocated message authentication code for HMAC-SHA-256.
112pub type HmacSha256Mac = StackByteArray<CRYPTO_AUTH_HMACSHA256_BYTES>;
113/// Stack-allocated key for HMAC-SHA-512.
114pub type HmacSha512Key = StackByteArray<CRYPTO_AUTH_HMACSHA512_KEYBYTES>;
115/// Stack-allocated message authentication code for HMAC-SHA-512.
116pub type HmacSha512Mac = StackByteArray<CRYPTO_AUTH_HMACSHA512_BYTES>;
117/// Stack-allocated key for HMAC-SHA-512-256.
118pub type HmacSha512256Key = StackByteArray<CRYPTO_AUTH_HMACSHA512256_KEYBYTES>;
119/// Stack-allocated message authentication code for HMAC-SHA-512-256.
120pub type HmacSha512256Mac = StackByteArray<CRYPTO_AUTH_HMACSHA512256_BYTES>;
121
122#[cfg(any(all(feature = "protected", any(unix, windows)), all(doc, not(doctest))))]
123#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
124pub mod protected {
125    //! # Protected memory type aliases for HMAC
126    //!
127    //! This mod provides protected-memory aliases for HMAC keys and MACs. Use
128    //! these aliases when key material or authentication tags should live in
129    //! locked memory.
130    //!
131    //! ```
132    //! use dryoc::hmac::HmacSha256;
133    //! use dryoc::hmac::protected::*;
134    //!
135    //! let key = HmacSha256Key::generate_readonly_locked().expect("key failed");
136    //! let input = HeapBytes::from_slice_into_readonly_locked(b"More matter, with less art.")
137    //!     .expect("input failed");
138    //! let mac: Locked<HmacSha256Mac> = HmacSha256::compute(key, &input);
139    //! ```
140    use super::*;
141    pub use crate::protected::*;
142
143    /// Heap-allocated, page-aligned key for HMAC-SHA-256.
144    pub type HmacSha256Key = HeapByteArray<CRYPTO_AUTH_HMACSHA256_KEYBYTES>;
145    /// Heap-allocated, page-aligned MAC for HMAC-SHA-256.
146    pub type HmacSha256Mac = HeapByteArray<CRYPTO_AUTH_HMACSHA256_BYTES>;
147    /// Heap-allocated, page-aligned key for HMAC-SHA-512.
148    pub type HmacSha512Key = HeapByteArray<CRYPTO_AUTH_HMACSHA512_KEYBYTES>;
149    /// Heap-allocated, page-aligned MAC for HMAC-SHA-512.
150    pub type HmacSha512Mac = HeapByteArray<CRYPTO_AUTH_HMACSHA512_BYTES>;
151    /// Heap-allocated, page-aligned key for HMAC-SHA-512-256.
152    pub type HmacSha512256Key = HeapByteArray<CRYPTO_AUTH_HMACSHA512256_KEYBYTES>;
153    /// Heap-allocated, page-aligned MAC for HMAC-SHA-512-256.
154    pub type HmacSha512256Mac = HeapByteArray<CRYPTO_AUTH_HMACSHA512256_BYTES>;
155}
156
157/// HMAC algorithm variant used by [`Hmac`].
158pub trait HmacVariant<const KEY_LENGTH: usize, const MAC_LENGTH: usize> {
159    /// Incremental state for this HMAC variant.
160    type State;
161    /// Default stack-allocated MAC type used by verification.
162    type Mac: NewByteArray<MAC_LENGTH> + Zeroize;
163
164    /// Computes a MAC in one shot.
165    fn compute(mac: &mut [u8; MAC_LENGTH], input: &[u8], key: &[u8; KEY_LENGTH]);
166    /// Verifies a MAC in one shot.
167    ///
168    /// # Errors
169    ///
170    /// Returns an error if `mac` does not authenticate `input` under `key`.
171    fn verify(mac: &[u8; MAC_LENGTH], input: &[u8], key: &[u8; KEY_LENGTH]) -> Result<(), Error>;
172    /// Initializes incremental authentication.
173    fn init(key: &[u8; KEY_LENGTH]) -> Self::State;
174    /// Updates incremental authentication.
175    fn update(state: &mut Self::State, input: &[u8]);
176    /// Finalizes incremental authentication.
177    fn finalize(state: Self::State, mac: &mut [u8; MAC_LENGTH]);
178}
179
180/// Rustaceous HMAC authenticator for a specific [`HmacVariant`].
181pub struct Hmac<Variant, const KEY_LENGTH: usize, const MAC_LENGTH: usize>
182where
183    Variant: HmacVariant<KEY_LENGTH, MAC_LENGTH>,
184{
185    state: Variant::State,
186    _variant: PhantomData<Variant>,
187}
188
189/// HMAC-SHA-256 algorithm marker.
190#[derive(Clone, Copy, Debug, Default)]
191pub struct HmacSha256Variant;
192/// HMAC-SHA-512 algorithm marker.
193#[derive(Clone, Copy, Debug, Default)]
194pub struct HmacSha512Variant;
195/// HMAC-SHA-512-256 algorithm marker.
196#[derive(Clone, Copy, Debug, Default)]
197pub struct HmacSha512256Variant;
198
199/// Rustaceous HMAC-SHA-256 authenticator.
200pub type HmacSha256 =
201    Hmac<HmacSha256Variant, CRYPTO_AUTH_HMACSHA256_KEYBYTES, CRYPTO_AUTH_HMACSHA256_BYTES>;
202/// Rustaceous HMAC-SHA-512 authenticator.
203pub type HmacSha512 =
204    Hmac<HmacSha512Variant, CRYPTO_AUTH_HMACSHA512_KEYBYTES, CRYPTO_AUTH_HMACSHA512_BYTES>;
205/// Rustaceous HMAC-SHA-512-256 authenticator.
206pub type HmacSha512256 =
207    Hmac<HmacSha512256Variant, CRYPTO_AUTH_HMACSHA512256_KEYBYTES, CRYPTO_AUTH_HMACSHA512256_BYTES>;
208
209macro_rules! impl_hmac_variant {
210    (
211        $variant:ty,
212        $key_len:expr,
213        $mac_len:expr,
214        $state:ty,
215        $mac:ty,
216        $compute:path,
217        $verify:path,
218        $init:path,
219        $update:path,
220        $finalize:path
221    ) => {
222        impl HmacVariant<$key_len, $mac_len> for $variant {
223            type Mac = $mac;
224            type State = $state;
225
226            fn compute(mac: &mut [u8; $mac_len], input: &[u8], key: &[u8; $key_len]) {
227                $compute(mac, input, key);
228            }
229
230            fn verify(
231                mac: &[u8; $mac_len],
232                input: &[u8],
233                key: &[u8; $key_len],
234            ) -> Result<(), Error> {
235                $verify(mac, input, key)
236            }
237
238            fn init(key: &[u8; $key_len]) -> Self::State {
239                $init(key)
240            }
241
242            fn update(state: &mut Self::State, input: &[u8]) {
243                $update(state, input);
244            }
245
246            fn finalize(state: Self::State, mac: &mut [u8; $mac_len]) {
247                $finalize(state, mac);
248            }
249        }
250    };
251}
252
253impl_hmac_variant!(
254    HmacSha256Variant,
255    CRYPTO_AUTH_HMACSHA256_KEYBYTES,
256    CRYPTO_AUTH_HMACSHA256_BYTES,
257    HmacSha256State,
258    HmacSha256Mac,
259    crypto_auth_hmacsha256,
260    crypto_auth_hmacsha256_verify,
261    crypto_auth_hmacsha256_init,
262    crypto_auth_hmacsha256_update,
263    crypto_auth_hmacsha256_final
264);
265
266impl_hmac_variant!(
267    HmacSha512Variant,
268    CRYPTO_AUTH_HMACSHA512_KEYBYTES,
269    CRYPTO_AUTH_HMACSHA512_BYTES,
270    HmacSha512State,
271    HmacSha512Mac,
272    crypto_auth_hmacsha512,
273    crypto_auth_hmacsha512_verify,
274    crypto_auth_hmacsha512_init,
275    crypto_auth_hmacsha512_update,
276    crypto_auth_hmacsha512_final
277);
278
279impl_hmac_variant!(
280    HmacSha512256Variant,
281    CRYPTO_AUTH_HMACSHA512256_KEYBYTES,
282    CRYPTO_AUTH_HMACSHA512256_BYTES,
283    HmacSha512256State,
284    HmacSha512256Mac,
285    crypto_auth_hmacsha512256,
286    crypto_auth_hmacsha512256_verify,
287    crypto_auth_hmacsha512256_init,
288    crypto_auth_hmacsha512256_update,
289    crypto_auth_hmacsha512256_final
290);
291
292impl<Variant, const KEY_LENGTH: usize, const MAC_LENGTH: usize>
293    Hmac<Variant, KEY_LENGTH, MAC_LENGTH>
294where
295    Variant: HmacVariant<KEY_LENGTH, MAC_LENGTH>,
296{
297    /// Computes and returns the message authentication code for `input` using
298    /// `key`.
299    ///
300    /// This function takes ownership of `key`, but HMAC keys may authenticate
301    /// multiple messages. Clone the key first when it is needed again.
302    pub fn compute<
303        Key: ByteArray<KEY_LENGTH>,
304        Input: Bytes + ?Sized,
305        Output: NewByteArray<MAC_LENGTH>,
306    >(
307        key: Key,
308        input: &Input,
309    ) -> Output {
310        let mut output = Output::new_byte_array();
311        Variant::compute(output.as_mut_array(), input.as_slice(), key.as_array());
312        output
313    }
314
315    /// Convenience wrapper around [`Self::compute`] that returns a [`Vec`].
316    pub fn compute_to_vec<Key: ByteArray<KEY_LENGTH>, Input: Bytes + ?Sized>(
317        key: Key,
318        input: &Input,
319    ) -> Vec<u8> {
320        Self::compute(key, input)
321    }
322
323    /// Verifies `other_mac` against `input` using `key`.
324    ///
325    /// # Errors
326    ///
327    /// Returns an error if `other_mac` does not authenticate `input` under
328    /// `key`.
329    pub fn compute_and_verify<
330        OtherMac: ByteArray<MAC_LENGTH>,
331        Key: ByteArray<KEY_LENGTH>,
332        Input: Bytes + ?Sized,
333    >(
334        other_mac: &OtherMac,
335        key: Key,
336        input: &Input,
337    ) -> Result<(), Error> {
338        Variant::verify(other_mac.as_array(), input.as_slice(), key.as_array())
339    }
340
341    /// Returns a new incremental authenticator for `key`.
342    ///
343    /// This function takes ownership of `key`, but HMAC keys may authenticate
344    /// multiple messages. Clone the key first when it is needed again.
345    pub fn new<Key: ByteArray<KEY_LENGTH>>(key: Key) -> Self {
346        Self {
347            state: Variant::init(key.as_array()),
348            _variant: PhantomData,
349        }
350    }
351
352    /// Updates the authenticator with `input`.
353    pub fn update<Input: Bytes + ?Sized>(&mut self, input: &Input) {
354        Variant::update(&mut self.state, input.as_slice())
355    }
356
357    /// Finalizes this authenticator, returning the message authentication code.
358    pub fn finalize<Output: NewByteArray<MAC_LENGTH>>(self) -> Output {
359        let mut output = Output::new_byte_array();
360        Variant::finalize(self.state, output.as_mut_array());
361        output
362    }
363
364    /// Finalizes this authenticator, returning the message authentication code
365    /// as a [`Vec`].
366    pub fn finalize_to_vec(self) -> Vec<u8> {
367        self.finalize()
368    }
369
370    /// Finalizes this authenticator and verifies that the computed code matches
371    /// `other_mac` using a constant-time comparison.
372    ///
373    /// # Errors
374    ///
375    /// Returns an error if `other_mac` does not match the authentication code
376    /// computed from the data passed to [`Hmac::update`].
377    pub fn verify<OtherMac: ByteArray<MAC_LENGTH>>(
378        self,
379        other_mac: &OtherMac,
380    ) -> Result<(), Error> {
381        let mut computed_mac = Variant::Mac::new_byte_array();
382        Variant::finalize(self.state, computed_mac.as_mut_array());
383        let valid = other_mac
384            .as_array()
385            .ct_eq(computed_mac.as_array())
386            .unwrap_u8();
387        computed_mac.as_mut_slice().zeroize();
388
389        if valid == 1 {
390            Ok(())
391        } else {
392            Err(Error::AuthenticationFailed)
393        }
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use super::*;
400
401    #[test]
402    fn test_hmac_sha256() {
403        let key = HmacSha256Key::generate();
404        let mac = HmacSha256::compute_to_vec(key.clone(), b"Data to authenticate");
405
406        HmacSha256::compute_and_verify(&mac, key, b"Data to authenticate").expect("verify failed");
407    }
408
409    #[test]
410    fn test_hmac_sha512() {
411        let key = HmacSha512Key::generate();
412        let mut auth = HmacSha512::new(key.clone());
413        auth.update(b"Multi-part");
414        auth.update(b"data");
415        let mac = auth.finalize_to_vec();
416
417        let mut verifier = HmacSha512::new(key);
418        verifier.update(b"Multi-part");
419        verifier.update(b"data");
420        verifier.verify(&mac).expect("verify failed");
421    }
422
423    #[test]
424    fn test_hmac_sha512256_rejects_invalid_input() {
425        let key = HmacSha512256Key::generate();
426        let mac = HmacSha512256::compute_to_vec(key.clone(), b"Data to authenticate");
427
428        HmacSha512256::compute_and_verify(&mac, key, b"Invalid data")
429            .expect_err("verify should fail");
430    }
431
432    #[test]
433    fn test_hmac_variant_generic_api() {
434        fn compute_with_variant<Variant, const KEY_LENGTH: usize, const MAC_LENGTH: usize>(
435            key: StackByteArray<KEY_LENGTH>,
436            input: &[u8],
437        ) -> Vec<u8>
438        where
439            Variant: HmacVariant<KEY_LENGTH, MAC_LENGTH>,
440        {
441            Hmac::<Variant, KEY_LENGTH, MAC_LENGTH>::compute_to_vec(key, input)
442        }
443
444        let key = HmacSha256Key::generate();
445        let generic_mac = compute_with_variant::<
446            HmacSha256Variant,
447            CRYPTO_AUTH_HMACSHA256_KEYBYTES,
448            CRYPTO_AUTH_HMACSHA256_BYTES,
449        >(key.clone(), b"Data to authenticate");
450        let concrete_mac = HmacSha256::compute_to_vec(key, b"Data to authenticate");
451
452        assert_eq!(generic_mac, concrete_mac);
453    }
454}