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. Both padded keys are zeroized before the constructor
16/// 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        let mut padded = [0u8; MAX_BLOCK_LEN];
52        if key.len() > D::BLOCK_LEN {
53            let hashed = D::digest(key);
54            padded[..D::OUTPUT_LEN].copy_from_slice(hashed.as_ref());
55        } else {
56            padded[..key.len()].copy_from_slice(key);
57        }
58
59        let mut inner = D::new();
60        let mut outer = D::new();
61        let mut pad = [0u8; MAX_BLOCK_LEN];
62
63        for i in 0..D::BLOCK_LEN {
64            pad[i] = padded[i] ^ 0x36;
65        }
66        inner.update(&pad[..D::BLOCK_LEN]);
67
68        for i in 0..D::BLOCK_LEN {
69            pad[i] = padded[i] ^ 0x5c;
70        }
71        outer.update(&pad[..D::BLOCK_LEN]);
72
73        pad.zeroize();
74        padded.zeroize();
75        Ok(Self { inner, outer })
76    }
77
78    fn update(&mut self, data: &[u8]) {
79        self.inner.update(data);
80    }
81
82    fn finalize(mut self) -> Self::Tag {
83        let inner_digest = self.inner.finalize();
84        self.outer.update(inner_digest.as_ref());
85        self.outer.finalize()
86    }
87}
88
89macro_rules! hmac_alias {
90    (
91        $name:ident, $digest:ty, $id:literal, $disp:literal, $taglen:literal,
92        $kat_key:expr, $kat_msg:expr, $kat_tag:literal
93    ) => {
94        #[doc = concat!($disp, ".")]
95        pub type $name = Hmac<$digest>;
96
97        impl HmacDigest for $digest {
98            const HMAC_ID: &'static str = $id;
99            const HMAC_NAME: &'static str = $disp;
100        }
101
102        impl SelfTest for Hmac<$digest> {
103            fn self_test() -> Result<()> {
104                let mut key = [0u8; 20];
105                ic_core::codec::hex_decode($kat_key.as_bytes(), &mut key)?;
106                let tag = <Self as Mac>::mac(&key, $kat_msg)?;
107                let mut want = [0u8; $taglen];
108                ic_core::codec::hex_decode($kat_tag.as_bytes(), &mut want)?;
109                ensure!(
110                    ic_core::ct::verify(&want, tag.as_ref()),
111                    SelfTestFailed,
112                    $id
113                );
114                Ok(())
115            }
116        }
117    };
118}
119
120/// The RFC 4231 test-case-1 key: twenty `0x0b` bytes.
121const KAT_KEY: &str = "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b";
122
123// Known-answer vectors, all over the RFC 4231 test-case-1 input
124// (a 20-byte 0x0b key over "Hi There"):
125//
126// * SHA-256 / SHA-384 / SHA-512 tags are RFC 4231 test case 1.
127// * SHA-512/256 and the SHA-3 tags are the corresponding NIST HMAC sample
128//   values for the same input.
129hmac_alias!(
130    HmacSha256,
131    ic_hash::Sha256,
132    "hmac-sha2-256",
133    "HMAC-SHA-256",
134    32,
135    KAT_KEY,
136    b"Hi There",
137    "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
138);
139hmac_alias!(
140    HmacSha384,
141    ic_hash::Sha384,
142    "hmac-sha2-384",
143    "HMAC-SHA-384",
144    48,
145    KAT_KEY,
146    b"Hi There",
147    "afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6"
148);
149hmac_alias!(
150    HmacSha512,
151    ic_hash::Sha512,
152    "hmac-sha2-512",
153    "HMAC-SHA-512",
154    64,
155    KAT_KEY,
156    b"Hi There",
157    "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"
158);
159hmac_alias!(
160    HmacSha512_256,
161    ic_hash::Sha512_256,
162    "hmac-sha2-512-256",
163    "HMAC-SHA-512/256",
164    32,
165    KAT_KEY,
166    b"Hi There",
167    "9f9126c3d9c3c330d760425ca8a217e31feae31bfe70196ff81642b868402eab"
168);
169hmac_alias!(
170    HmacSha3_256,
171    ic_hash::Sha3_256,
172    "hmac-sha3-256",
173    "HMAC-SHA3-256",
174    32,
175    KAT_KEY,
176    b"Hi There",
177    "ba85192310dffa96e2a3a40e69774351140bb7185e1202cdcc917589f95e16bb"
178);
179hmac_alias!(
180    HmacSha3_512,
181    ic_hash::Sha3_512,
182    "hmac-sha3-512",
183    "HMAC-SHA3-512",
184    64,
185    KAT_KEY,
186    b"Hi There",
187    "eb3fbd4b2eaab8f5c504bd3a41465aacec15770a7cabac531e482f860b5ec7ba47ccb2c6f2afce8f88d22b6dc61380f23a668fd3888bb80537c0a0b86407689e"
188);
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193    use ic_core::codec::hex;
194
195    #[test]
196    fn rfc4231_case_1() {
197        let key = [0x0bu8; 20];
198        assert_eq!(
199            hex(HmacSha256::mac(&key, b"Hi There").unwrap().as_ref()),
200            "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
201        );
202        assert_eq!(
203            hex(HmacSha512::mac(&key, b"Hi There").unwrap().as_ref()),
204            "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"
205        );
206    }
207
208    #[test]
209    fn rfc4231_case_2_short_key() {
210        assert_eq!(
211            hex(HmacSha256::mac(b"Jefe", b"what do ya want for nothing?")
212                .unwrap()
213                .as_ref()),
214            "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
215        );
216    }
217
218    /// Case 3 uses a key and message that both exceed one block.
219    #[test]
220    fn rfc4231_case_3_long_data() {
221        let key = [0xaau8; 20];
222        let data = [0xddu8; 50];
223        assert_eq!(
224            hex(HmacSha256::mac(&key, &data).unwrap().as_ref()),
225            "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"
226        );
227    }
228
229    /// Case 6: a 131-byte key, longer than the SHA-256 block, so it is hashed
230    /// down first.
231    #[test]
232    fn rfc4231_case_6_oversized_key() {
233        let key = [0xaau8; 131];
234        assert_eq!(
235            hex(HmacSha256::mac(
236                &key,
237                b"Test Using Larger Than Block-Size Key - Hash Key First"
238            )
239            .unwrap()
240            .as_ref()),
241            "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"
242        );
243    }
244
245    #[test]
246    fn empty_key_and_message() {
247        assert_eq!(
248            hex(HmacSha256::mac(b"", b"").unwrap().as_ref()),
249            "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"
250        );
251    }
252
253    #[test]
254    fn streaming_matches_one_shot() {
255        let data: Vec<u8> = (0..200u8).collect();
256        for split in [0usize, 1, 63, 64, 128, 200] {
257            let mut m = HmacSha256::new(b"k").unwrap();
258            m.update(&data[..split]);
259            m.update(&data[split..]);
260            assert_eq!(m.finalize(), HmacSha256::mac(b"k", &data).unwrap());
261        }
262    }
263
264    #[test]
265    fn verify_rejects_wrong_tag_and_length() {
266        let tag = HmacSha256::mac(b"k", b"m").unwrap();
267        HmacSha256::verify(b"k", b"m", tag.as_ref()).unwrap();
268        let mut bad = tag;
269        bad[0] ^= 1;
270        assert!(HmacSha256::verify(b"k", b"m", bad.as_ref()).is_err());
271        assert!(HmacSha256::verify(b"k", b"m", &tag.as_ref()[..31]).is_err());
272    }
273
274    #[test]
275    fn self_tests_pass() {
276        HmacSha256::self_test().unwrap();
277        HmacSha384::self_test().unwrap();
278        HmacSha512::self_test().unwrap();
279        HmacSha512_256::self_test().unwrap();
280        HmacSha3_256::self_test().unwrap();
281        HmacSha3_512::self_test().unwrap();
282    }
283}