Skip to main content

crypto_hmac/
types.rs

1// SPDX-FileCopyrightText: Copyright © 2026 ReallyMe LLC. All rights reserved
2//
3// SPDX-License-Identifier: Apache-2.0
4
5use crypto_core::{CryptoError, MacAlgorithm, MacFailureKind, MacHash};
6use subtle::ConstantTimeEq;
7use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
8
9/// Length in bytes of an HMAC-SHA-256 tag.
10pub const HMAC_SHA256_TAG_LENGTH: usize = 32;
11/// Length in bytes of an HMAC-SHA-384 tag.
12pub const HMAC_SHA384_TAG_LENGTH: usize = 48;
13/// Length in bytes of an HMAC-SHA-512 tag.
14pub const HMAC_SHA512_TAG_LENGTH: usize = 64;
15/// Maximum accepted HMAC key length in bytes.
16///
17/// HMAC itself permits arbitrary-length keys, but the public API caps accepted
18/// key material so boundary callers cannot force unbounded allocation. Long
19/// keys above the SHA-512 block size are already hashed by HMAC internally.
20pub const HMAC_MAX_KEY_LENGTH: usize = 4096;
21
22/// HMAC key material.
23///
24/// The key is copied into an owned zeroizing buffer before use so callers can
25/// drop their input independently and the primitive controls memory cleanup.
26#[derive(Zeroize, ZeroizeOnDrop)]
27pub struct HmacKey {
28    bytes: Zeroizing<Vec<u8>>,
29}
30
31impl HmacKey {
32    /// Constructs an HMAC key from raw bytes.
33    ///
34    /// Empty keys are rejected because they provide no secret entropy. Very
35    /// large keys are rejected to keep allocation behavior deterministic at
36    /// FFI and platform boundaries.
37    pub fn from_slice(input: &[u8]) -> Result<Self, CryptoError> {
38        if input.is_empty() || input.len() > HMAC_MAX_KEY_LENGTH {
39            return Err(CryptoError::Mac {
40                hash: MacHash::Sha2_256,
41                kind: MacFailureKind::InvalidKeyLength,
42            });
43        }
44
45        Ok(Self {
46            bytes: Zeroizing::new(input.to_vec()),
47        })
48    }
49
50    /// Borrows the raw key bytes.
51    pub fn as_bytes(&self) -> &[u8] {
52        &self.bytes
53    }
54}
55
56/// HMAC authentication tag bytes.
57#[derive(Clone, Zeroize, ZeroizeOnDrop)]
58pub struct HmacTag {
59    bytes: [u8; HMAC_SHA512_TAG_LENGTH],
60    len: usize,
61}
62
63impl PartialEq for HmacTag {
64    fn eq(&self, other: &Self) -> bool {
65        // Compare the complete fixed-capacity representation as well as the
66        // public tag length so `==` cannot become a prefix timing oracle for
67        // downstream callers that compare computed and received MACs.
68        bool::from(self.bytes.ct_eq(&other.bytes) & self.len.ct_eq(&other.len))
69    }
70}
71
72impl Eq for HmacTag {}
73
74impl HmacTag {
75    /// Constructs a tag from fixed-size bytes for `algorithm`.
76    pub fn from_slice(algorithm: MacAlgorithm, input: &[u8]) -> Result<Self, CryptoError> {
77        let expected = tag_length(algorithm);
78        if input.len() != expected {
79            return Err(CryptoError::Mac {
80                hash: mac_hash(algorithm),
81                kind: MacFailureKind::InvalidTagLength,
82            });
83        }
84
85        let mut bytes = [0u8; HMAC_SHA512_TAG_LENGTH];
86        bytes[..expected].copy_from_slice(input);
87        Ok(Self {
88            bytes,
89            len: expected,
90        })
91    }
92
93    /// Borrows the tag bytes.
94    pub fn as_bytes(&self) -> &[u8] {
95        &self.bytes[..self.len]
96    }
97
98    /// Returns the tag length in bytes.
99    pub fn len(&self) -> usize {
100        self.len
101    }
102
103    /// Returns whether this tag contains zero bytes.
104    pub fn is_empty(&self) -> bool {
105        self.len == 0
106    }
107
108    /// Consumes the tag and returns the owned tag bytes.
109    pub fn into_vec(self) -> Vec<u8> {
110        self.as_bytes().to_vec()
111    }
112}
113
114impl core::fmt::Debug for HmacTag {
115    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
116        write!(f, "HmacTag(len={})", self.len)
117    }
118}
119
120pub(crate) fn tag_length(algorithm: MacAlgorithm) -> usize {
121    match algorithm {
122        MacAlgorithm::HmacSha256 => HMAC_SHA256_TAG_LENGTH,
123        MacAlgorithm::HmacSha384 => HMAC_SHA384_TAG_LENGTH,
124        MacAlgorithm::HmacSha512 => HMAC_SHA512_TAG_LENGTH,
125    }
126}
127
128pub(crate) fn mac_hash(algorithm: MacAlgorithm) -> MacHash {
129    match algorithm {
130        MacAlgorithm::HmacSha256 => MacHash::Sha2_256,
131        MacAlgorithm::HmacSha384 => MacHash::Sha2_384,
132        MacAlgorithm::HmacSha512 => MacHash::Sha2_512,
133    }
134}