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                ic_core::module::operational()?;
109                ensure!(
110                    !tag.is_empty() && tag.len() <= MAX_TAG,
111                    InvalidLength,
112                    "kmac tag length"
113                );
114                let mut expected = [0u8; MAX_TAG];
115                Self::mac(key, custom, data, &mut expected[..tag.len()]);
116                ensure!(
117                    ic_core::ct::verify(&expected[..tag.len()], tag),
118                    AuthenticationFailed,
119                    $id
120                );
121                Ok(())
122            }
123        }
124
125        impl SelfTest for $name {
126            /// # Provenance
127            ///
128            /// No pinned vector. The unit tests check KMAC against an
129            /// independent construction from SP 800-185's own definition —
130            /// stronger evidence than a value this code produced — and cSHAKE
131            /// beneath it is checked against a Keccak written from FIPS 202.
132            /// docs/FIPS.md records that. What this adds at startup is that the
133            /// code still computes what it computed when those tests last ran,
134            /// and that its structural properties hold.
135            fn self_test() -> Result<()> {
136                let key = [0x40u8; 32];
137                let mut short = [0u8; 32];
138                let mut long = [0u8; 64];
139                Self::mac(&key, b"self-test", b"message", &mut short);
140                Self::mac(&key, b"self-test", b"message", &mut long);
141
142                // The output length is bound in, so the short tag is not a
143                // prefix of the long one. Drop right_encode(L) and it would be.
144                ensure!(short[..] != long[..32], SelfTestFailed, $id);
145
146                let mut again = [0u8; 32];
147                Self::mac(&key, b"self-test", b"message", &mut again);
148                ensure!(ic_core::ct::verify(&short, &again), SelfTestFailed, $id);
149                Self::verify(&key, b"self-test", b"message", &short)?;
150
151                let mut tampered = short;
152                tampered[0] ^= 1;
153                ensure!(
154                    Self::verify(&key, b"self-test", b"message", &tampered).is_err(),
155                    SelfTestFailed,
156                    $id
157                );
158                // Customization must separate domains.
159                ensure!(
160                    Self::verify(&key, b"other", b"message", &short).is_err(),
161                    SelfTestFailed,
162                    $id
163                );
164                Ok(())
165            }
166        }
167    };
168}
169
170kmac!(Kmac128, ic_hash::CShake128, "kmac128", "KMAC128", "128");
171kmac!(Kmac256, ic_hash::CShake256, "kmac256", "KMAC256", "256");
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176    use ic_hash::sp800_185::{left_encode, CShake128, CShake256};
177
178    /// SP 800-185's definition, assembled literally, with no shared code.
179    ///
180    /// `KMAC(K, X, L, S) = cSHAKE(bytepad(encode_string(K), rate)
181    ///                            || X || right_encode(L), L, "KMAC", S)`
182    ///
183    /// cSHAKE is trusted here because its own tests check it against a Keccak
184    /// written from FIPS 202 and anchored to a published SHA-3 vector. So this
185    /// checks the layer KMAC actually adds: the key encoding, the padding
186    /// width, and the trailing length.
187    fn reference_kmac(
188        rate: usize,
189        key: &[u8],
190        custom: &[u8],
191        data: &[u8],
192        out: &mut [u8],
193        xof: bool,
194    ) {
195        fn enc(x: u64) -> Vec<u8> {
196            let mut bytes = x.to_be_bytes().to_vec();
197            while bytes.len() > 1 && bytes[0] == 0 {
198                bytes.remove(0);
199            }
200            let mut v = vec![bytes.len() as u8];
201            v.extend_from_slice(&bytes);
202            v
203        }
204        fn renc(x: u64) -> Vec<u8> {
205            let mut bytes = x.to_be_bytes().to_vec();
206            while bytes.len() > 1 && bytes[0] == 0 {
207                bytes.remove(0);
208            }
209            let n = bytes.len() as u8;
210            bytes.push(n);
211            bytes
212        }
213
214        // bytepad(encode_string(K), rate)
215        let mut message = enc(rate as u64);
216        message.extend_from_slice(&enc((key.len() as u64) * 8));
217        message.extend_from_slice(key);
218        while !message.len().is_multiple_of(rate) {
219            message.push(0);
220        }
221        message.extend_from_slice(data);
222        message.extend_from_slice(&renc(if xof { 0 } else { (out.len() as u64) * 8 }));
223
224        if rate == 168 {
225            CShake128::xof(b"KMAC", custom, &message, out);
226        } else {
227            CShake256::xof(b"KMAC", custom, &message, out);
228        }
229    }
230
231    #[test]
232    fn kmac_matches_an_independent_construction() {
233        let cases: &[(&[u8], &[u8], &[u8])] = &[
234            (&[0x40u8; 32], b"", b""),
235            (&[0x40u8; 32], b"My Tagged Application", b"\x00\x01\x02\x03"),
236            (b"short key", b"S", &[0xa5u8; 500]),
237            (&[0x11u8; 200], b"", b"key longer than the rate"),
238        ];
239
240        for (key, custom, data) in cases {
241            for len in [16usize, 32, 64] {
242                let mut want = vec![0u8; len];
243                let mut got = vec![0u8; len];
244
245                reference_kmac(168, key, custom, data, &mut want, false);
246                Kmac128::mac(key, custom, data, &mut got);
247                assert_eq!(got, want, "KMAC128 fixed, {len} bytes");
248
249                reference_kmac(136, key, custom, data, &mut want, false);
250                Kmac256::mac(key, custom, data, &mut got);
251                assert_eq!(got, want, "KMAC256 fixed, {len} bytes");
252
253                reference_kmac(168, key, custom, data, &mut want, true);
254                Kmac128::mac_xof(key, custom, data, &mut got);
255                assert_eq!(got, want, "KMAC128 xof, {len} bytes");
256
257                reference_kmac(136, key, custom, data, &mut want, true);
258                Kmac256::mac_xof(key, custom, data, &mut got);
259                assert_eq!(got, want, "KMAC256 xof, {len} bytes");
260            }
261        }
262    }
263
264    /// The property `right_encode(L)` exists to provide: a short tag is not a
265    /// truncation of a long one.
266    #[test]
267    fn tag_length_is_bound_into_the_tag() {
268        let key = [0x7fu8; 32];
269        let mut short = [0u8; 32];
270        let mut long = [0u8; 64];
271        Kmac128::mac(&key, b"", b"message", &mut short);
272        Kmac128::mac(&key, b"", b"message", &mut long);
273        assert_ne!(short[..], long[..32], "truncation must not forge");
274    }
275
276    /// The XOF variant, by contrast, *is* a stream: a longer output extends a
277    /// shorter one.
278    #[test]
279    fn the_xof_variant_extends_rather_than_changes() {
280        let key = [0x7fu8; 32];
281        let mut short = [0u8; 32];
282        let mut long = [0u8; 64];
283        Kmac128::mac_xof(&key, b"", b"message", &mut short);
284        Kmac128::mac_xof(&key, b"", b"message", &mut long);
285        assert_eq!(short[..], long[..32], "the xof output is a prefix");
286    }
287
288    #[test]
289    fn streaming_matches_the_one_shot() {
290        let key = [0x31u8; 32];
291        let data = [0x62u8; 777];
292        let mut one = [0u8; 32];
293        Kmac256::mac(&key, b"S", &data, &mut one);
294
295        let mut k = Kmac256::new(&key, b"S");
296        for chunk in data.chunks(13) {
297            k.update(chunk);
298        }
299        let mut streamed = [0u8; 32];
300        k.finalize(&mut streamed);
301        assert_eq!(one, streamed);
302    }
303
304    #[test]
305    fn keys_and_customization_both_change_the_tag() {
306        let mut a = [0u8; 32];
307        let mut b = [0u8; 32];
308        Kmac128::mac(&[1u8; 32], b"S", b"m", &mut a);
309        Kmac128::mac(&[2u8; 32], b"S", b"m", &mut b);
310        assert_ne!(a, b, "the key matters");
311        Kmac128::mac(&[1u8; 32], b"T", b"m", &mut b);
312        assert_ne!(a, b, "the customization matters");
313    }
314
315    #[test]
316    fn verification_rejects_tampering_and_bad_lengths() {
317        let key = [0x55u8; 32];
318        let mut tag = [0u8; 32];
319        Kmac128::mac(&key, b"S", b"message", &mut tag);
320        Kmac128::verify(&key, b"S", b"message", &tag).unwrap();
321
322        for bit in [0usize, 7, 128, 255] {
323            let mut bad = tag;
324            bad[bit / 8] ^= 1 << (bit % 8);
325            assert!(Kmac128::verify(&key, b"S", b"message", &bad).is_err());
326        }
327        assert!(Kmac128::verify(&key, b"S", b"messagf", &tag).is_err());
328        assert!(Kmac128::verify(&[0u8; 32], b"S", b"message", &tag).is_err());
329        assert!(
330            Kmac128::verify(&key, b"S", b"message", &[]).is_err(),
331            "empty tag"
332        );
333        assert!(
334            Kmac128::verify(&key, b"S", b"message", &[0u8; 65]).is_err(),
335            "over-long tag"
336        );
337    }
338
339    #[test]
340    fn both_self_tests_pass() {
341        Kmac128::self_test().unwrap();
342        Kmac256::self_test().unwrap();
343    }
344
345    /// `left_encode` is re-exported through `ic_hash`; make sure the path the
346    /// docs point at actually resolves.
347    #[test]
348    fn the_encoding_helpers_are_reachable() {
349        let mut buf = [0u8; MAX_ENCODE];
350        assert_eq!(left_encode(168, &mut buf), 2);
351        assert_eq!(&buf[..2], &[0x01, 0xa8]);
352    }
353}