Skip to main content

ic_mac/
kmac.rs

1//! SP 800-185 KMAC: a MAC built on cSHAKE.
2//!
3//! KMAC is the SHA-3 family's answer to HMAC, and a simpler one. HMAC exists
4//! because the Merkle-Damgard hashes it wraps are vulnerable to length
5//! extension, so it needs two passes with an inner and an outer key. A sponge
6//! has no such weakness, so KMAC just absorbs the key first:
7//!
8//! ```text
9//! KMAC128(K, X, L, S) = cSHAKE128(bytepad(encode_string(K), 168)
10//!                                 || X || right_encode(L),
11//!                                 L, "KMAC", S)
12//! ```
13//!
14//! # The output length is authenticated
15//!
16//! `right_encode(L)` at the end is the part worth understanding. It binds the
17//! requested output length into the input, so a 32-byte tag is not a prefix of
18//! a 64-byte tag over the same key and message. Without it, anyone who saw a
19//! long tag could truncate it into a valid short one.
20//!
21//! That is also why [`Kmac128::finalize`] and [`Kmac128::finalize_xof`] differ
22//! for the same length: the XOF variant encodes zero, which is precisely what
23//! makes its output a stream that can be extended without changing its prefix.
24//!
25//! # Customization separates domains
26//!
27//! Two subsystems sharing a key should pass different `custom` strings. Then a
28//! tag from one cannot be replayed into the other, and neither has to trust the
29//! other's message framing.
30
31use ic_core::traits::{Algorithm, SelfTest};
32use ic_core::{ensure, Result};
33use ic_hash::sp800_185::{right_encode, MAX_ENCODE};
34
35/// The widest tag the fixed-length helpers handle.
36const MAX_TAG: usize = 64;
37
38/// Declare a KMAC over one cSHAKE parameter set.
39macro_rules! kmac {
40    ($name:ident, $cshake:ty, $id:literal, $disp:literal, $bits:literal) => {
41        #[doc = concat!("SP 800-185 ", $disp, ", offering ", $bits, "-bit security.")]
42        #[derive(Clone)]
43        pub struct $name {
44            inner: $cshake,
45        }
46
47        impl Algorithm for $name {
48            const ID: &'static str = $id;
49            const NAME: &'static str = $disp;
50        }
51
52        impl $name {
53            /// Start a KMAC with `key` and a customization string.
54            ///
55            /// Pass an empty `custom` when the key has only one use.
56            pub fn new(key: &[u8], custom: &[u8]) -> Self {
57                let mut inner = <$cshake>::new(b"KMAC", custom);
58                inner.absorb_bytepadded_string(key);
59                Self { inner }
60            }
61
62            /// Absorb more of the message.
63            pub fn update(&mut self, data: &[u8]) {
64                self.inner.update(data);
65            }
66
67            /// Produce a tag of exactly `out.len()` bytes.
68            ///
69            /// The length is bound into the computation, so tags of different
70            /// lengths over the same input are unrelated.
71            pub fn finalize(mut self, out: &mut [u8]) {
72                let mut buf = [0u8; MAX_ENCODE];
73                let used = right_encode((out.len() as u64) * 8, &mut buf);
74                self.inner.update(&buf[..used]);
75                self.inner.finalize_xof(out);
76            }
77
78            /// Produce an arbitrary-length stream instead of a fixed tag.
79            ///
80            /// Encodes a length of zero, per SP 800-185 section 4.3.1.
81            pub fn finalize_xof(mut self, out: &mut [u8]) {
82                let mut buf = [0u8; MAX_ENCODE];
83                let used = right_encode(0, &mut buf);
84                self.inner.update(&buf[..used]);
85                self.inner.finalize_xof(out);
86            }
87
88            /// One-shot fixed-length tag.
89            pub fn mac(key: &[u8], custom: &[u8], data: &[u8], out: &mut [u8]) {
90                let mut k = Self::new(key, custom);
91                k.update(data);
92                k.finalize(out);
93            }
94
95            /// One-shot XOF mode.
96            pub fn mac_xof(key: &[u8], custom: &[u8], data: &[u8], out: &mut [u8]) {
97                let mut k = Self::new(key, custom);
98                k.update(data);
99                k.finalize_xof(out);
100            }
101
102            /// Verify a tag in constant time.
103            ///
104            /// Compare with this rather than `==`. An early-exit comparison
105            /// leaks how many leading bytes matched, which is enough to recover
106            /// a valid tag one byte at a time.
107            pub fn verify(key: &[u8], custom: &[u8], data: &[u8], tag: &[u8]) -> Result<()> {
108                ensure!(
109                    !tag.is_empty() && tag.len() <= MAX_TAG,
110                    InvalidLength,
111                    "kmac tag length"
112                );
113                let mut expected = [0u8; MAX_TAG];
114                Self::mac(key, custom, data, &mut expected[..tag.len()]);
115                ensure!(
116                    ic_core::ct::verify(&expected[..tag.len()], tag),
117                    AuthenticationFailed,
118                    $id
119                );
120                Ok(())
121            }
122        }
123
124        impl SelfTest for $name {
125            /// # Provenance
126            ///
127            /// No pinned vector. The unit tests check KMAC against an
128            /// independent construction from SP 800-185's own definition —
129            /// stronger evidence than a value this code produced — and cSHAKE
130            /// beneath it is checked against a Keccak written from FIPS 202.
131            /// docs/FIPS.md records that. What this adds at startup is that the
132            /// code still computes what it computed when those tests last ran,
133            /// and that its structural properties hold.
134            fn self_test() -> Result<()> {
135                let key = [0x40u8; 32];
136                let mut short = [0u8; 32];
137                let mut long = [0u8; 64];
138                Self::mac(&key, b"self-test", b"message", &mut short);
139                Self::mac(&key, b"self-test", b"message", &mut long);
140
141                // The output length is bound in, so the short tag is not a
142                // prefix of the long one. Drop right_encode(L) and it would be.
143                ensure!(short[..] != long[..32], SelfTestFailed, $id);
144
145                let mut again = [0u8; 32];
146                Self::mac(&key, b"self-test", b"message", &mut again);
147                ensure!(ic_core::ct::verify(&short, &again), SelfTestFailed, $id);
148                Self::verify(&key, b"self-test", b"message", &short)?;
149
150                let mut tampered = short;
151                tampered[0] ^= 1;
152                ensure!(
153                    Self::verify(&key, b"self-test", b"message", &tampered).is_err(),
154                    SelfTestFailed,
155                    $id
156                );
157                // Customization must separate domains.
158                ensure!(
159                    Self::verify(&key, b"other", b"message", &short).is_err(),
160                    SelfTestFailed,
161                    $id
162                );
163                Ok(())
164            }
165        }
166    };
167}
168
169kmac!(Kmac128, ic_hash::CShake128, "kmac128", "KMAC128", "128");
170kmac!(Kmac256, ic_hash::CShake256, "kmac256", "KMAC256", "256");
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175    use ic_hash::sp800_185::{left_encode, CShake128, CShake256};
176
177    /// SP 800-185's definition, assembled literally, with no shared code.
178    ///
179    /// `KMAC(K, X, L, S) = cSHAKE(bytepad(encode_string(K), rate)
180    ///                            || X || right_encode(L), L, "KMAC", S)`
181    ///
182    /// cSHAKE is trusted here because its own tests check it against a Keccak
183    /// written from FIPS 202 and anchored to a published SHA-3 vector. So this
184    /// checks the layer KMAC actually adds: the key encoding, the padding
185    /// width, and the trailing length.
186    fn reference_kmac(
187        rate: usize,
188        key: &[u8],
189        custom: &[u8],
190        data: &[u8],
191        out: &mut [u8],
192        xof: bool,
193    ) {
194        fn enc(x: u64) -> Vec<u8> {
195            let mut bytes = x.to_be_bytes().to_vec();
196            while bytes.len() > 1 && bytes[0] == 0 {
197                bytes.remove(0);
198            }
199            let mut v = vec![bytes.len() as u8];
200            v.extend_from_slice(&bytes);
201            v
202        }
203        fn renc(x: u64) -> Vec<u8> {
204            let mut bytes = x.to_be_bytes().to_vec();
205            while bytes.len() > 1 && bytes[0] == 0 {
206                bytes.remove(0);
207            }
208            let n = bytes.len() as u8;
209            bytes.push(n);
210            bytes
211        }
212
213        // bytepad(encode_string(K), rate)
214        let mut message = enc(rate as u64);
215        message.extend_from_slice(&enc((key.len() as u64) * 8));
216        message.extend_from_slice(key);
217        while message.len() % rate != 0 {
218            message.push(0);
219        }
220        message.extend_from_slice(data);
221        message.extend_from_slice(&renc(if xof { 0 } else { (out.len() as u64) * 8 }));
222
223        if rate == 168 {
224            CShake128::xof(b"KMAC", custom, &message, out);
225        } else {
226            CShake256::xof(b"KMAC", custom, &message, out);
227        }
228    }
229
230    #[test]
231    fn kmac_matches_an_independent_construction() {
232        let cases: &[(&[u8], &[u8], &[u8])] = &[
233            (&[0x40u8; 32], b"", b""),
234            (&[0x40u8; 32], b"My Tagged Application", b"\x00\x01\x02\x03"),
235            (b"short key", b"S", &[0xa5u8; 500]),
236            (&[0x11u8; 200], b"", b"key longer than the rate"),
237        ];
238
239        for (key, custom, data) in cases {
240            for len in [16usize, 32, 64] {
241                let mut want = vec![0u8; len];
242                let mut got = vec![0u8; len];
243
244                reference_kmac(168, key, custom, data, &mut want, false);
245                Kmac128::mac(key, custom, data, &mut got);
246                assert_eq!(got, want, "KMAC128 fixed, {len} bytes");
247
248                reference_kmac(136, key, custom, data, &mut want, false);
249                Kmac256::mac(key, custom, data, &mut got);
250                assert_eq!(got, want, "KMAC256 fixed, {len} bytes");
251
252                reference_kmac(168, key, custom, data, &mut want, true);
253                Kmac128::mac_xof(key, custom, data, &mut got);
254                assert_eq!(got, want, "KMAC128 xof, {len} bytes");
255
256                reference_kmac(136, key, custom, data, &mut want, true);
257                Kmac256::mac_xof(key, custom, data, &mut got);
258                assert_eq!(got, want, "KMAC256 xof, {len} bytes");
259            }
260        }
261    }
262
263    /// The property `right_encode(L)` exists to provide: a short tag is not a
264    /// truncation of a long one.
265    #[test]
266    fn tag_length_is_bound_into_the_tag() {
267        let key = [0x7fu8; 32];
268        let mut short = [0u8; 32];
269        let mut long = [0u8; 64];
270        Kmac128::mac(&key, b"", b"message", &mut short);
271        Kmac128::mac(&key, b"", b"message", &mut long);
272        assert_ne!(short[..], long[..32], "truncation must not forge");
273    }
274
275    /// The XOF variant, by contrast, *is* a stream: a longer output extends a
276    /// shorter one.
277    #[test]
278    fn the_xof_variant_extends_rather_than_changes() {
279        let key = [0x7fu8; 32];
280        let mut short = [0u8; 32];
281        let mut long = [0u8; 64];
282        Kmac128::mac_xof(&key, b"", b"message", &mut short);
283        Kmac128::mac_xof(&key, b"", b"message", &mut long);
284        assert_eq!(short[..], long[..32], "the xof output is a prefix");
285    }
286
287    #[test]
288    fn streaming_matches_the_one_shot() {
289        let key = [0x31u8; 32];
290        let data = [0x62u8; 777];
291        let mut one = [0u8; 32];
292        Kmac256::mac(&key, b"S", &data, &mut one);
293
294        let mut k = Kmac256::new(&key, b"S");
295        for chunk in data.chunks(13) {
296            k.update(chunk);
297        }
298        let mut streamed = [0u8; 32];
299        k.finalize(&mut streamed);
300        assert_eq!(one, streamed);
301    }
302
303    #[test]
304    fn keys_and_customization_both_change_the_tag() {
305        let mut a = [0u8; 32];
306        let mut b = [0u8; 32];
307        Kmac128::mac(&[1u8; 32], b"S", b"m", &mut a);
308        Kmac128::mac(&[2u8; 32], b"S", b"m", &mut b);
309        assert_ne!(a, b, "the key matters");
310        Kmac128::mac(&[1u8; 32], b"T", b"m", &mut b);
311        assert_ne!(a, b, "the customization matters");
312    }
313
314    #[test]
315    fn verification_rejects_tampering_and_bad_lengths() {
316        let key = [0x55u8; 32];
317        let mut tag = [0u8; 32];
318        Kmac128::mac(&key, b"S", b"message", &mut tag);
319        Kmac128::verify(&key, b"S", b"message", &tag).unwrap();
320
321        for bit in [0usize, 7, 128, 255] {
322            let mut bad = tag;
323            bad[bit / 8] ^= 1 << (bit % 8);
324            assert!(Kmac128::verify(&key, b"S", b"message", &bad).is_err());
325        }
326        assert!(Kmac128::verify(&key, b"S", b"messagf", &tag).is_err());
327        assert!(Kmac128::verify(&[0u8; 32], b"S", b"message", &tag).is_err());
328        assert!(
329            Kmac128::verify(&key, b"S", b"message", &[]).is_err(),
330            "empty tag"
331        );
332        assert!(
333            Kmac128::verify(&key, b"S", b"message", &[0u8; 65]).is_err(),
334            "over-long tag"
335        );
336    }
337
338    #[test]
339    fn both_self_tests_pass() {
340        Kmac128::self_test().unwrap();
341        Kmac256::self_test().unwrap();
342    }
343
344    /// `left_encode` is re-exported through `ic_hash`; make sure the path the
345    /// docs point at actually resolves.
346    #[test]
347    fn the_encoding_helpers_are_reachable() {
348        let mut buf = [0u8; MAX_ENCODE];
349        assert_eq!(left_encode(168, &mut buf), 2);
350        assert_eq!(&buf[..2], &[0x01, 0xa8]);
351    }
352}