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