Skip to main content

ic_mac/
hmac.rs

1//! FIPS 198-1 HMAC, generic over any digest.
2
3use ic_core::traits::{Algorithm, Digest, Mac, SelfTest};
4use ic_core::{ensure, Result, Zeroize};
5
6/// The largest block size among supported digests.
7///
8/// SHA-512 uses 128 bytes; SHA3-224 has the widest rate at 144, so the padded
9/// key buffers are sized for it.
10const MAX_BLOCK_LEN: usize = 144;
11
12/// HMAC over the digest `D`.
13///
14/// The key is processed per FIPS 198-1: hashed if longer than the block size,
15/// zero-padded otherwise. The padded key, and the hash of a long key, are
16/// zeroized before the constructor returns.
17#[derive(Clone)]
18pub struct Hmac<D: Digest> {
19    inner: D,
20    outer: D,
21}
22
23/// A digest that can name its HMAC instantiation in the ontology.
24///
25/// Rust cannot concatenate `&'static str` constants at compile time, so the
26/// composed identifier (`"hmac-sha2-256"`) is declared explicitly per digest
27/// rather than derived from [`Digest::ID`].
28pub trait HmacDigest: Digest {
29    /// Ontology identifier of the HMAC built on this digest.
30    const HMAC_ID: &'static str;
31    /// Display name of the HMAC built on this digest.
32    const HMAC_NAME: &'static str;
33}
34
35impl<D: HmacDigest> Algorithm for Hmac<D> {
36    const ID: &'static str = D::HMAC_ID;
37    const NAME: &'static str = D::HMAC_NAME;
38}
39
40impl<D: HmacDigest> Mac for Hmac<D> {
41    type Tag = D::Output;
42    const TAG_LEN: usize = D::OUTPUT_LEN;
43
44    fn new(key: &[u8]) -> Result<Self> {
45        ensure!(
46            D::BLOCK_LEN <= MAX_BLOCK_LEN,
47            InvalidParameter,
48            "digest block exceeds hmac buffer"
49        );
50
51        // One buffer, built as the inner pad directly -- the zero-padded key
52        // XOR 0x36 -- and turned into the outer pad in place, since
53        // `k ^ 0x5c == (k ^ 0x36) ^ (0x36 ^ 0x5c)`. The wipe is volatile and
54        // byte by byte, which is what makes it stick and also what makes it
55        // cost: it now covers the one block this digest used, not two
56        // buffers sized for the widest digest there is. For SHA-256 that is
57        // 64 bytes where it was 288, on every HMAC key setup -- and HKDF sets
58        // up a key per call.
59        let mut pad = [0x36u8; MAX_BLOCK_LEN];
60        let block = &mut pad[..D::BLOCK_LEN];
61        if key.len() > D::BLOCK_LEN {
62            let mut hashed = D::digest(key);
63            for (p, k) in block.iter_mut().zip(hashed.as_ref()) {
64                *p ^= k;
65            }
66            // Key-equivalent: HMAC under the hash is HMAC under the key.
67            hashed.as_mut().zeroize();
68        } else {
69            for (p, k) in block.iter_mut().zip(key) {
70                *p ^= k;
71            }
72        }
73
74        let mut inner = D::new();
75        inner.update(block);
76        for p in block.iter_mut() {
77            *p ^= 0x36 ^ 0x5c;
78        }
79        let mut outer = D::new();
80        outer.update(block);
81
82        block.zeroize();
83        Ok(Self { inner, outer })
84    }
85
86    fn update(&mut self, data: &[u8]) {
87        self.inner.update(data);
88    }
89
90    fn finalize(mut self) -> Self::Tag {
91        let inner_digest = self.inner.finalize();
92        self.outer.update(inner_digest.as_ref());
93        self.outer.finalize()
94    }
95}
96
97macro_rules! hmac_alias {
98    (
99        $name:ident, $digest:ty, $id:literal, $disp:literal, $taglen:literal,
100        $kat_key:expr, $kat_msg:expr, $kat_tag:literal
101    ) => {
102        #[doc = concat!($disp, ".")]
103        pub type $name = Hmac<$digest>;
104
105        impl HmacDigest for $digest {
106            const HMAC_ID: &'static str = $id;
107            const HMAC_NAME: &'static str = $disp;
108        }
109
110        impl SelfTest for Hmac<$digest> {
111            fn self_test() -> Result<()> {
112                let mut key = [0u8; 20];
113                ic_core::codec::hex_decode($kat_key.as_bytes(), &mut key)?;
114                let tag = <Self as Mac>::mac(&key, $kat_msg)?;
115                let mut want = [0u8; $taglen];
116                ic_core::codec::hex_decode($kat_tag.as_bytes(), &mut want)?;
117                ensure!(
118                    ic_core::ct::verify(&want, tag.as_ref()),
119                    SelfTestFailed,
120                    $id
121                );
122                Ok(())
123            }
124        }
125    };
126}
127
128/// The RFC 4231 test-case-1 key: twenty `0x0b` bytes.
129const KAT_KEY: &str = "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b";
130
131// Known-answer vectors, all over the RFC 4231 test-case-1 input
132// (a 20-byte 0x0b key over "Hi There"):
133//
134// * SHA-256 / SHA-384 / SHA-512 tags are RFC 4231 test case 1.
135// * SHA-512/256 and the SHA-3 tags are the corresponding NIST HMAC sample
136//   values for the same input.
137hmac_alias!(
138    HmacSha256,
139    ic_hash::Sha256,
140    "hmac-sha2-256",
141    "HMAC-SHA-256",
142    32,
143    KAT_KEY,
144    b"Hi There",
145    "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
146);
147hmac_alias!(
148    HmacSha384,
149    ic_hash::Sha384,
150    "hmac-sha2-384",
151    "HMAC-SHA-384",
152    48,
153    KAT_KEY,
154    b"Hi There",
155    "afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6"
156);
157hmac_alias!(
158    HmacSha512,
159    ic_hash::Sha512,
160    "hmac-sha2-512",
161    "HMAC-SHA-512",
162    64,
163    KAT_KEY,
164    b"Hi There",
165    "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"
166);
167hmac_alias!(
168    HmacSha512_256,
169    ic_hash::Sha512_256,
170    "hmac-sha2-512-256",
171    "HMAC-SHA-512/256",
172    32,
173    KAT_KEY,
174    b"Hi There",
175    "9f9126c3d9c3c330d760425ca8a217e31feae31bfe70196ff81642b868402eab"
176);
177hmac_alias!(
178    HmacSha3_256,
179    ic_hash::Sha3_256,
180    "hmac-sha3-256",
181    "HMAC-SHA3-256",
182    32,
183    KAT_KEY,
184    b"Hi There",
185    "ba85192310dffa96e2a3a40e69774351140bb7185e1202cdcc917589f95e16bb"
186);
187hmac_alias!(
188    HmacSha3_512,
189    ic_hash::Sha3_512,
190    "hmac-sha3-512",
191    "HMAC-SHA3-512",
192    64,
193    KAT_KEY,
194    b"Hi There",
195    "eb3fbd4b2eaab8f5c504bd3a41465aacec15770a7cabac531e482f860b5ec7ba47ccb2c6f2afce8f88d22b6dc61380f23a668fd3888bb80537c0a0b86407689e"
196);
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201    use ic_core::codec::hex;
202
203    #[test]
204    fn rfc4231_case_1() {
205        let key = [0x0bu8; 20];
206        assert_eq!(
207            hex(HmacSha256::mac(&key, b"Hi There").unwrap().as_ref()),
208            "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
209        );
210        assert_eq!(
211            hex(HmacSha512::mac(&key, b"Hi There").unwrap().as_ref()),
212            "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"
213        );
214    }
215
216    #[test]
217    fn rfc4231_case_2_short_key() {
218        assert_eq!(
219            hex(HmacSha256::mac(b"Jefe", b"what do ya want for nothing?")
220                .unwrap()
221                .as_ref()),
222            "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
223        );
224    }
225
226    /// Case 3 uses a key and message that both exceed one block.
227    #[test]
228    fn rfc4231_case_3_long_data() {
229        let key = [0xaau8; 20];
230        let data = [0xddu8; 50];
231        assert_eq!(
232            hex(HmacSha256::mac(&key, &data).unwrap().as_ref()),
233            "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"
234        );
235    }
236
237    /// Case 6: a 131-byte key, longer than the SHA-256 block, so it is hashed
238    /// down first.
239    #[test]
240    fn rfc4231_case_6_oversized_key() {
241        let key = [0xaau8; 131];
242        assert_eq!(
243            hex(HmacSha256::mac(
244                &key,
245                b"Test Using Larger Than Block-Size Key - Hash Key First"
246            )
247            .unwrap()
248            .as_ref()),
249            "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"
250        );
251    }
252
253    #[test]
254    fn empty_key_and_message() {
255        assert_eq!(
256            hex(HmacSha256::mac(b"", b"").unwrap().as_ref()),
257            "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"
258        );
259    }
260
261    #[test]
262    fn streaming_matches_one_shot() {
263        let data: Vec<u8> = (0..200u8).collect();
264        for split in [0usize, 1, 63, 64, 128, 200] {
265            let mut m = HmacSha256::new(b"k").unwrap();
266            m.update(&data[..split]);
267            m.update(&data[split..]);
268            assert_eq!(m.finalize(), HmacSha256::mac(b"k", &data).unwrap());
269        }
270    }
271
272    #[test]
273    fn verify_rejects_wrong_tag_and_length() {
274        let tag = HmacSha256::mac(b"k", b"m").unwrap();
275        HmacSha256::verify(b"k", b"m", tag.as_ref()).unwrap();
276        let mut bad = tag;
277        bad[0] ^= 1;
278        assert!(HmacSha256::verify(b"k", b"m", bad.as_ref()).is_err());
279        assert!(HmacSha256::verify(b"k", b"m", &tag.as_ref()[..31]).is_err());
280    }
281
282    #[test]
283    fn self_tests_pass() {
284        HmacSha256::self_test().unwrap();
285        HmacSha384::self_test().unwrap();
286        HmacSha512::self_test().unwrap();
287        HmacSha512_256::self_test().unwrap();
288        HmacSha3_256::self_test().unwrap();
289        HmacSha3_512::self_test().unwrap();
290    }
291}