From 25f33f2aa3d8aed5e85741d71f4a486986fcca2c Mon Sep 17 00:00:00 2001 From: Liam Gray Date: Tue, 3 Mar 2026 18:40:22 +0000 Subject: [PATCH 1/3] kmac: Initial crate submission --- .github/workflows/kmac.yml | 62 +++++++ Cargo.lock | 152 ++++++++++++----- Cargo.toml | 1 + kmac/Cargo.toml | 22 +++ kmac/README.md | 124 ++++++++++++++ kmac/benches/mod.rs | 61 +++++++ kmac/src/encoding.rs | 102 +++++++++++ kmac/src/kmac.rs | 146 ++++++++++++++++ kmac/src/lib.rs | 277 ++++++++++++++++++++++++++++++ kmac/src/traits.rs | 50 ++++++ kmac/tests/nist.rs | 339 +++++++++++++++++++++++++++++++++++++ 11 files changed, 1293 insertions(+), 43 deletions(-) create mode 100644 .github/workflows/kmac.yml create mode 100644 kmac/Cargo.toml create mode 100644 kmac/README.md create mode 100644 kmac/benches/mod.rs create mode 100644 kmac/src/encoding.rs create mode 100644 kmac/src/kmac.rs create mode 100644 kmac/src/lib.rs create mode 100644 kmac/src/traits.rs create mode 100644 kmac/tests/nist.rs diff --git a/.github/workflows/kmac.yml b/.github/workflows/kmac.yml new file mode 100644 index 0000000..08b4b4e --- /dev/null +++ b/.github/workflows/kmac.yml @@ -0,0 +1,62 @@ +name: kmac + +on: + pull_request: + paths: + - ".github/workflows/kmac.yml" + - "kmac/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: kmac + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.85.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v5 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --no-default-features --target ${{ matrix.target }} + + minimal-versions: + # disabled until belt-block gets published + if: false + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.85.0 # MSRV + - stable + steps: + - uses: actions/checkout@v5 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - uses: RustCrypto/actions/cargo-hack-install@master + - run: cargo hack test --feature-powerset + - run: cargo test --release --all-features diff --git a/Cargo.lock b/Cargo.lock index 4463bca..11bd8ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,20 +4,20 @@ version = 4 [[package]] name = "aes" -version = "0.9.0-rc.1" +version = "0.9.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e713c57c2a2b19159e7be83b9194600d7e8eb3b7c2cd67e671adf47ce189a05" +checksum = "04097e08a47d9ad181c2e1f4a5fabc9ae06ce8839a333ba9a949bcb0d31fd2a3" dependencies = [ - "cfg-if", "cipher", - "cpufeatures", + "cpubits", + "cpufeatures 0.2.17", ] [[package]] name = "belt-block" -version = "0.2.0-rc.1" +version = "0.2.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4bbddee901eea0f5448cfcc94ea6390d0ee0f3ca8d0308fe34e37553f856640" +checksum = "0e3b1e9d1ad19c345095575076767febd525013fc5782276a21069901815ea45" dependencies = [ "cipher", ] @@ -40,9 +40,9 @@ checksum = "89af0b093cc13baa4e51e64e65ec2422f7e73aea0e612e5ad3872986671622f1" [[package]] name = "block-buffer" -version = "0.11.0-rc.5" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9ef36a6fcdb072aa548f3da057640ec10859eb4e91ddf526ee648d50c76a949" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" dependencies = [ "hybrid-array", "zeroize", @@ -67,9 +67,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cipher" -version = "0.5.0-rc.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e12a13eb01ded5d32ee9658d94f553a19e804204f2dc811df69ab4d9e0cb8c7" +checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" dependencies = [ "block-buffer", "crypto-common", @@ -91,6 +91,24 @@ dependencies = [ "magma", ] +[[package]] +name = "cmov" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0758edba32d61d1fd9f4d69491b47604b91ee2f7e6b33de7e54ca4ebe55dc3" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "cpubits" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef0c543070d296ea414df2dd7625d1b24866ce206709d8a4a424f28377f5861" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -100,15 +118,33 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crypto-common" -version = "0.2.0-rc.4" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8235645834fbc6832939736ce2f2d08192652269e11010a6240f61b908a1c6" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ "hybrid-array", ] +[[package]] +name = "ctutils" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1005a6d4446f5120ef475ad3d2af2b30c49c2c9c6904258e3bb30219bebed5e4" +dependencies = [ + "cmov", +] + [[package]] name = "dbl" version = "0.5.0" @@ -120,31 +156,38 @@ dependencies = [ [[package]] name = "des" -version = "0.9.0-rc.1" +version = "0.9.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f51594a70805988feb1c85495ddec0c2052e4fbe59d9c0bb7f94bfc164f4f90" +checksum = "3214053e68a813b9c06ef61075c844f3a1cdeb307d8998ea8555c063caa52fa9" dependencies = [ "cipher", ] [[package]] name = "digest" -version = "0.11.0-rc.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac89f8a64533a9b0eaa73a68e424db0fb1fd6271c74cc0125336a05f090568d" +checksum = "285743a676ccb6b3e116bc14cc69319b957867930ae9c4822f8e0f54509d7243" dependencies = [ "blobby", "block-buffer", + "const-oid", "crypto-common", - "subtle", + "ctutils", "zeroize", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hex-literal" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" +checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" [[package]] name = "hmac" @@ -160,9 +203,9 @@ dependencies = [ [[package]] name = "hybrid-array" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0" +checksum = "e1b229d73f5803b562cc26e4da0396c8610a4ee209f4fac8fa4f8d709166dc45" dependencies = [ "typenum", "zeroize", @@ -170,18 +213,37 @@ dependencies = [ [[package]] name = "inout" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7357b6e7aa75618c7864ebd0634b115a7218b0615f4cb1df33ac3eca23943d4" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" dependencies = [ "hybrid-array", ] +[[package]] +name = "keccak" +version = "0.2.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882b69cb15b1f78b51342322a97ccd16f5123d1dc8a3da981a95244f488e8692" +dependencies = [ + "cpufeatures 0.3.0", +] + +[[package]] +name = "kmac" +version = "0.1.0" +dependencies = [ + "digest", + "hex", + "hex-literal", + "sha3", +] + [[package]] name = "kuznyechik" -version = "0.9.0-rc.1" +version = "0.9.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e39eb5f1f53d41c9341e559c72a732db17619643679116d56ca0af8b34cb8b26" +checksum = "1845d0271ee188d9eddbb2b027738da1e492fe5622d6f5353aea2e1bd40a62ff" dependencies = [ "cfg-if", "cipher", @@ -189,24 +251,24 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.177" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "magma" -version = "0.10.0-rc.1" +version = "0.10.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c60b0343c651857c53e0072315831622d550efeb663a589bbd3fdd83073da0" +checksum = "287314ef5d338202e7be522fbbae35cad5d1dff1b8cb079a395eec3a9e31104a" dependencies = [ "cipher", ] [[package]] name = "md-5" -version = "0.11.0-rc.2" +version = "0.11.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9ec86664728010f574d67ef01aec964e6f1299241a3402857c1a8a390a62478" +checksum = "59e715bb6f273068fc89403d6c4f5eeb83708c62b74c8d43e3e8772ca73a6288" dependencies = [ "cfg-if", "digest", @@ -235,40 +297,44 @@ dependencies = [ [[package]] name = "sha1" -version = "0.11.0-rc.2" +version = "0.11.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e046edf639aa2e7afb285589e5405de2ef7e61d4b0ac1e30256e3eab911af9" +checksum = "3b167252f3c126be0d8926639c4c4706950f01445900c4b3db0fd7e89fcb750a" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] [[package]] name = "sha2" -version = "0.11.0-rc.2" +version = "0.11.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1e3878ab0f98e35b2df35fe53201d088299b41a6bb63e3e34dada2ac4abd924" +checksum = "7c5f3b1e2dc8aad28310d8410bd4d7e180eca65fca176c52ab00d364475d0024" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] [[package]] -name = "streebog" -version = "0.11.0-rc.2" +name = "sha3" +version = "0.11.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7032930cdbb2ca75cafb6b91674728bd923d9a47941ac95183337c53445399ff" +checksum = "95f78cd62cc39ece5aefbeb6caaa2ea44f70b4815d4b85f7e150ac685ada2bb5" dependencies = [ "digest", + "keccak", ] [[package]] -name = "subtle" -version = "2.6.1" +name = "streebog" +version = "0.11.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +checksum = "8f02c29e9fbd46b3493c2d1b8038d738480a56d25edc0ec0acc27f796a125a89" +dependencies = [ + "digest", +] [[package]] name = "typenum" diff --git a/Cargo.toml b/Cargo.toml index b8c3abb..e1aefa3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "cbc-mac", "cmac", "hmac", + "kmac", "pmac", "retail-mac", ] diff --git a/kmac/Cargo.toml b/kmac/Cargo.toml new file mode 100644 index 0000000..2b79ffe --- /dev/null +++ b/kmac/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "kmac" +version = "0.1.0" +description = "Keccak Message Authentication Code (KMAC)" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +edition = "2024" +readme = "README.md" +documentation = "https://docs.rs/kmac" +repository = "https://github.com/RustCrypto/MACs" +keywords = ["crypto", "mac", "kmac", "digest"] +categories = ["cryptography", "no-std"] +rust-version = "1.85" + +[dependencies] +digest = { version = "0.11.0-rc.3", features = ["mac"] } +sha3 = "0.11.0-rc.3" + +[dev-dependencies] +digest = { version = "0.11.0-rc.3", features = ["dev"] } +hex-literal = "1.1.0" +hex = "0.4.3" diff --git a/kmac/README.md b/kmac/README.md new file mode 100644 index 0000000..0d86d95 --- /dev/null +++ b/kmac/README.md @@ -0,0 +1,124 @@ +# RustCrypto: KMAC + +A rust implementation of [KMAC](https://en.wikipedia.org/wiki/SHA-3#Additional_instances), following the [NIST SP 800-185] specification. + +This crate provides implementations for KMAC128, KMAC256, KMACXOF128, and KMACXOF256. KMAC is a PRF and keyed hash function based on the Keccak (SHA-3) sponge construction, designed for message authentication (MAC) and key derivation (KDF). + +## NIST security guidance + +Security guidance for KMAC is discussed in Section 8.4 of [NIST SP 800-185]. + +KMAC128 is built from cSHAKE128, giving it a security strength <= 128 bits. The `Kmac128` default MAC tag length is the NIST recommended 32 bytes (256 bits). The input key length must also be at least 16 bytes (128 bits) to achieve the full security strength. + +KMAC256 is built from cSHAKE256, giving it a security strength <= 256 bits. The `Kmac256` default MAC tag length is the NIST recommended 64 bytes (512 bits). The input key length must also be at least 32 bytes (256 bits) to achieve the full security strength. + +The below table summarises the equivalence with other MAC algorithms, where `K` is the input key, `text` is the input message, `L` is the output tag length, and `S` is an optional customization string. + +| Existing MAC Algorithm | KMAC Equivalent | +|------------------------|------------------------------| +| `AES-KMAC(K, text)` | `KMAC128(K, text, L=128, S)` | +| `HMAC-SHA256(K, text)` | `KMAC256(K, text, L=256, S)` | +| `HMAC-SHA512(K, text)` | `KMAC256(K, text, L=512, S)` | + +## Examples + +### Generating a MAC +```rust +use kmac::{Kmac128, Mac, KeyInit}; +use hex_literal::hex; + +// Use KMAC128 to produce a MAC +let mut kmac = Kmac128::new_from_slice(b"key material").unwrap(); +kmac.update(b"input message"); + +// `result` has type `CtOutput` which is a thin wrapper around array of +// bytes for providing constant time equality check +let result = kmac.finalize(); + +// To get underlying array use `into_bytes`, but be careful, since +// incorrect use of the code value may permit timing attacks which defeats +// the security provided by the `CtOutput` +let mac_bytes = result.into_bytes(); +let expected = hex!(" + c39a8f614f8821443599440df5402787 + 0f67e4c47919061584f14a616f3efcf5 +"); +assert_eq!(mac_bytes[..], expected[..]); +``` + +### Verifying a MAC +```rust +use kmac::{Kmac128, Mac, KeyInit}; +use hex_literal::hex; + +let mut kmac = Kmac128::new_from_slice(b"key material").unwrap(); +kmac.update(b"input message"); + +let mac_bytes = hex!(" + c39a8f614f8821443599440df5402787 + 0f67e4c47919061584f14a616f3efcf5 +"); + +// `verify_slice` will return `Ok(())` if code is correct, `Err(MacError)` otherwise +kmac.verify_slice(&mac_bytes).unwrap(); +``` + +### Producing a fixed-length output + +KMAC can also be used to produce an output of any length, which is particularly useful when KMAC is being used as a [key-derivation function (KDF)](https://en.wikipedia.org/wiki/Key_derivation_function). + +This method finalizes the KMAC and mixes the requested output length into the KMAC domain separation. The resulting bytes are dependent on the exact length of `out`. Use this when the output length is part of the MAC/derivation semantics (for example when the length itself must influence the MAC result). + +A customisation string can also be provided to further domain-separate different uses of KMAC with the same key when initialising the KMAC instance with `new_customization`. + +```rust +use kmac::{Kmac256, Mac}; +use hex_literal::hex; + +let mut mac = Kmac256::new_customization(b"key material", b"customization").unwrap(); +mac.update(b"input message"); +let mut output = [0u8; 32]; +mac.finalize_into(&mut output); + +let expected = hex!(" + 85fb77da3a35e4c4b0057c3151e6cc54 + ee401ffe65ec2f0239f439be8896f7b6 +"); +assert_eq!(output[..], expected[..]); +``` + +### Producing a variable-length output + +Variable length KMAC output uses the `ExtendableOutput` trait. This is useful when the desired output length is not immediately known, and will append data to a buffer until the desired length is reached. + +The XOF variant finalizes the sponge state without binding the requested output length into the KMAC domain separation. The returned reader yields an effectively infinite stream of bytes; reading the first `N` bytes from the reader (and truncating) produces the same `N`-byte prefix regardless of whether more bytes will be read later. + +```rust +use kmac::{Kmac256, Mac, ExtendableOutput, XofReader}; +use hex_literal::hex; + +let mut kmac = Kmac256::new_customization(b"key material", b"customization").unwrap(); +kmac.update(b"input message"); +let mut reader = kmac.finalize_xof(); + +let mut output = [0u8; 32]; +reader.read(&mut output); + +let expected = hex!(" + b675b75668eab0706ab05650f34fa1b6 + 24051a9a42b5e42cfe9970e8f903d45b +"); +assert_eq!(output[..], expected[..]); +``` + +## License + +Licensed under either of: +- [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) +- [MIT license](http://opensource.org/licenses/MIT) +at your option. + +### Contribution +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + +[NIST SP 800-185]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf diff --git a/kmac/benches/mod.rs b/kmac/benches/mod.rs new file mode 100644 index 0000000..9d37e1a --- /dev/null +++ b/kmac/benches/mod.rs @@ -0,0 +1,61 @@ +#![feature(test)] +extern crate test; + +use core::hint::black_box; +use kmac::{KeyInit, Kmac128, Kmac256, Mac}; +use test::Bencher; + +#[macro_export] +macro_rules! bench_full { + ( + $init:expr; + $($name:ident $bs:expr;)* + ) => { + $( + #[bench] + fn $name(b: &mut Bencher) { + let data = [0; $bs]; + + b.iter(|| { + let mut d = $init; + digest::Update::update(&mut d, black_box(&data[..])); + black_box(d.finalize()); + }); + + b.bytes = $bs; + } + )* + }; +} + +bench_full!( + Kmac128::new(black_box(&Default::default())); + kmac128_10 10; + kmac128_100 100; + kmac128_1000 1000; + kmac128_10000 10000; +); + +bench_full!( + Kmac256::new(black_box(&Default::default())); + kmac256_10 10; + kmac256_100 100; + kmac256_1000 1000; + kmac256_10000 10000; +); + +digest::bench_update!( + Kmac128::new(black_box(&Default::default())); + kmac128_update_10 10; + kmac128_update_100 100; + kmac128_update_1000 1000; + kmac128_update_10000 10000; +); + +digest::bench_update!( + Kmac256::new(black_box(&Default::default())); + kmac256_update_10 10; + kmac256_update_100 100; + kmac256_update_1000 1000; + kmac256_update_10000 10000; +); diff --git a/kmac/src/encoding.rs b/kmac/src/encoding.rs new file mode 100644 index 0000000..94abfdb --- /dev/null +++ b/kmac/src/encoding.rs @@ -0,0 +1,102 @@ +/// The number of bytes required to write a number in the KMAC encoded format, excluding the +/// leading byte that indicates the length of the encoding. +#[inline(always)] +pub(crate) fn num_encoding_size(num: u64) -> usize { + let bits = 64 - (num | 1).leading_zeros() as usize; + bits.div_ceil(8) +} + +#[inline(always)] +pub(crate) fn left_encode(num: u64, buffer: &mut [u8; 9]) -> &[u8] { + let encoding_size = num_encoding_size(num); + buffer[0] = encoding_size as u8; + buffer[1..=encoding_size].copy_from_slice(&num.to_be_bytes()[8 - encoding_size..]); + &buffer[..=encoding_size] +} + +#[inline(always)] +pub(crate) fn right_encode(num: u64, buffer: &mut [u8; 9]) -> &[u8] { + let encoding_size = num_encoding_size(num); + buffer[0..encoding_size].copy_from_slice(&num.to_be_bytes()[8 - encoding_size..]); + buffer[encoding_size] = encoding_size as u8; + &buffer[..=encoding_size] +} + +#[cfg(test)] +mod tests { + use super::*; + extern crate std; + + #[test] + fn test_num_encoding_size() { + // sha3::block_api::Sha3ReaderCore::::new(&[0; 200]); + let test_cases = [ + (0, 1), + (1, 1), + (2, 1), + (3, 1), + (4, 1), + (5, 1), + (6, 1), + (7, 1), + (8, 1), + (9, 1), + (10, 1), + (255, 1), + (256, 2), + (257, 2), + (65535, 2), + (65536, 3), + (65537, 3), + (16777215, 3), + (16777216, 4), + (16777217, 4), + ]; + + for &(num, expected_size) in &test_cases { + assert_eq!( + num_encoding_size(num), + expected_size, + "num_encoding_size({}) should return {}", + num, + expected_size + ); + } + } + + #[test] + fn test_left_encoding() { + let mut buf = [0u8; 9]; + + assert_eq!(left_encode(0, &mut buf), &[1, 0]); + assert_eq!(left_encode(1, &mut buf), &[1, 1]); + assert_eq!(left_encode(8, &mut buf), &[1, 8]); + assert_eq!(left_encode(256, &mut buf), &[2, 1, 0]); + + for i in 0..usize::BITS { + let x: usize = 1 << i; + let mut want = std::vec![0; 1]; + want.extend(x.to_be_bytes().iter().skip_while(|&&v| v == 0)); + want[0] = (want.len() - 1) as u8; + assert_eq!(left_encode(x as u64, &mut buf), want, "#{x}"); + } + } + + #[test] + fn test_right_encoding() { + let mut buf = [0u8; 9]; + + assert_eq!(right_encode(0, &mut buf), &[0, 1]); + assert_eq!(right_encode(1, &mut buf), &[1, 1]); + assert_eq!(right_encode(8, &mut buf), &[8, 1]); + assert_eq!(right_encode(256, &mut buf), &[1, 0, 2]); + + for i in 0..usize::BITS { + let x: usize = 1 << i; + let mut want = + std::vec::Vec::from_iter(x.to_be_bytes().iter().copied().skip_while(|&v| v == 0)); + want.push(want.len() as u8); + assert_eq!(right_encode(x as u64, &mut buf), want, "#{x}"); + } + } +} diff --git a/kmac/src/kmac.rs b/kmac/src/kmac.rs new file mode 100644 index 0000000..b24f8bb --- /dev/null +++ b/kmac/src/kmac.rs @@ -0,0 +1,146 @@ +use crate::encoding::{left_encode, right_encode}; +use crate::traits::{CShake, EagerHash}; +use core::fmt; +use digest::block_api::{ + AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, Eager, ExtendableOutputCore, + FixedOutputCore, UpdateCore, XofReaderCore, +}; +use digest::common::KeySizeUser; +use digest::{InvalidLength, Key, KeyInit, MacMarker, Output, OutputSizeUser}; + +pub struct KmacCore { + digest: D::Core, +} + +impl Clone for KmacCore { + #[inline(always)] + fn clone(&self) -> Self { + Self { + digest: self.digest.clone(), + } + } +} + +impl MacMarker for KmacCore {} + +impl BufferKindUser for KmacCore { + type BufferKind = Eager; +} + +impl KeySizeUser for KmacCore { + type KeySize = ::BlockSize; +} + +impl BlockSizeUser for KmacCore { + type BlockSize = ::BlockSize; +} + +impl KmacCore { + #[inline(always)] + pub fn new_customization(key: &[u8], customisation: &[u8]) -> Self { + // digest: bufpad(encode_string(K), bufsize) || X || right_encode(L) + // where bufpad(X, w) = left_encode(len(w)) || X || zeros + // where encode_string(K) = left_encode(len(K)) || K + let mut digest = D::Core::new_cshake(customisation); + let mut buffer = Buffer::::default(); + let mut encode_buffer = [0u8; 9]; + + // bytepad, left_encode(w) + buffer.digest_blocks( + left_encode(D::block_size() as u64, &mut encode_buffer), + |blocks| digest.update_blocks(blocks), + ); + + // encode_string(K), left_encode(len(K)) -- length is in bits + buffer.digest_blocks( + left_encode(8 * key.len() as u64, &mut encode_buffer), + |blocks| digest.update_blocks(blocks), + ); + + // encode_string(K) copy K into blocks + buffer.digest_blocks(key, |blocks| digest.update_blocks(blocks)); + + // bytepad, pad the key to the block size + digest.update_blocks(&[buffer.pad_with_zeros()]); + + Self { digest } + } +} + +impl KeyInit for KmacCore { + #[inline] + fn new(key: &Key) -> Self { + Self::new_customization(key.as_slice(), &[]) + } + + #[inline(always)] + fn new_from_slice(key: &[u8]) -> Result { + Ok(Self::new_customization(key, &[])) + } +} + +impl UpdateCore for KmacCore { + #[inline(always)] + fn update_blocks(&mut self, blocks: &[Block]) { + self.digest.update_blocks(blocks); + } +} + +impl KmacCore { + /// Finalizes the KMAC for any output array size. + #[inline(always)] + pub fn finalize_core(&mut self, buffer: &mut Buffer, out: &mut [u8]) { + // right_encode(L), where L = output length in bits + buffer.digest_blocks( + right_encode(8 * out.len() as u64, &mut [0u8; 9]), + |blocks| self.update_blocks(blocks), + ); + + let mut reader = self.digest.finalize_xof_core(buffer); + + let mut pos = 0; + while pos < out.len() { + let block = reader.read_block(); + let to_copy = core::cmp::min(out.len() - pos, block.len()); + out[pos..pos + to_copy].copy_from_slice(&block[..to_copy]); + pos += to_copy; + } + } +} + +impl FixedOutputCore for KmacCore +where + KmacCore: OutputSizeUser, +{ + #[inline(always)] + fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { + self.finalize_core(buffer, out.as_mut_slice()); + } +} + +impl ExtendableOutputCore for KmacCore { + type ReaderCore = ::ReaderCore; + + #[inline(always)] + fn finalize_xof_core(&mut self, buffer: &mut Buffer) -> Self::ReaderCore { + // right_encode(0), as L = 0 for extendable output + buffer.digest_blocks(right_encode(0, &mut [0u8; 9]), |blocks| { + self.update_blocks(blocks) + }); + self.digest.finalize_xof_core(buffer) + } +} + +impl AlgorithmName for KmacCore { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Kmac<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + +impl fmt::Debug for KmacCore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("KmacCore { ... }") + } +} diff --git a/kmac/src/lib.rs b/kmac/src/lib.rs new file mode 100644 index 0000000..0181ea0 --- /dev/null +++ b/kmac/src/lib.rs @@ -0,0 +1,277 @@ +//! [NIST SP 800-185]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf + +#![no_std] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg" +)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![forbid(unsafe_code)] +#![warn(missing_docs)] + +mod encoding; +mod kmac; +mod traits; + +use crate::kmac::KmacCore; +use digest::block_api::{Block, BlockSizeUser, Buffer, ExtendableOutputCore, XofReaderCore}; +use digest::block_buffer::ReadBuffer; +use digest::consts::{U32, U64, U136, U168}; +pub use digest::{self, ExtendableOutput, KeyInit, Mac, XofReader}; +use digest::{InvalidLength, OutputSizeUser}; +use sha3::block_api::Sha3ReaderCore; +use sha3::{CShake128, CShake256}; + +/// Manually implement the extra KMAC methods and XOF traits. +macro_rules! impl_kmac { + ($kmac:ident, $cshake:ident, $reader:ident, $block_size:ident, $output_size:ident) => { + digest::buffer_fixed!( + /// KMAC implementation as per Section 4 of [NIST SP 800-185]. + pub struct $kmac(KmacCore<$cshake>); + impl: MacTraits KeyInit; + ); + + impl OutputSizeUser for KmacCore<$cshake> { + type OutputSize = $output_size; + } + + impl $kmac { + /// Create a new KMAC with the given key and customisation. + /// + /// Section 4.2 of [NIST SP 800-185] specifies that KMAC takes both a key (K) and an + /// optional customisation string (S). + #[inline] + pub fn new_customization(key: &[u8], customisation: &[u8]) -> Result { + // TODO: KeyInitWithCustomization trait, following KeyInit as new_with_customization and new_from_slice_with_customization. + // TODO: review the Result, as this implementation is infallible. Currently matching KeyInit::new_from_slice. + // FUTURE: support key+customisation initialisation via traits. + let core = KmacCore::<$cshake>::new_customization(key, customisation); + let buffer = Buffer::>::default(); + Ok(Self { core, buffer }) + } + + /// Finalize this KMAC into a fixed-length output buffer, as defined in Section 4.3 + /// (Definition) of [NIST SP 800-185]. + /// + /// This method finalizes the KMAC and *mixes the requested output length into the + /// KMAC domain separation*. That means the resulting bytes are dependent on the + /// exact length of `out`. Use this when the output length is part of the MAC/derivation + /// semantics (for example when the length itself must influence the MAC result). + /// + /// This is *not* equivalent to calling `finalize_xof()` and then reading `out.len()` + /// bytes from the returned reader; the two approaches produce different outputs. + #[inline] + pub fn finalize_into(&mut self, out: &mut [u8]) { + // TODO: review method naming. + // FUTURE: support custom output sizes via traits. + let buffer = &mut self.buffer; + self.core.finalize_core(buffer, out); + } + } + + /// Reader for KMAC that implements the XOF interface. + pub struct $reader { + core: Sha3ReaderCore<$block_size>, + buffer: ReadBuffer< as BlockSizeUser>::BlockSize>, + } + + impl BlockSizeUser for $reader { + type BlockSize = as BlockSizeUser>::BlockSize; + } + + impl XofReaderCore for $reader { + #[inline(always)] + fn read_block(&mut self) -> Block { + self.core.read_block() + } + } + + impl XofReader for $reader { + #[inline(always)] + fn read(&mut self, buf: &mut [u8]) -> () { + let Self { core, buffer } = self; + buffer.read(buf, |block| { + *block = XofReaderCore::read_block(core); + }); + } + } + + impl ExtendableOutput for $kmac { + type Reader = $reader; + + /// Finalize this KMAC to a variable-length (extendable) output stream, as defined in + /// Section 4.3.1 (KMAC with Arbitrary-Length Output) of [NIST SP 800-185]. + /// + /// The XOF variant finalizes the sponge state without binding the requested + /// output length into the KMAC domain separation. The returned reader yields + /// an effectively infinite stream of bytes; reading the first `N` bytes + /// from the reader (and truncating) produces the same `N`-byte prefix + /// regardless of whether more bytes will be read later. + /// + /// Use `finalize_xof()` when you need a stream of arbitrary length (e.g. for + /// KDFs or streaming output). Use `finalize_into()` when the requested output + /// length must influence the MAC result itself. + #[inline(always)] + fn finalize_xof(mut self) -> Self::Reader { + // FUTURE: support extendable output via a MAC trait? + let Self { core, buffer } = &mut self; + let core = as ExtendableOutputCore>::finalize_xof_core(core, buffer); + let buffer = Default::default(); + Self::Reader { core, buffer } + } + } + }; +} + +impl_kmac!(Kmac128, CShake128, Kmac128Reader, U168, U32); +impl_kmac!(Kmac256, CShake256, Kmac256Reader, U136, U64); + +#[cfg(test)] +mod tests { + extern crate std; + use super::*; + use hex_literal::hex; + + fn run_kmac128() -> Kmac128 { + let mut mac = Kmac128::new_customization(b"my secret key", b"S") + .expect("Failed to create a KMAC128 instance from key"); + mac.update(b"my message"); + mac + } + + fn run_kmac256() -> Kmac256 { + let mut mac = Kmac256::new_customization(b"my secret key", b"S") + .expect("Failed to create a KMAC256 instance from key"); + mac.update(b"my message"); + mac + } + + #[test] + #[rustfmt::skip] + fn test_kmac128() { + let out_default = run_kmac128().finalize(); + assert_eq!(out_default.as_bytes().as_slice(), &[248, 117, 251, 104, 105, 74, 192, 171, 41, 119, 90, 145, 137, 1, 243, 168, 28, 139, 94, 23, 113, 176, 36, 194, 10, 9, 40, 209, 193, 167, 181, 254]); + + // confirm finalize_into works the same way + let mut out_into = [0u8; 32]; + run_kmac128().finalize_into(&mut out_into); + assert_eq!(out_default.as_bytes().as_slice(), &out_into); + + // confirm finalize_into does not compute subsets + let mut out_into_subset = [0u8; 16]; + run_kmac128().finalize_into(&mut out_into_subset); + assert_ne!(&out_into_subset, &out_into[..16]); + + // confirm xof is different + let mut reader_xof = run_kmac128().finalize_xof(); + let mut out_xof = [0u8; 32]; + reader_xof.read(&mut out_xof); + assert_ne!(out_xof, out_default.as_bytes().as_slice()); + assert_eq!(&out_xof, &[71, 56, 26, 111, 123, 15, 120, 166, 36, 250, 143, 80, 116, 63, 206, 89, 113, 96, 83, 169, 87, 200, 233, 11, 202, 145, 90, 196, 108, 24, 82, 103]); + + // confirm xof is subset + let mut reader_xof_subset = run_kmac128().finalize_xof(); + let mut out_xof_subset = [0u8; 16]; + reader_xof_subset.read(&mut out_xof_subset); + assert_eq!(&out_xof[..16], &out_xof_subset); + } + + #[test] + #[rustfmt::skip] + fn test_kmac256() { + let out_default = run_kmac256().finalize(); + assert_eq!(out_default.as_bytes().as_slice(), &[158, 175, 254, 101, 124, 16, 93, 198, 176, 54, 249, 78, 167, 112, 206, 159, 229, 55, 225, 168, 71, 228, 28, 222, 195, 148, 255, 241, 196, 172, 37, 60, 135, 67, 155, 134, 43, 61, 215, 243, 128, 55, 227, 169, 175, 22, 14, 132, 174, 63, 69, 60, 50, 41, 88, 148, 11, 41, 9, 90, 0, 87, 143, 131]); + + // confirm finalize_into works the same way + let mut out_into = [0u8; 64]; + run_kmac256().finalize_into(&mut out_into); + assert_eq!(out_default.as_bytes().as_slice(), &out_into); + + // confirm finalize_into does not compute subsets + let mut out_into_subset = [0u8; 32]; + run_kmac256().finalize_into(&mut out_into_subset); + assert_ne!(&out_into_subset, &out_into[..32]); + + // confirm xof is different + let mut reader_xof = run_kmac256().finalize_xof(); + let mut out_xof = [0u8; 64]; + reader_xof.read(&mut out_xof); + assert_ne!(out_xof, out_default.as_bytes().as_slice()); + assert_eq!(&out_xof, &[37, 85, 107, 43, 116, 204, 145, 99, 161, 150, 174, 110, 206, 240, 129, 44, 64, 135, 52, 83, 20, 250, 101, 166, 99, 189, 129, 61, 204, 210, 197, 150, 17, 43, 99, 218, 159, 87, 85, 155, 240, 197, 115, 97, 209, 145, 228, 236, 86, 104, 143, 194, 191, 69, 226, 206, 173, 224, 226, 25, 10, 13, 195, 252]); + + // confirm xof is subset + let mut reader_xof_subset = run_kmac256().finalize_xof(); + let mut out_xof_subset = [0u8; 32]; + reader_xof_subset.read(&mut out_xof_subset); + assert_eq!(&out_xof[..32], &out_xof_subset); + } + + #[test] + fn test_readme_example_verify() { + let mut mac = Kmac128::new_from_slice(b"key material").unwrap(); + mac.update(b"input message"); + let result = mac.finalize(); + let code_bytes = result.into_bytes(); + let expected = hex!( + " + c39a8f614f8821443599440df5402787 + 0f67e4c47919061584f14a616f3efcf5 + " + ); + assert_eq!( + code_bytes[..], + expected[..], + "Expected hex output is {}", + hex::encode(&code_bytes) + ); + + let mut mac = Kmac128::new_from_slice(b"key material").unwrap(); + mac.update(b"input message"); + mac.verify_slice(&expected).unwrap(); + } + + #[test] + fn test_readme_example_into() { + let mut mac = Kmac256::new_customization(b"key material", b"customization").unwrap(); + mac.update(b"input message"); + let mut output = [0u8; 32]; + mac.finalize_into(&mut output); + + let expected = hex!( + " + 85fb77da3a35e4c4b0057c3151e6cc54 + ee401ffe65ec2f0239f439be8896f7b6 + " + ); + assert_eq!( + output[..], + expected[..], + "Expected hex output is {}", + hex::encode(&output) + ); + } + + #[test] + fn test_readme_example_xof() { + let mut mac = Kmac256::new_customization(b"key material", b"customization").unwrap(); + mac.update(b"input message"); + let mut reader = mac.finalize_xof(); + + let mut output = [0u8; 32]; + reader.read(&mut output); + + let expected = hex!( + " + b675b75668eab0706ab05650f34fa1b6 + 24051a9a42b5e42cfe9970e8f903d45b + " + ); + assert_eq!( + output[..], + expected[..], + "Expected hex output is {}", + hex::encode(&output) + ); + } +} diff --git a/kmac/src/traits.rs b/kmac/src/traits.rs new file mode 100644 index 0000000..d727639 --- /dev/null +++ b/kmac/src/traits.rs @@ -0,0 +1,50 @@ +use digest::HashMarker; +use digest::block_api::{ + BufferKindUser, CoreProxy, Eager, ExtendableOutputCore, SmallBlockSizeUser, UpdateCore, +}; + +const FUNCTION_NAME: &[u8] = b"KMAC"; + +pub trait CShake { + fn new_cshake(customization: &[u8]) -> Self; +} + +impl CShake for sha3::block_api::CShake128Core { + fn new_cshake(customization: &[u8]) -> Self { + Self::new_with_function_name(FUNCTION_NAME, customization) + } +} + +impl CShake for sha3::block_api::CShake256Core { + fn new_cshake(customization: &[u8]) -> Self { + Self::new_with_function_name(FUNCTION_NAME, customization) + } +} + +/// Trait implemented by eager hashes which expose their block-level core. +pub trait EagerHash: SmallBlockSizeUser { + /// Block-level core type of the hash. + type Core: HashMarker + + CShake + + UpdateCore + + ExtendableOutputCore + + SmallBlockSizeUser<_BlockSize = ::_BlockSize> + + BufferKindUser + + Default + + Clone; +} + +impl EagerHash for T +where + T: CoreProxy + SmallBlockSizeUser, + ::Core: HashMarker + + CShake + + UpdateCore + + ExtendableOutputCore + + SmallBlockSizeUser<_BlockSize = ::_BlockSize> + + BufferKindUser + + Default + + Clone, +{ + type Core = T::Core; +} diff --git a/kmac/tests/nist.rs b/kmac/tests/nist.rs new file mode 100644 index 0000000..d471e0f --- /dev/null +++ b/kmac/tests/nist.rs @@ -0,0 +1,339 @@ +//! KMAC and KMACXOF test vectors, sourced from NIST SP 800-185 in: +//! - https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/KMAC_samples.pdf +//! - https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/KMACXOF_samples.pdf + +use kmac::{ExtendableOutput, Kmac128, Kmac256, Mac, XofReader}; + +#[test] +fn test_kmac128() { + let vectors: &[(&[u8], &[u8], &[u8], &[u8])] = &[ + ( + &[ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, + 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, + 0x5C, 0x5D, 0x5E, 0x5F, + ], + &[0x00, 0x01, 0x02, 0x03], + &[], + &[ + 0xE5, 0x78, 0x0B, 0x0D, 0x3E, 0xA6, 0xF7, 0xD3, 0xA4, 0x29, 0xC5, 0x70, 0x6A, 0xA4, + 0x3A, 0x00, 0xFA, 0xDB, 0xD7, 0xD4, 0x96, 0x28, 0x83, 0x9E, 0x31, 0x87, 0x24, 0x3F, + 0x45, 0x6E, 0xE1, 0x4E, + ], + ), + ( + &[ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, + 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, + 0x5C, 0x5D, 0x5E, 0x5F, + ], + &[0x00, 0x01, 0x02, 0x03], + b"My Tagged Application", + &[ + 0x3B, 0x1F, 0xBA, 0x96, 0x3C, 0xD8, 0xB0, 0xB5, 0x9E, 0x8C, 0x1A, 0x6D, 0x71, 0x88, + 0x8B, 0x71, 0x43, 0x65, 0x1A, 0xF8, 0xBA, 0x0A, 0x70, 0x70, 0xC0, 0x97, 0x9E, 0x28, + 0x11, 0x32, 0x4A, 0xA5, + ], + ), + ( + &[ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, + 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, + 0x5C, 0x5D, 0x5E, 0x5F, + ], + &[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, + 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, + 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, + 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, + 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, + 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, + 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, + 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, + 0xC4, 0xC5, 0xC6, 0xC7, + ], + b"My Tagged Application", + &[ + 0x1F, 0x5B, 0x4E, 0x6C, 0xCA, 0x02, 0x20, 0x9E, 0x0D, 0xCB, 0x5C, 0xA6, 0x35, 0xB8, + 0x9A, 0x15, 0xE2, 0x71, 0xEC, 0xC7, 0x60, 0x07, 0x1D, 0xFD, 0x80, 0x5F, 0xAA, 0x38, + 0xF9, 0x72, 0x92, 0x30, + ], + ), + ]; + for (i, &(key, data, s, expected)) in vectors.iter().enumerate() { + let mut hash = Kmac128::new_customization(key, s).unwrap(); + hash.update(data); + let result = hash.finalize(); + assert_eq!(result.as_bytes().as_slice().len(), expected.len(), "#{i}"); + assert_eq!(result.as_bytes().as_slice(), expected, "#{i}"); + } +} + +#[test] +fn test_kmacxof128() { + let vectors: &[(&[u8], &[u8], &[u8], &[u8])] = &[ + ( + &[ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, + 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, + 0x5C, 0x5D, 0x5E, 0x5F, + ], + &[0x00, 0x01, 0x02, 0x03], + &[], + &[ + 0xCD, 0x83, 0x74, 0x0B, 0xBD, 0x92, 0xCC, 0xC8, 0xCF, 0x03, 0x2B, 0x14, 0x81, 0xA0, + 0xF4, 0x46, 0x0E, 0x7C, 0xA9, 0xDD, 0x12, 0xB0, 0x8A, 0x0C, 0x40, 0x31, 0x17, 0x8B, + 0xAC, 0xD6, 0xEC, 0x35, + ], + ), + ( + &[ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, + 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, + 0x5C, 0x5D, 0x5E, 0x5F, + ], + &[0x00, 0x01, 0x02, 0x03], + b"My Tagged Application", + &[ + 0x31, 0xA4, 0x45, 0x27, 0xB4, 0xED, 0x9F, 0x5C, 0x61, 0x01, 0xD1, 0x1D, 0xE6, 0xD2, + 0x6F, 0x06, 0x20, 0xAA, 0x5C, 0x34, 0x1D, 0xEF, 0x41, 0x29, 0x96, 0x57, 0xFE, 0x9D, + 0xF1, 0xA3, 0xB1, 0x6C, + ], + ), + ( + &[ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, + 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, + 0x5C, 0x5D, 0x5E, 0x5F, + ], + &[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, + 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, + 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, + 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, + 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, + 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, + 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, + 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, + 0xC4, 0xC5, 0xC6, 0xC7, + ], + b"My Tagged Application", + &[ + 0x47, 0x02, 0x6C, 0x7C, 0xD7, 0x93, 0x08, 0x4A, 0xA0, 0x28, 0x3C, 0x25, 0x3E, 0xF6, + 0x58, 0x49, 0x0C, 0x0D, 0xB6, 0x14, 0x38, 0xB8, 0x32, 0x6F, 0xE9, 0xBD, 0xDF, 0x28, + 0x1B, 0x83, 0xAE, 0x0F, + ], + ), + ]; + + for (i, &(key, data, s, expected)) in vectors.iter().enumerate() { + let mut hash = Kmac128::new_customization(key, s).unwrap(); + hash.update(data); + let mut reader = hash.finalize_xof(); + let mut result = [0u8; 32]; + reader.read(&mut result); + assert_eq!(result.as_slice().len(), expected.len(), "#{i}"); + assert_eq!(result.as_slice(), expected, "#{i}"); + } +} + +#[test] +fn test_kmac256() { + let vectors: &[(&[u8], &[u8], &[u8], &[u8])] = &[ + ( + &[ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, + 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, + 0x5C, 0x5D, 0x5E, 0x5F, + ], + &[0x00, 0x01, 0x02, 0x03], + b"My Tagged Application", + &[ + 0x20, 0xC5, 0x70, 0xC3, 0x13, 0x46, 0xF7, 0x03, 0xC9, 0xAC, 0x36, 0xC6, 0x1C, 0x03, + 0xCB, 0x64, 0xC3, 0x97, 0x0D, 0x0C, 0xFC, 0x78, 0x7E, 0x9B, 0x79, 0x59, 0x9D, 0x27, + 0x3A, 0x68, 0xD2, 0xF7, 0xF6, 0x9D, 0x4C, 0xC3, 0xDE, 0x9D, 0x10, 0x4A, 0x35, 0x16, + 0x89, 0xF2, 0x7C, 0xF6, 0xF5, 0x95, 0x1F, 0x01, 0x03, 0xF3, 0x3F, 0x4F, 0x24, 0x87, + 0x10, 0x24, 0xD9, 0xC2, 0x77, 0x73, 0xA8, 0xDD, + ], + ), + ( + &[ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, + 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, + 0x5C, 0x5D, 0x5E, 0x5F, + ], + &[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, + 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, + 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, + 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, + 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, + 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, + 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, + 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, + 0xC4, 0xC5, 0xC6, 0xC7, + ], + &[], + &[ + 0x75, 0x35, 0x8C, 0xF3, 0x9E, 0x41, 0x49, 0x4E, 0x94, 0x97, 0x07, 0x92, 0x7C, 0xEE, + 0x0A, 0xF2, 0x0A, 0x3F, 0xF5, 0x53, 0x90, 0x4C, 0x86, 0xB0, 0x8F, 0x21, 0xCC, 0x41, + 0x4B, 0xCF, 0xD6, 0x91, 0x58, 0x9D, 0x27, 0xCF, 0x5E, 0x15, 0x36, 0x9C, 0xBB, 0xFF, + 0x8B, 0x9A, 0x4C, 0x2E, 0xB1, 0x78, 0x00, 0x85, 0x5D, 0x02, 0x35, 0xFF, 0x63, 0x5D, + 0xA8, 0x25, 0x33, 0xEC, 0x6B, 0x75, 0x9B, 0x69, + ], + ), + ( + &[ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, + 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, + 0x5C, 0x5D, 0x5E, 0x5F, + ], + &[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, + 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, + 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, + 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, + 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, + 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, + 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, + 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, + 0xC4, 0xC5, 0xC6, 0xC7, + ], + b"My Tagged Application", + &[ + 0xB5, 0x86, 0x18, 0xF7, 0x1F, 0x92, 0xE1, 0xD5, 0x6C, 0x1B, 0x8C, 0x55, 0xDD, 0xD7, + 0xCD, 0x18, 0x8B, 0x97, 0xB4, 0xCA, 0x4D, 0x99, 0x83, 0x1E, 0xB2, 0x69, 0x9A, 0x83, + 0x7D, 0xA2, 0xE4, 0xD9, 0x70, 0xFB, 0xAC, 0xFD, 0xE5, 0x00, 0x33, 0xAE, 0xA5, 0x85, + 0xF1, 0xA2, 0x70, 0x85, 0x10, 0xC3, 0x2D, 0x07, 0x88, 0x08, 0x01, 0xBD, 0x18, 0x28, + 0x98, 0xFE, 0x47, 0x68, 0x76, 0xFC, 0x89, 0x65, + ], + ), + ]; + for (i, &(key, data, s, expected)) in vectors.iter().enumerate() { + let mut hash = Kmac256::new_customization(key, s).unwrap(); + hash.update(data); + let result = hash.finalize(); + assert_eq!(result.as_bytes().as_slice().len(), expected.len(), "#{i}"); + assert_eq!(result.as_bytes().as_slice(), expected, "#{i}"); + } +} + +#[test] +fn test_kmacxof256() { + let vectors: &[(&[u8], &[u8], &[u8], &[u8])] = &[ + ( + &[ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, + 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, + 0x5C, 0x5D, 0x5E, 0x5F, + ], + &[0x00, 0x01, 0x02, 0x03], + b"My Tagged Application", + &[ + 0x17, 0x55, 0x13, 0x3F, 0x15, 0x34, 0x75, 0x2A, 0xAD, 0x07, 0x48, 0xF2, 0xC7, 0x06, + 0xFB, 0x5C, 0x78, 0x45, 0x12, 0xCA, 0xB8, 0x35, 0xCD, 0x15, 0x67, 0x6B, 0x16, 0xC0, + 0xC6, 0x64, 0x7F, 0xA9, 0x6F, 0xAA, 0x7A, 0xF6, 0x34, 0xA0, 0xBF, 0x8F, 0xF6, 0xDF, + 0x39, 0x37, 0x4F, 0xA0, 0x0F, 0xAD, 0x9A, 0x39, 0xE3, 0x22, 0xA7, 0xC9, 0x20, 0x65, + 0xA6, 0x4E, 0xB1, 0xFB, 0x08, 0x01, 0xEB, 0x2B, + ], + ), + ( + &[ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, + 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, + 0x5C, 0x5D, 0x5E, 0x5F, + ], + &[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, + 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, + 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, + 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, + 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, + 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, + 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, + 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, + 0xC4, 0xC5, 0xC6, 0xC7, + ], + &[], + &[ + 0xFF, 0x7B, 0x17, 0x1F, 0x1E, 0x8A, 0x2B, 0x24, 0x68, 0x3E, 0xED, 0x37, 0x83, 0x0E, + 0xE7, 0x97, 0x53, 0x8B, 0xA8, 0xDC, 0x56, 0x3F, 0x6D, 0xA1, 0xE6, 0x67, 0x39, 0x1A, + 0x75, 0xED, 0xC0, 0x2C, 0xA6, 0x33, 0x07, 0x9F, 0x81, 0xCE, 0x12, 0xA2, 0x5F, 0x45, + 0x61, 0x5E, 0xC8, 0x99, 0x72, 0x03, 0x1D, 0x18, 0x33, 0x73, 0x31, 0xD2, 0x4C, 0xEB, + 0x8F, 0x8C, 0xA8, 0xE6, 0xA1, 0x9F, 0xD9, 0x8B, + ], + ), + ( + &[ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, + 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, + 0x5C, 0x5D, 0x5E, 0x5F, + ], + &[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, + 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, + 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, + 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, + 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, + 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, + 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, + 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, + 0xC4, 0xC5, 0xC6, 0xC7, + ], + b"My Tagged Application", + &[ + 0xD5, 0xBE, 0x73, 0x1C, 0x95, 0x4E, 0xD7, 0x73, 0x28, 0x46, 0xBB, 0x59, 0xDB, 0xE3, + 0xA8, 0xE3, 0x0F, 0x83, 0xE7, 0x7A, 0x4B, 0xFF, 0x44, 0x59, 0xF2, 0xF1, 0xC2, 0xB4, + 0xEC, 0xEB, 0xB8, 0xCE, 0x67, 0xBA, 0x01, 0xC6, 0x2E, 0x8A, 0xB8, 0x57, 0x8D, 0x2D, + 0x49, 0x9B, 0xD1, 0xBB, 0x27, 0x67, 0x68, 0x78, 0x11, 0x90, 0x02, 0x0A, 0x30, 0x6A, + 0x97, 0xDE, 0x28, 0x1D, 0xCC, 0x30, 0x30, 0x5D, + ], + ), + ]; + + for (i, &(key, data, s, expected)) in vectors.iter().enumerate() { + let mut hash = Kmac256::new_customization(key, s).unwrap(); + hash.update(data); + let mut reader = hash.finalize_xof(); + let mut result = [0u8; 64]; + reader.read(&mut result); + assert_eq!(result.as_slice().len(), expected.len(), "#{i}"); + assert_eq!(result.as_slice(), expected, "#{i}"); + } +} From ec417abcbb698d01e0a16d7870b8e157ab8d9147 Mon Sep 17 00:00:00 2001 From: Liam Gray Date: Fri, 3 Apr 2026 13:16:20 +0100 Subject: [PATCH 2/3] kmac: Replace sha3 dependency with new cshake crate --- Cargo.lock | 21 +++--- kmac/Cargo.toml | 2 +- kmac/README.md | 2 +- kmac/src/kmac.rs | 172 ++++++++++++++++++++------------------------- kmac/src/lib.rs | 134 ++++++++++++++++++++--------------- kmac/src/traits.rs | 50 ------------- kmac/tests/nist.rs | 2 + 7 files changed, 166 insertions(+), 217 deletions(-) delete mode 100644 kmac/src/traits.rs diff --git a/Cargo.lock b/Cargo.lock index 74dd027..449a0ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,6 +136,15 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "cshake" +version = "0.1.0" +source = "git+https://github.com/RustCrypto/hashes?branch=master#6c69ea988cac53319564ed90cd87671ed50d3579" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "ctutils" version = "0.4.0" @@ -234,10 +243,10 @@ dependencies = [ name = "kmac" version = "0.1.0" dependencies = [ + "cshake", "digest", "hex", "hex-literal", - "sha3", ] [[package]] @@ -318,16 +327,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sha3" -version = "0.11.0-rc.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b233a7d59d7bfc027208506a33ffc9532b2acb24ddc61fe7e758dc2250db431" -dependencies = [ - "digest", - "keccak", -] - [[package]] name = "streebog" version = "0.11.0" diff --git a/kmac/Cargo.toml b/kmac/Cargo.toml index 2d6a6f1..01af9bd 100644 --- a/kmac/Cargo.toml +++ b/kmac/Cargo.toml @@ -13,8 +13,8 @@ categories = ["cryptography", "no-std"] rust-version = "1.85" [dependencies] +cshake = { git = "https://github.com/RustCrypto/hashes", branch = "master" } digest = { version = "0.11.0", features = ["mac"] } -sha3 = "0.11.0-rc.9" [dev-dependencies] digest = { version = "0.11.0", features = ["dev"] } diff --git a/kmac/README.md b/kmac/README.md index 0d86d95..903399b 100644 --- a/kmac/README.md +++ b/kmac/README.md @@ -78,7 +78,7 @@ use hex_literal::hex; let mut mac = Kmac256::new_customization(b"key material", b"customization").unwrap(); mac.update(b"input message"); let mut output = [0u8; 32]; -mac.finalize_into(&mut output); +mac.finalize_into_buf(&mut output); let expected = hex!(" 85fb77da3a35e4c4b0057c3151e6cc54 diff --git a/kmac/src/kmac.rs b/kmac/src/kmac.rs index b24f8bb..2dd8fb2 100644 --- a/kmac/src/kmac.rs +++ b/kmac/src/kmac.rs @@ -1,73 +1,87 @@ use crate::encoding::{left_encode, right_encode}; -use crate::traits::{CShake, EagerHash}; -use core::fmt; -use digest::block_api::{ - AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, Eager, ExtendableOutputCore, - FixedOutputCore, UpdateCore, XofReaderCore, -}; +use cshake::CShake; +use digest::block_api::BlockSizeUser; use digest::common::KeySizeUser; -use digest::{InvalidLength, Key, KeyInit, MacMarker, Output, OutputSizeUser}; +use digest::typenum::Unsigned; +use digest::{ExtendableOutput, InvalidLength, Key, KeyInit, MacMarker, Update, XofReader}; -pub struct KmacCore { - digest: D::Core, +/// Trait alias for CShake types usable with KMAC. +pub trait CShakeUser: BlockSizeUser + Update + ExtendableOutput + Clone { + fn new_kmac(customization: &[u8]) -> Self; } -impl Clone for KmacCore { +impl CShakeUser for cshake::CShake128 { + fn new_kmac(customization: &[u8]) -> Self { + CShake::new_with_function_name(b"KMAC", customization) + } +} + +impl CShakeUser for cshake::CShake256 { + fn new_kmac(customization: &[u8]) -> Self { + CShake::new_with_function_name(b"KMAC", customization) + } +} + +pub struct KmacInner { + cshake: D, +} + +impl Clone for KmacInner { #[inline(always)] fn clone(&self) -> Self { Self { - digest: self.digest.clone(), + cshake: self.cshake.clone(), } } } -impl MacMarker for KmacCore {} - -impl BufferKindUser for KmacCore { - type BufferKind = Eager; -} +impl MacMarker for KmacInner {} -impl KeySizeUser for KmacCore { - type KeySize = ::BlockSize; +impl KeySizeUser for KmacInner { + type KeySize = D::BlockSize; } -impl BlockSizeUser for KmacCore { - type BlockSize = ::BlockSize; +impl BlockSizeUser for KmacInner { + type BlockSize = D::BlockSize; } -impl KmacCore { +impl KmacInner { #[inline(always)] pub fn new_customization(key: &[u8], customisation: &[u8]) -> Self { - // digest: bufpad(encode_string(K), bufsize) || X || right_encode(L) - // where bufpad(X, w) = left_encode(len(w)) || X || zeros - // where encode_string(K) = left_encode(len(K)) || K - let mut digest = D::Core::new_cshake(customisation); - let mut buffer = Buffer::::default(); + let mut cshake = D::new_kmac(customisation); + let block_size = D::BlockSize::USIZE; let mut encode_buffer = [0u8; 9]; - // bytepad, left_encode(w) - buffer.digest_blocks( - left_encode(D::block_size() as u64, &mut encode_buffer), - |blocks| digest.update_blocks(blocks), - ); - - // encode_string(K), left_encode(len(K)) -- length is in bits - buffer.digest_blocks( - left_encode(8 * key.len() as u64, &mut encode_buffer), - |blocks| digest.update_blocks(blocks), - ); - - // encode_string(K) copy K into blocks - buffer.digest_blocks(key, |blocks| digest.update_blocks(blocks)); - - // bytepad, pad the key to the block size - digest.update_blocks(&[buffer.pad_with_zeros()]); + // bytepad: left_encode(w) + let le_w = left_encode(block_size as u64, &mut encode_buffer); + let mut total = le_w.len(); + cshake.update(le_w); + + // encode_string(K): left_encode(8*len(K)) || K + let le_k = left_encode(8 * key.len() as u64, &mut encode_buffer); + total += le_k.len(); + cshake.update(le_k); + + total += key.len(); + cshake.update(key); + + // pad to block boundary + let pad_len = (block_size - (total % block_size)) % block_size; + if pad_len > 0 { + let zeros = [0u8; 168]; // max block size + let mut remaining = pad_len; + while remaining > 0 { + let chunk = core::cmp::min(remaining, zeros.len()); + cshake.update(&zeros[..chunk]); + remaining -= chunk; + } + } - Self { digest } + Self { cshake } } } -impl KeyInit for KmacCore { +impl KeyInit for KmacInner { #[inline] fn new(key: &Key) -> Self { Self::new_customization(key.as_slice(), &[]) @@ -79,68 +93,34 @@ impl KeyInit for KmacCore { } } -impl UpdateCore for KmacCore { +impl Update for KmacInner { #[inline(always)] - fn update_blocks(&mut self, blocks: &[Block]) { - self.digest.update_blocks(blocks); + fn update(&mut self, data: &[u8]) { + self.cshake.update(data); } } -impl KmacCore { - /// Finalizes the KMAC for any output array size. +impl KmacInner { + /// Finalizes the KMAC for any output array size (fixed-length output). #[inline(always)] - pub fn finalize_core(&mut self, buffer: &mut Buffer, out: &mut [u8]) { + pub fn finalize_fixed_inner(mut self, out: &mut [u8]) { // right_encode(L), where L = output length in bits - buffer.digest_blocks( - right_encode(8 * out.len() as u64, &mut [0u8; 9]), - |blocks| self.update_blocks(blocks), - ); - - let mut reader = self.digest.finalize_xof_core(buffer); - - let mut pos = 0; - while pos < out.len() { - let block = reader.read_block(); - let to_copy = core::cmp::min(out.len() - pos, block.len()); - out[pos..pos + to_copy].copy_from_slice(&block[..to_copy]); - pos += to_copy; - } - } -} + let mut encode_buffer = [0u8; 9]; + let re = right_encode(8 * out.len() as u64, &mut encode_buffer); + self.cshake.update(re); -impl FixedOutputCore for KmacCore -where - KmacCore: OutputSizeUser, -{ - #[inline(always)] - fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { - self.finalize_core(buffer, out.as_mut_slice()); + let mut reader = self.cshake.finalize_xof(); + reader.read(out); } -} - -impl ExtendableOutputCore for KmacCore { - type ReaderCore = ::ReaderCore; + /// Finalizes the KMAC for extendable output (XOF). #[inline(always)] - fn finalize_xof_core(&mut self, buffer: &mut Buffer) -> Self::ReaderCore { + pub fn finalize_xof_inner(mut self) -> D::Reader { // right_encode(0), as L = 0 for extendable output - buffer.digest_blocks(right_encode(0, &mut [0u8; 9]), |blocks| { - self.update_blocks(blocks) - }); - self.digest.finalize_xof_core(buffer) - } -} - -impl AlgorithmName for KmacCore { - fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Kmac<")?; - ::write_alg_name(f)?; - f.write_str(">") - } -} + let mut encode_buffer = [0u8; 9]; + let re = right_encode(0, &mut encode_buffer); + self.cshake.update(re); -impl fmt::Debug for KmacCore { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("KmacCore { ... }") + self.cshake.finalize_xof() } } diff --git a/kmac/src/lib.rs b/kmac/src/lib.rs index 0181ea0..84e22d3 100644 --- a/kmac/src/lib.rs +++ b/kmac/src/lib.rs @@ -12,43 +12,78 @@ mod encoding; mod kmac; -mod traits; -use crate::kmac::KmacCore; -use digest::block_api::{Block, BlockSizeUser, Buffer, ExtendableOutputCore, XofReaderCore}; -use digest::block_buffer::ReadBuffer; +use crate::kmac::KmacInner; +use cshake::{CShake128, CShake256, CShakeReader}; use digest::consts::{U32, U64, U136, U168}; -pub use digest::{self, ExtendableOutput, KeyInit, Mac, XofReader}; -use digest::{InvalidLength, OutputSizeUser}; -use sha3::block_api::Sha3ReaderCore; -use sha3::{CShake128, CShake256}; +pub use digest::{self, ExtendableOutput, FixedOutput, KeyInit, Mac, XofReader}; +use digest::{InvalidLength, MacMarker, Output, OutputSizeUser, Update}; /// Manually implement the extra KMAC methods and XOF traits. macro_rules! impl_kmac { - ($kmac:ident, $cshake:ident, $reader:ident, $block_size:ident, $output_size:ident) => { - digest::buffer_fixed!( - /// KMAC implementation as per Section 4 of [NIST SP 800-185]. - pub struct $kmac(KmacCore<$cshake>); - impl: MacTraits KeyInit; - ); + ($kmac:ident, $cshake:ty, $reader:ident, $rate:ident, $output_size:ident) => { + /// KMAC implementation as per Section 4 of [NIST SP 800-185]. + #[derive(Clone)] + pub struct $kmac { + inner: KmacInner<$cshake>, + } - impl OutputSizeUser for KmacCore<$cshake> { + impl MacMarker for $kmac {} + + impl OutputSizeUser for $kmac { type OutputSize = $output_size; } + impl digest::common::KeySizeUser for $kmac { + type KeySize = as digest::common::KeySizeUser>::KeySize; + } + + impl KeyInit for $kmac { + #[inline] + fn new(key: &digest::Key) -> Self { + Self { + inner: KmacInner::<$cshake>::new_customization(key.as_slice(), &[]), + } + } + + #[inline(always)] + fn new_from_slice(key: &[u8]) -> Result { + Ok(Self { + inner: KmacInner::<$cshake>::new_customization(key, &[]), + }) + } + } + + impl Update for $kmac { + #[inline(always)] + fn update(&mut self, data: &[u8]) { + self.inner.update(data); + } + } + + impl FixedOutput for $kmac { + #[inline(always)] + fn finalize_into(self, out: &mut Output) { + self.inner.finalize_fixed_inner(out.as_mut_slice()); + } + } + impl $kmac { /// Create a new KMAC with the given key and customisation. /// /// Section 4.2 of [NIST SP 800-185] specifies that KMAC takes both a key (K) and an /// optional customisation string (S). #[inline] - pub fn new_customization(key: &[u8], customisation: &[u8]) -> Result { + pub fn new_customization( + key: &[u8], + customisation: &[u8], + ) -> Result { // TODO: KeyInitWithCustomization trait, following KeyInit as new_with_customization and new_from_slice_with_customization. // TODO: review the Result, as this implementation is infallible. Currently matching KeyInit::new_from_slice. // FUTURE: support key+customisation initialisation via traits. - let core = KmacCore::<$cshake>::new_customization(key, customisation); - let buffer = Buffer::>::default(); - Ok(Self { core, buffer }) + Ok(Self { + inner: KmacInner::<$cshake>::new_customization(key, customisation), + }) } /// Finalize this KMAC into a fixed-length output buffer, as defined in Section 4.3 @@ -57,43 +92,27 @@ macro_rules! impl_kmac { /// This method finalizes the KMAC and *mixes the requested output length into the /// KMAC domain separation*. That means the resulting bytes are dependent on the /// exact length of `out`. Use this when the output length is part of the MAC/derivation - /// semantics (for example when the length itself must influence the MAC result). + /// semantics (for example, when the length itself must influence the MAC result). /// /// This is *not* equivalent to calling `finalize_xof()` and then reading `out.len()` /// bytes from the returned reader; the two approaches produce different outputs. #[inline] - pub fn finalize_into(&mut self, out: &mut [u8]) { + pub fn finalize_into_buf(self, out: &mut [u8]) { // TODO: review method naming. // FUTURE: support custom output sizes via traits. - let buffer = &mut self.buffer; - self.core.finalize_core(buffer, out); + self.inner.finalize_fixed_inner(out); } } /// Reader for KMAC that implements the XOF interface. pub struct $reader { - core: Sha3ReaderCore<$block_size>, - buffer: ReadBuffer< as BlockSizeUser>::BlockSize>, - } - - impl BlockSizeUser for $reader { - type BlockSize = as BlockSizeUser>::BlockSize; - } - - impl XofReaderCore for $reader { - #[inline(always)] - fn read_block(&mut self) -> Block { - self.core.read_block() - } + inner: CShakeReader<$rate>, } impl XofReader for $reader { #[inline(always)] fn read(&mut self, buf: &mut [u8]) -> () { - let Self { core, buffer } = self; - buffer.read(buf, |block| { - *block = XofReaderCore::read_block(core); - }); + self.inner.read(buf); } } @@ -113,12 +132,11 @@ macro_rules! impl_kmac { /// KDFs or streaming output). Use `finalize_into()` when the requested output /// length must influence the MAC result itself. #[inline(always)] - fn finalize_xof(mut self) -> Self::Reader { + fn finalize_xof(self) -> Self::Reader { // FUTURE: support extendable output via a MAC trait? - let Self { core, buffer } = &mut self; - let core = as ExtendableOutputCore>::finalize_xof_core(core, buffer); - let buffer = Default::default(); - Self::Reader { core, buffer } + $reader { + inner: self.inner.finalize_xof_inner(), + } } } }; @@ -130,7 +148,7 @@ impl_kmac!(Kmac256, CShake256, Kmac256Reader, U136, U64); #[cfg(test)] mod tests { extern crate std; - use super::*; + use super::{ExtendableOutput, KeyInit, Kmac128, Kmac256, Mac, XofReader}; use hex_literal::hex; fn run_kmac128() -> Kmac128 { @@ -153,14 +171,14 @@ mod tests { let out_default = run_kmac128().finalize(); assert_eq!(out_default.as_bytes().as_slice(), &[248, 117, 251, 104, 105, 74, 192, 171, 41, 119, 90, 145, 137, 1, 243, 168, 28, 139, 94, 23, 113, 176, 36, 194, 10, 9, 40, 209, 193, 167, 181, 254]); - // confirm finalize_into works the same way + // confirm finalize_into_buf works the same way let mut out_into = [0u8; 32]; - run_kmac128().finalize_into(&mut out_into); + run_kmac128().finalize_into_buf(&mut out_into); assert_eq!(out_default.as_bytes().as_slice(), &out_into); - // confirm finalize_into does not compute subsets + // confirm finalize_into_buf does not compute subsets let mut out_into_subset = [0u8; 16]; - run_kmac128().finalize_into(&mut out_into_subset); + run_kmac128().finalize_into_buf(&mut out_into_subset); assert_ne!(&out_into_subset, &out_into[..16]); // confirm xof is different @@ -183,14 +201,14 @@ mod tests { let out_default = run_kmac256().finalize(); assert_eq!(out_default.as_bytes().as_slice(), &[158, 175, 254, 101, 124, 16, 93, 198, 176, 54, 249, 78, 167, 112, 206, 159, 229, 55, 225, 168, 71, 228, 28, 222, 195, 148, 255, 241, 196, 172, 37, 60, 135, 67, 155, 134, 43, 61, 215, 243, 128, 55, 227, 169, 175, 22, 14, 132, 174, 63, 69, 60, 50, 41, 88, 148, 11, 41, 9, 90, 0, 87, 143, 131]); - // confirm finalize_into works the same way + // confirm finalize_into_buf works the same way let mut out_into = [0u8; 64]; - run_kmac256().finalize_into(&mut out_into); + run_kmac256().finalize_into_buf(&mut out_into); assert_eq!(out_default.as_bytes().as_slice(), &out_into); - // confirm finalize_into does not compute subsets + // confirm finalize_into_buf does not compute subsets let mut out_into_subset = [0u8; 32]; - run_kmac256().finalize_into(&mut out_into_subset); + run_kmac256().finalize_into_buf(&mut out_into_subset); assert_ne!(&out_into_subset, &out_into[..32]); // confirm xof is different @@ -223,7 +241,7 @@ mod tests { code_bytes[..], expected[..], "Expected hex output is {}", - hex::encode(&code_bytes) + hex::encode(code_bytes) ); let mut mac = Kmac128::new_from_slice(b"key material").unwrap(); @@ -236,7 +254,7 @@ mod tests { let mut mac = Kmac256::new_customization(b"key material", b"customization").unwrap(); mac.update(b"input message"); let mut output = [0u8; 32]; - mac.finalize_into(&mut output); + mac.finalize_into_buf(&mut output); let expected = hex!( " @@ -248,7 +266,7 @@ mod tests { output[..], expected[..], "Expected hex output is {}", - hex::encode(&output) + hex::encode(output) ); } @@ -271,7 +289,7 @@ mod tests { output[..], expected[..], "Expected hex output is {}", - hex::encode(&output) + hex::encode(output) ); } } diff --git a/kmac/src/traits.rs b/kmac/src/traits.rs deleted file mode 100644 index d727639..0000000 --- a/kmac/src/traits.rs +++ /dev/null @@ -1,50 +0,0 @@ -use digest::HashMarker; -use digest::block_api::{ - BufferKindUser, CoreProxy, Eager, ExtendableOutputCore, SmallBlockSizeUser, UpdateCore, -}; - -const FUNCTION_NAME: &[u8] = b"KMAC"; - -pub trait CShake { - fn new_cshake(customization: &[u8]) -> Self; -} - -impl CShake for sha3::block_api::CShake128Core { - fn new_cshake(customization: &[u8]) -> Self { - Self::new_with_function_name(FUNCTION_NAME, customization) - } -} - -impl CShake for sha3::block_api::CShake256Core { - fn new_cshake(customization: &[u8]) -> Self { - Self::new_with_function_name(FUNCTION_NAME, customization) - } -} - -/// Trait implemented by eager hashes which expose their block-level core. -pub trait EagerHash: SmallBlockSizeUser { - /// Block-level core type of the hash. - type Core: HashMarker - + CShake - + UpdateCore - + ExtendableOutputCore - + SmallBlockSizeUser<_BlockSize = ::_BlockSize> - + BufferKindUser - + Default - + Clone; -} - -impl EagerHash for T -where - T: CoreProxy + SmallBlockSizeUser, - ::Core: HashMarker - + CShake - + UpdateCore - + ExtendableOutputCore - + SmallBlockSizeUser<_BlockSize = ::_BlockSize> - + BufferKindUser - + Default - + Clone, -{ - type Core = T::Core; -} diff --git a/kmac/tests/nist.rs b/kmac/tests/nist.rs index d471e0f..2cb0fa2 100644 --- a/kmac/tests/nist.rs +++ b/kmac/tests/nist.rs @@ -2,6 +2,8 @@ //! - https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/KMAC_samples.pdf //! - https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/KMACXOF_samples.pdf +#![allow(clippy::type_complexity)] + use kmac::{ExtendableOutput, Kmac128, Kmac256, Mac, XofReader}; #[test] From 0baa1dee7c82eb61ac60dbaa4191eb634d04b9a5 Mon Sep 17 00:00:00 2001 From: Liam Gray Date: Fri, 3 Apr 2026 16:21:51 +0100 Subject: [PATCH 3/3] kmac: Refactor Rate trait based implementation Integrating comments from @newpavlov's code review. - Remove the messy impl kmac macro in favour of a cleaner trait system approach. - Clean up test vectors and use hex-literal, and use raw NIST vectors copy/pasted from the PDFs to make review easier. - Move public API tests into tests/kmac.rs - Remove std from the std tests. - Remove the bench_full benchmarks, we will discuss this in a separate PR. - Adjust docstrings and README for completeness. --- .github/workflows/kmac.yml | 6 +- kmac/README.md | 16 +- kmac/benches/mod.rs | 41 +--- kmac/src/encoding.rs | 32 ++- kmac/src/kmac.rs | 126 ---------- kmac/src/lib.rs | 446 +++++++++++++++------------------- kmac/tests/kmac.rs | 162 +++++++++++++ kmac/tests/nist.rs | 486 ++++++++++++++----------------------- 8 files changed, 572 insertions(+), 743 deletions(-) delete mode 100644 kmac/src/kmac.rs create mode 100644 kmac/tests/kmac.rs diff --git a/.github/workflows/kmac.yml b/.github/workflows/kmac.yml index 08b4b4e..62bab9b 100644 --- a/.github/workflows/kmac.yml +++ b/.github/workflows/kmac.yml @@ -29,7 +29,7 @@ jobs: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: @@ -38,8 +38,6 @@ jobs: - run: cargo build --no-default-features --target ${{ matrix.target }} minimal-versions: - # disabled until belt-block gets published - if: false uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master with: working-directory: ${{ github.workflow }} @@ -52,7 +50,7 @@ jobs: - 1.85.0 # MSRV - stable steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: diff --git a/kmac/README.md b/kmac/README.md index 903399b..b143caf 100644 --- a/kmac/README.md +++ b/kmac/README.md @@ -16,7 +16,7 @@ The below table summarises the equivalence with other MAC algorithms, where `K` | Existing MAC Algorithm | KMAC Equivalent | |------------------------|------------------------------| -| `AES-KMAC(K, text)` | `KMAC128(K, text, L=128, S)` | +| `AES-CMAC(K, text)` | `KMAC128(K, text, L=128, S)` | | `HMAC-SHA256(K, text)` | `KMAC256(K, text, L=256, S)` | | `HMAC-SHA512(K, text)` | `KMAC256(K, text, L=512, S)` | @@ -63,13 +63,13 @@ let mac_bytes = hex!(" kmac.verify_slice(&mac_bytes).unwrap(); ``` -### Producing a fixed-length output +### Producing a custom-length output -KMAC can also be used to produce an output of any length, which is particularly useful when KMAC is being used as a [key-derivation function (KDF)](https://en.wikipedia.org/wiki/Key_derivation_function). +KMAC can produce an output of any length via `finalize_into_buf`, which is particularly useful when KMAC is being used as a [key-derivation function (KDF)](https://en.wikipedia.org/wiki/Key_derivation_function). -This method finalizes the KMAC and mixes the requested output length into the KMAC domain separation. The resulting bytes are dependent on the exact length of `out`. Use this when the output length is part of the MAC/derivation semantics (for example when the length itself must influence the MAC result). +This method mixes the requested output length into the KMAC domain separation, so the resulting bytes depend on the exact length of `out`. This is distinct from both `finalize()` (which uses the type's default output length) and `finalize_xof()` (KMACXOF, which does not bind output length at all). -A customisation string can also be provided to further domain-separate different uses of KMAC with the same key when initialising the KMAC instance with `new_customization`. +A customisation string can also be provided via `new_customization` to further domain-separate different uses of KMAC with the same key. ```rust use kmac::{Kmac256, Mac}; @@ -87,11 +87,9 @@ let expected = hex!(" assert_eq!(output[..], expected[..]); ``` -### Producing a variable-length output +### KMACXOF: variable-length output -Variable length KMAC output uses the `ExtendableOutput` trait. This is useful when the desired output length is not immediately known, and will append data to a buffer until the desired length is reached. - -The XOF variant finalizes the sponge state without binding the requested output length into the KMAC domain separation. The returned reader yields an effectively infinite stream of bytes; reading the first `N` bytes from the reader (and truncating) produces the same `N`-byte prefix regardless of whether more bytes will be read later. +KMACXOF (defined in Section 4.3.1 of [NIST SP 800-185]) produces an arbitrary-length output via the `ExtendableOutput` trait. Unlike `finalize()` and `finalize_into_buf()`, KMACXOF does not bind the output length into the domain separation — the returned reader yields an effectively infinite stream of bytes, and reading the first `N` bytes produces the same `N`-byte prefix regardless of how many bytes are read in total. ```rust use kmac::{Kmac256, Mac, ExtendableOutput, XofReader}; diff --git a/kmac/benches/mod.rs b/kmac/benches/mod.rs index 9d37e1a..20e081d 100644 --- a/kmac/benches/mod.rs +++ b/kmac/benches/mod.rs @@ -2,48 +2,9 @@ extern crate test; use core::hint::black_box; -use kmac::{KeyInit, Kmac128, Kmac256, Mac}; +use kmac::{KeyInit, Kmac128, Kmac256}; use test::Bencher; -#[macro_export] -macro_rules! bench_full { - ( - $init:expr; - $($name:ident $bs:expr;)* - ) => { - $( - #[bench] - fn $name(b: &mut Bencher) { - let data = [0; $bs]; - - b.iter(|| { - let mut d = $init; - digest::Update::update(&mut d, black_box(&data[..])); - black_box(d.finalize()); - }); - - b.bytes = $bs; - } - )* - }; -} - -bench_full!( - Kmac128::new(black_box(&Default::default())); - kmac128_10 10; - kmac128_100 100; - kmac128_1000 1000; - kmac128_10000 10000; -); - -bench_full!( - Kmac256::new(black_box(&Default::default())); - kmac256_10 10; - kmac256_100 100; - kmac256_1000 1000; - kmac256_10000 10000; -); - digest::bench_update!( Kmac128::new(black_box(&Default::default())); kmac128_update_10 10; diff --git a/kmac/src/encoding.rs b/kmac/src/encoding.rs index 94abfdb..d3f93b7 100644 --- a/kmac/src/encoding.rs +++ b/kmac/src/encoding.rs @@ -25,11 +25,9 @@ pub(crate) fn right_encode(num: u64, buffer: &mut [u8; 9]) -> &[u8] { #[cfg(test)] mod tests { use super::*; - extern crate std; #[test] fn test_num_encoding_size() { - // sha3::block_api::Sha3ReaderCore::::new(&[0; 200]); let test_cases = [ (0, 1), (1, 1), @@ -75,10 +73,17 @@ mod tests { for i in 0..usize::BITS { let x: usize = 1 << i; - let mut want = std::vec![0; 1]; - want.extend(x.to_be_bytes().iter().skip_while(|&&v| v == 0)); - want[0] = (want.len() - 1) as u8; - assert_eq!(left_encode(x as u64, &mut buf), want, "#{x}"); + let be_bytes = x.to_be_bytes(); + let skip = be_bytes + .iter() + .position(|&v| v != 0) + .unwrap_or(be_bytes.len() - 1); + let len = be_bytes.len() - skip; + let mut want = [0u8; 9]; + want[0] = len as u8; + want[1..=len].copy_from_slice(&be_bytes[skip..]); + let total_len = len + 1; + assert_eq!(left_encode(x as u64, &mut buf), &want[..total_len], "#{x}"); } } @@ -93,10 +98,17 @@ mod tests { for i in 0..usize::BITS { let x: usize = 1 << i; - let mut want = - std::vec::Vec::from_iter(x.to_be_bytes().iter().copied().skip_while(|&v| v == 0)); - want.push(want.len() as u8); - assert_eq!(right_encode(x as u64, &mut buf), want, "#{x}"); + let be_bytes = x.to_be_bytes(); + let skip = be_bytes + .iter() + .position(|&v| v != 0) + .unwrap_or(be_bytes.len() - 1); + let len = be_bytes.len() - skip; + let mut want = [0u8; 9]; + want[..len].copy_from_slice(&be_bytes[skip..]); + want[len] = len as u8; + let total_len = len + 1; + assert_eq!(right_encode(x as u64, &mut buf), &want[..total_len], "#{x}"); } } } diff --git a/kmac/src/kmac.rs b/kmac/src/kmac.rs deleted file mode 100644 index 2dd8fb2..0000000 --- a/kmac/src/kmac.rs +++ /dev/null @@ -1,126 +0,0 @@ -use crate::encoding::{left_encode, right_encode}; -use cshake::CShake; -use digest::block_api::BlockSizeUser; -use digest::common::KeySizeUser; -use digest::typenum::Unsigned; -use digest::{ExtendableOutput, InvalidLength, Key, KeyInit, MacMarker, Update, XofReader}; - -/// Trait alias for CShake types usable with KMAC. -pub trait CShakeUser: BlockSizeUser + Update + ExtendableOutput + Clone { - fn new_kmac(customization: &[u8]) -> Self; -} - -impl CShakeUser for cshake::CShake128 { - fn new_kmac(customization: &[u8]) -> Self { - CShake::new_with_function_name(b"KMAC", customization) - } -} - -impl CShakeUser for cshake::CShake256 { - fn new_kmac(customization: &[u8]) -> Self { - CShake::new_with_function_name(b"KMAC", customization) - } -} - -pub struct KmacInner { - cshake: D, -} - -impl Clone for KmacInner { - #[inline(always)] - fn clone(&self) -> Self { - Self { - cshake: self.cshake.clone(), - } - } -} - -impl MacMarker for KmacInner {} - -impl KeySizeUser for KmacInner { - type KeySize = D::BlockSize; -} - -impl BlockSizeUser for KmacInner { - type BlockSize = D::BlockSize; -} - -impl KmacInner { - #[inline(always)] - pub fn new_customization(key: &[u8], customisation: &[u8]) -> Self { - let mut cshake = D::new_kmac(customisation); - let block_size = D::BlockSize::USIZE; - let mut encode_buffer = [0u8; 9]; - - // bytepad: left_encode(w) - let le_w = left_encode(block_size as u64, &mut encode_buffer); - let mut total = le_w.len(); - cshake.update(le_w); - - // encode_string(K): left_encode(8*len(K)) || K - let le_k = left_encode(8 * key.len() as u64, &mut encode_buffer); - total += le_k.len(); - cshake.update(le_k); - - total += key.len(); - cshake.update(key); - - // pad to block boundary - let pad_len = (block_size - (total % block_size)) % block_size; - if pad_len > 0 { - let zeros = [0u8; 168]; // max block size - let mut remaining = pad_len; - while remaining > 0 { - let chunk = core::cmp::min(remaining, zeros.len()); - cshake.update(&zeros[..chunk]); - remaining -= chunk; - } - } - - Self { cshake } - } -} - -impl KeyInit for KmacInner { - #[inline] - fn new(key: &Key) -> Self { - Self::new_customization(key.as_slice(), &[]) - } - - #[inline(always)] - fn new_from_slice(key: &[u8]) -> Result { - Ok(Self::new_customization(key, &[])) - } -} - -impl Update for KmacInner { - #[inline(always)] - fn update(&mut self, data: &[u8]) { - self.cshake.update(data); - } -} - -impl KmacInner { - /// Finalizes the KMAC for any output array size (fixed-length output). - #[inline(always)] - pub fn finalize_fixed_inner(mut self, out: &mut [u8]) { - // right_encode(L), where L = output length in bits - let mut encode_buffer = [0u8; 9]; - let re = right_encode(8 * out.len() as u64, &mut encode_buffer); - self.cshake.update(re); - - let mut reader = self.cshake.finalize_xof(); - reader.read(out); - } - - /// Finalizes the KMAC for extendable output (XOF). - #[inline(always)] - pub fn finalize_xof_inner(mut self) -> D::Reader { - // right_encode(0), as L = 0 for extendable output - let mut encode_buffer = [0u8; 9]; - let re = right_encode(0, &mut encode_buffer); - self.cshake.update(re); - - self.cshake.finalize_xof() - } -} diff --git a/kmac/src/lib.rs b/kmac/src/lib.rs index 84e22d3..fc049ba 100644 --- a/kmac/src/lib.rs +++ b/kmac/src/lib.rs @@ -11,285 +11,227 @@ #![warn(missing_docs)] mod encoding; -mod kmac; -use crate::kmac::KmacInner; -use cshake::{CShake128, CShake256, CShakeReader}; -use digest::consts::{U32, U64, U136, U168}; +use crate::encoding::{left_encode, right_encode}; +use cshake::{CShake, CShakeReader}; +use digest::block_buffer::BlockSizes; +use digest::consts::{U136, U168}; pub use digest::{self, ExtendableOutput, FixedOutput, KeyInit, Mac, XofReader}; use digest::{InvalidLength, MacMarker, Output, OutputSizeUser, Update}; -/// Manually implement the extra KMAC methods and XOF traits. -macro_rules! impl_kmac { - ($kmac:ident, $cshake:ty, $reader:ident, $rate:ident, $output_size:ident) => { - /// KMAC implementation as per Section 4 of [NIST SP 800-185]. - #[derive(Clone)] - pub struct $kmac { - inner: KmacInner<$cshake>, - } - - impl MacMarker for $kmac {} - - impl OutputSizeUser for $kmac { - type OutputSize = $output_size; - } - - impl digest::common::KeySizeUser for $kmac { - type KeySize = as digest::common::KeySizeUser>::KeySize; - } +mod sealed { + use digest::array::ArraySize; + use digest::consts::{U32, U64, U136, U168}; - impl KeyInit for $kmac { - #[inline] - fn new(key: &digest::Key) -> Self { - Self { - inner: KmacInner::<$cshake>::new_customization(key.as_slice(), &[]), - } - } + pub trait KmacParams { + type OutputSize: ArraySize; + } - #[inline(always)] - fn new_from_slice(key: &[u8]) -> Result { - Ok(Self { - inner: KmacInner::<$cshake>::new_customization(key, &[]), - }) - } - } + impl KmacParams for U168 { + type OutputSize = U32; + } - impl Update for $kmac { - #[inline(always)] - fn update(&mut self, data: &[u8]) { - self.inner.update(data); - } - } + impl KmacParams for U136 { + type OutputSize = U64; + } +} - impl FixedOutput for $kmac { - #[inline(always)] - fn finalize_into(self, out: &mut Output) { - self.inner.finalize_fixed_inner(out.as_mut_slice()); - } - } +/// KMAC implementation as per Section 4 of [NIST SP 800-185]. +/// +/// [NIST SP 800-185]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf +#[derive(Clone)] +pub struct Kmac { + cshake: CShake, +} - impl $kmac { - /// Create a new KMAC with the given key and customisation. - /// - /// Section 4.2 of [NIST SP 800-185] specifies that KMAC takes both a key (K) and an - /// optional customisation string (S). - #[inline] - pub fn new_customization( - key: &[u8], - customisation: &[u8], - ) -> Result { - // TODO: KeyInitWithCustomization trait, following KeyInit as new_with_customization and new_from_slice_with_customization. - // TODO: review the Result, as this implementation is infallible. Currently matching KeyInit::new_from_slice. - // FUTURE: support key+customisation initialisation via traits. - Ok(Self { - inner: KmacInner::<$cshake>::new_customization(key, customisation), - }) - } +/// KMAC128: KMAC with 128-bit security strength, as defined in Section 4 of +/// [NIST SP 800-185]. +/// +/// Produces a 32-byte (256-bit) fixed-length output by default via [`Mac::finalize`]. +/// For a custom output length where the length is mixed into the domain separation, +/// use [`Kmac::finalize_into_buf`]. For KMACXOF128 (arbitrary-length XOF output), use +/// [`ExtendableOutput::finalize_xof`]. +/// +/// # Example +/// ``` +/// use kmac::{Kmac128, Mac, KeyInit}; +/// +/// let mut mac = Kmac128::new_from_slice(b"my secret key").unwrap(); +/// mac.update(b"input message"); +/// let result = mac.finalize(); +/// let tag = result.into_bytes(); +/// ``` +/// +/// [NIST SP 800-185]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf +pub type Kmac128 = Kmac; + +/// KMAC256: KMAC with 256-bit security strength, as defined in Section 4 of +/// [NIST SP 800-185]. +/// +/// Produces a 64-byte (512-bit) fixed-length output by default via [`Mac::finalize`]. +/// For a custom output length where the length is mixed into the domain separation, +/// use [`Kmac::finalize_into_buf`]. For KMACXOF256 (arbitrary-length XOF output), use +/// [`ExtendableOutput::finalize_xof`]. +/// +/// # Example +/// ``` +/// use kmac::{Kmac256, Mac, KeyInit}; +/// +/// let mut mac = Kmac256::new_from_slice(b"my secret key").unwrap(); +/// mac.update(b"input message"); +/// let result = mac.finalize(); +/// let tag = result.into_bytes(); +/// ``` +/// +/// [NIST SP 800-185]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf +pub type Kmac256 = Kmac; + +/// KMACXOF128 reader, returned by calling [`ExtendableOutput::finalize_xof`] on [`Kmac128`]. +/// +/// Implements [`XofReader`] to produce an arbitrary-length output stream (KMACXOF128). +pub type Kmac128Reader = CShakeReader; + +/// KMACXOF256 reader, returned by calling [`ExtendableOutput::finalize_xof`] on [`Kmac256`]. +/// +/// Implements [`XofReader`] to produce an arbitrary-length output stream (KMACXOF256). +pub type Kmac256Reader = CShakeReader; + +impl MacMarker for Kmac {} + +impl OutputSizeUser for Kmac { + type OutputSize = ::OutputSize; +} - /// Finalize this KMAC into a fixed-length output buffer, as defined in Section 4.3 - /// (Definition) of [NIST SP 800-185]. - /// - /// This method finalizes the KMAC and *mixes the requested output length into the - /// KMAC domain separation*. That means the resulting bytes are dependent on the - /// exact length of `out`. Use this when the output length is part of the MAC/derivation - /// semantics (for example, when the length itself must influence the MAC result). - /// - /// This is *not* equivalent to calling `finalize_xof()` and then reading `out.len()` - /// bytes from the returned reader; the two approaches produce different outputs. - #[inline] - pub fn finalize_into_buf(self, out: &mut [u8]) { - // TODO: review method naming. - // FUTURE: support custom output sizes via traits. - self.inner.finalize_fixed_inner(out); - } - } +impl digest::common::KeySizeUser for Kmac { + type KeySize = Rate; +} - /// Reader for KMAC that implements the XOF interface. - pub struct $reader { - inner: CShakeReader<$rate>, - } +impl KeyInit for Kmac { + #[inline] + fn new(key: &digest::Key) -> Self { + Self::new_customization_inner(key.as_slice(), &[]) + } - impl XofReader for $reader { - #[inline(always)] - fn read(&mut self, buf: &mut [u8]) -> () { - self.inner.read(buf); - } - } + #[inline(always)] + fn new_from_slice(key: &[u8]) -> Result { + Ok(Self::new_customization_inner(key, &[])) + } +} - impl ExtendableOutput for $kmac { - type Reader = $reader; - - /// Finalize this KMAC to a variable-length (extendable) output stream, as defined in - /// Section 4.3.1 (KMAC with Arbitrary-Length Output) of [NIST SP 800-185]. - /// - /// The XOF variant finalizes the sponge state without binding the requested - /// output length into the KMAC domain separation. The returned reader yields - /// an effectively infinite stream of bytes; reading the first `N` bytes - /// from the reader (and truncating) produces the same `N`-byte prefix - /// regardless of whether more bytes will be read later. - /// - /// Use `finalize_xof()` when you need a stream of arbitrary length (e.g. for - /// KDFs or streaming output). Use `finalize_into()` when the requested output - /// length must influence the MAC result itself. - #[inline(always)] - fn finalize_xof(self) -> Self::Reader { - // FUTURE: support extendable output via a MAC trait? - $reader { - inner: self.inner.finalize_xof_inner(), - } - } - } - }; +impl Update for Kmac { + #[inline(always)] + fn update(&mut self, data: &[u8]) { + self.cshake.update(data); + } } -impl_kmac!(Kmac128, CShake128, Kmac128Reader, U168, U32); -impl_kmac!(Kmac256, CShake256, Kmac256Reader, U136, U64); +impl FixedOutput for Kmac { + #[inline(always)] + fn finalize_into(self, out: &mut Output) { + self.finalize_fixed_inner(out.as_mut_slice()); + } +} -#[cfg(test)] -mod tests { - extern crate std; - use super::{ExtendableOutput, KeyInit, Kmac128, Kmac256, Mac, XofReader}; - use hex_literal::hex; +impl ExtendableOutput for Kmac { + type Reader = CShakeReader; - fn run_kmac128() -> Kmac128 { - let mut mac = Kmac128::new_customization(b"my secret key", b"S") - .expect("Failed to create a KMAC128 instance from key"); - mac.update(b"my message"); - mac + // Finalize as KMACXOF, a variable-length (extendable) output stream, as defined in + // Section 4.3.1 (KMAC with Arbitrary-Length Output) of [NIST SP 800-185]. + #[inline(always)] + fn finalize_xof(self) -> Self::Reader { + self.finalize_xof_inner() } +} - fn run_kmac256() -> Kmac256 { - let mut mac = Kmac256::new_customization(b"my secret key", b"S") - .expect("Failed to create a KMAC256 instance from key"); - mac.update(b"my message"); - mac +impl Kmac { + /// Create a new KMAC with the given key and customisation. + /// + /// Section 4.2 of [NIST SP 800-185] specifies that KMAC takes both a key (K) and an + /// optional customisation string (S). + #[inline] + pub fn new_customization(key: &[u8], customisation: &[u8]) -> Result { + Ok(Self::new_customization_inner(key, customisation)) } - #[test] - #[rustfmt::skip] - fn test_kmac128() { - let out_default = run_kmac128().finalize(); - assert_eq!(out_default.as_bytes().as_slice(), &[248, 117, 251, 104, 105, 74, 192, 171, 41, 119, 90, 145, 137, 1, 243, 168, 28, 139, 94, 23, 113, 176, 36, 194, 10, 9, 40, 209, 193, 167, 181, 254]); - - // confirm finalize_into_buf works the same way - let mut out_into = [0u8; 32]; - run_kmac128().finalize_into_buf(&mut out_into); - assert_eq!(out_default.as_bytes().as_slice(), &out_into); - - // confirm finalize_into_buf does not compute subsets - let mut out_into_subset = [0u8; 16]; - run_kmac128().finalize_into_buf(&mut out_into_subset); - assert_ne!(&out_into_subset, &out_into[..16]); - - // confirm xof is different - let mut reader_xof = run_kmac128().finalize_xof(); - let mut out_xof = [0u8; 32]; - reader_xof.read(&mut out_xof); - assert_ne!(out_xof, out_default.as_bytes().as_slice()); - assert_eq!(&out_xof, &[71, 56, 26, 111, 123, 15, 120, 166, 36, 250, 143, 80, 116, 63, 206, 89, 113, 96, 83, 169, 87, 200, 233, 11, 202, 145, 90, 196, 108, 24, 82, 103]); - - // confirm xof is subset - let mut reader_xof_subset = run_kmac128().finalize_xof(); - let mut out_xof_subset = [0u8; 16]; - reader_xof_subset.read(&mut out_xof_subset); - assert_eq!(&out_xof[..16], &out_xof_subset); + /// Finalize this KMAC into a fixed-length output buffer, as defined in Section 4.3 + /// (Definition) of [NIST SP 800-185]. + /// + /// This method finalizes the KMAC and *mixes the requested output length into the + /// KMAC domain separation*. That means the resulting bytes are dependent on the + /// exact length of `out`. Use this when the output length is part of the MAC/derivation + /// semantics (for example, when the length itself must influence the MAC result). + /// + /// This is *not* equivalent to calling `finalize_xof()` and then reading `out.len()` + /// bytes from the returned reader; the two approaches produce different outputs. + /// + /// # Example + /// ``` + /// use kmac::{Kmac256, Mac}; + /// + /// let mut mac = Kmac256::new_customization(b"my key", b"my customization").unwrap(); + /// mac.update(b"input message"); + /// let mut output = [0u8; 48]; + /// mac.finalize_into_buf(&mut output); + /// ``` + #[inline] + pub fn finalize_into_buf(self, out: &mut [u8]) { + self.finalize_fixed_inner(out); } - #[test] - #[rustfmt::skip] - fn test_kmac256() { - let out_default = run_kmac256().finalize(); - assert_eq!(out_default.as_bytes().as_slice(), &[158, 175, 254, 101, 124, 16, 93, 198, 176, 54, 249, 78, 167, 112, 206, 159, 229, 55, 225, 168, 71, 228, 28, 222, 195, 148, 255, 241, 196, 172, 37, 60, 135, 67, 155, 134, 43, 61, 215, 243, 128, 55, 227, 169, 175, 22, 14, 132, 174, 63, 69, 60, 50, 41, 88, 148, 11, 41, 9, 90, 0, 87, 143, 131]); - - // confirm finalize_into_buf works the same way - let mut out_into = [0u8; 64]; - run_kmac256().finalize_into_buf(&mut out_into); - assert_eq!(out_default.as_bytes().as_slice(), &out_into); - - // confirm finalize_into_buf does not compute subsets - let mut out_into_subset = [0u8; 32]; - run_kmac256().finalize_into_buf(&mut out_into_subset); - assert_ne!(&out_into_subset, &out_into[..32]); - - // confirm xof is different - let mut reader_xof = run_kmac256().finalize_xof(); - let mut out_xof = [0u8; 64]; - reader_xof.read(&mut out_xof); - assert_ne!(out_xof, out_default.as_bytes().as_slice()); - assert_eq!(&out_xof, &[37, 85, 107, 43, 116, 204, 145, 99, 161, 150, 174, 110, 206, 240, 129, 44, 64, 135, 52, 83, 20, 250, 101, 166, 99, 189, 129, 61, 204, 210, 197, 150, 17, 43, 99, 218, 159, 87, 85, 155, 240, 197, 115, 97, 209, 145, 228, 236, 86, 104, 143, 194, 191, 69, 226, 206, 173, 224, 226, 25, 10, 13, 195, 252]); - - // confirm xof is subset - let mut reader_xof_subset = run_kmac256().finalize_xof(); - let mut out_xof_subset = [0u8; 32]; - reader_xof_subset.read(&mut out_xof_subset); - assert_eq!(&out_xof[..32], &out_xof_subset); - } + #[inline(always)] + fn new_customization_inner(key: &[u8], customisation: &[u8]) -> Self { + let mut cshake = CShake::::new_with_function_name(b"KMAC", customisation); + let block_size = Rate::USIZE; + let mut encode_buffer = [0u8; 9]; + + // bytepad: left_encode(w) + let le_w = left_encode(block_size as u64, &mut encode_buffer); + let mut total = le_w.len(); + cshake.update(le_w); + + // encode_string(K): left_encode(8*len(K)) || K + let le_k = left_encode(8 * key.len() as u64, &mut encode_buffer); + total += le_k.len(); + cshake.update(le_k); + + total += key.len(); + cshake.update(key); + + // pad to block boundary + let pad_len = (block_size - (total % block_size)) % block_size; + if pad_len > 0 { + let zeros = [0u8; 168]; // max block size + let mut remaining = pad_len; + while remaining > 0 { + let chunk = core::cmp::min(remaining, zeros.len()); + cshake.update(&zeros[..chunk]); + remaining -= chunk; + } + } - #[test] - fn test_readme_example_verify() { - let mut mac = Kmac128::new_from_slice(b"key material").unwrap(); - mac.update(b"input message"); - let result = mac.finalize(); - let code_bytes = result.into_bytes(); - let expected = hex!( - " - c39a8f614f8821443599440df5402787 - 0f67e4c47919061584f14a616f3efcf5 - " - ); - assert_eq!( - code_bytes[..], - expected[..], - "Expected hex output is {}", - hex::encode(code_bytes) - ); - - let mut mac = Kmac128::new_from_slice(b"key material").unwrap(); - mac.update(b"input message"); - mac.verify_slice(&expected).unwrap(); + Self { cshake } } - #[test] - fn test_readme_example_into() { - let mut mac = Kmac256::new_customization(b"key material", b"customization").unwrap(); - mac.update(b"input message"); - let mut output = [0u8; 32]; - mac.finalize_into_buf(&mut output); - - let expected = hex!( - " - 85fb77da3a35e4c4b0057c3151e6cc54 - ee401ffe65ec2f0239f439be8896f7b6 - " - ); - assert_eq!( - output[..], - expected[..], - "Expected hex output is {}", - hex::encode(output) - ); + /// Finalizes the KMAC for any output array size (fixed-length output). + #[inline(always)] + fn finalize_fixed_inner(mut self, out: &mut [u8]) { + // right_encode(L), where L = output length in bits + let mut encode_buffer = [0u8; 9]; + let re = right_encode(8 * out.len() as u64, &mut encode_buffer); + self.cshake.update(re); + + let mut reader = self.cshake.finalize_xof(); + reader.read(out); } - #[test] - fn test_readme_example_xof() { - let mut mac = Kmac256::new_customization(b"key material", b"customization").unwrap(); - mac.update(b"input message"); - let mut reader = mac.finalize_xof(); - - let mut output = [0u8; 32]; - reader.read(&mut output); - - let expected = hex!( - " - b675b75668eab0706ab05650f34fa1b6 - 24051a9a42b5e42cfe9970e8f903d45b - " - ); - assert_eq!( - output[..], - expected[..], - "Expected hex output is {}", - hex::encode(output) - ); + /// Finalizes the KMAC for extendable output (XOF). + #[inline(always)] + fn finalize_xof_inner(mut self) -> CShakeReader { + // right_encode(0), as L = 0 for extendable output + let mut encode_buffer = [0u8; 9]; + let re = right_encode(0, &mut encode_buffer); + self.cshake.update(re); + + self.cshake.finalize_xof() } } diff --git a/kmac/tests/kmac.rs b/kmac/tests/kmac.rs new file mode 100644 index 0000000..73fcefe --- /dev/null +++ b/kmac/tests/kmac.rs @@ -0,0 +1,162 @@ +use hex_literal::hex; +use kmac::{ExtendableOutput, KeyInit, Kmac128, Kmac256, Mac, XofReader}; + +fn run_kmac128() -> Kmac128 { + let mut mac = Kmac128::new_customization(b"my secret key", b"S") + .expect("Failed to create a KMAC128 instance from key"); + mac.update(b"my message"); + mac +} + +fn run_kmac256() -> Kmac256 { + let mut mac = Kmac256::new_customization(b"my secret key", b"S") + .expect("Failed to create a KMAC256 instance from key"); + mac.update(b"my message"); + mac +} + +#[test] +fn test_kmac128() { + let out_default = run_kmac128().finalize(); + assert_eq!( + out_default.as_bytes().as_slice(), + hex!("f875fb68694ac0ab29775a918901f3a81c8b5e1771b024c20a0928d1c1a7b5fe"), + "Expected hex output is {}", + hex::encode(out_default.as_bytes().as_slice()) + ); + + // confirm finalize_into_buf works the same way + let mut out_into = [0u8; 32]; + run_kmac128().finalize_into_buf(&mut out_into); + assert_eq!(out_default.as_bytes().as_slice(), &out_into); + + // confirm finalize_into_buf does not compute subsets + let mut out_into_subset = [0u8; 16]; + run_kmac128().finalize_into_buf(&mut out_into_subset); + assert_ne!(&out_into_subset, &out_into[..16]); + + // confirm xof is different + let mut reader_xof = run_kmac128().finalize_xof(); + let mut out_xof = [0u8; 32]; + reader_xof.read(&mut out_xof); + assert_ne!(out_xof, out_default.as_bytes().as_slice()); + assert_eq!( + &out_xof, + &hex!("47381a6f7b0f78a624fa8f50743fce59716053a957c8e90bca915ac46c185267"), + "Expected hex output is {}", + hex::encode(out_xof) + ); + + // confirm xof is subset + let mut reader_xof_subset = run_kmac128().finalize_xof(); + let mut out_xof_subset = [0u8; 16]; + reader_xof_subset.read(&mut out_xof_subset); + assert_eq!(&out_xof[..16], &out_xof_subset); +} + +#[test] +fn test_kmac256() { + let out_default = run_kmac256().finalize(); + assert_eq!( + out_default.as_bytes().as_slice(), + hex!( + "9eaffe657c105dc6b036f94ea770ce9fe537e1a847e41cdec394fff1c4ac253c" + "87439b862b3dd7f38037e3a9af160e84ae3f453c322958940b29095a00578f83" + ), + "Expected hex output is {}", + hex::encode(out_default.as_bytes().as_slice()) + ); + + // confirm finalize_into_buf works the same way + let mut out_into = [0u8; 64]; + run_kmac256().finalize_into_buf(&mut out_into); + assert_eq!(out_default.as_bytes().as_slice(), &out_into); + + // confirm finalize_into_buf does not compute subsets + let mut out_into_subset = [0u8; 32]; + run_kmac256().finalize_into_buf(&mut out_into_subset); + assert_ne!(&out_into_subset, &out_into[..32]); + + // confirm xof is different + let mut reader_xof = run_kmac256().finalize_xof(); + let mut out_xof = [0u8; 64]; + reader_xof.read(&mut out_xof); + assert_ne!(out_xof, out_default.as_bytes().as_slice()); + assert_eq!( + &out_xof, + &hex!( + "25556b2b74cc9163a196ae6ecef0812c4087345314fa65a663bd813dccd2c596" + "112b63da9f57559bf0c57361d191e4ec56688fc2bf45e2ceade0e2190a0dc3fc" + ), + "Expected hex output is {}", + hex::encode(out_xof) + ); + + // confirm xof is subset + let mut reader_xof_subset = run_kmac256().finalize_xof(); + let mut out_xof_subset = [0u8; 32]; + reader_xof_subset.read(&mut out_xof_subset); + assert_eq!(&out_xof[..32], &out_xof_subset); +} + +#[test] +fn test_readme_example_verify() { + let mut mac = Kmac128::new_from_slice(b"key material").unwrap(); + mac.update(b"input message"); + let result = mac.finalize(); + let code_bytes = result.into_bytes(); + let expected = hex!( + "c39a8f614f8821443599440df5402787" + "0f67e4c47919061584f14a616f3efcf5" + ); + assert_eq!( + code_bytes[..], + expected[..], + "Expected hex output is {}", + hex::encode(code_bytes) + ); + + let mut mac = Kmac128::new_from_slice(b"key material").unwrap(); + mac.update(b"input message"); + mac.verify_slice(&expected).unwrap(); +} + +#[test] +fn test_readme_example_into() { + let mut mac = Kmac256::new_customization(b"key material", b"customization").unwrap(); + mac.update(b"input message"); + let mut output = [0u8; 32]; + mac.finalize_into_buf(&mut output); + + let expected = hex!( + "85fb77da3a35e4c4b0057c3151e6cc54" + "ee401ffe65ec2f0239f439be8896f7b6" + ); + assert_eq!( + output[..], + expected[..], + "Expected hex output is {}", + hex::encode(output) + ); +} + +#[test] +fn test_readme_example_xof() { + let mut mac = Kmac256::new_customization(b"key material", b"customization").unwrap(); + mac.update(b"input message"); + let mut reader = mac.finalize_xof(); + + let mut output = [0u8; 32]; + reader.read(&mut output); + + let expected = hex!( + "b675b75668eab0706ab05650f34fa1b6" + "24051a9a42b5e42cfe9970e8f903d45b" + ); + assert_eq!( + output[..], + expected[..], + "Expected hex output is {}", + hex::encode(output) + ); +} diff --git a/kmac/tests/nist.rs b/kmac/tests/nist.rs index 2cb0fa2..cdb20ed 100644 --- a/kmac/tests/nist.rs +++ b/kmac/tests/nist.rs @@ -2,340 +2,222 @@ //! - https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/KMAC_samples.pdf //! - https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/KMACXOF_samples.pdf -#![allow(clippy::type_complexity)] - +use hex_literal::hex; use kmac::{ExtendableOutput, Kmac128, Kmac256, Mac, XofReader}; +struct NistVector { + key: &'static [u8], + data: &'static [u8], + customization: &'static [u8], + output: &'static [u8], +} + +// These same key and data fields are used throughout the test vectors. +const KEY: [u8; 32] = hex!( + "404142434445464748494A4B4C4D4E4F" + "505152535455565758595A5B5C5D5E5F" +); +const DATA_SHORT: [u8; 4] = hex!("00010203"); +const DATA_LONG: [u8; 200] = hex!( + "000102030405060708090A0B0C0D0E0F" + "101112131415161718191A1B1C1D1E1F" + "202122232425262728292A2B2C2D2E2F" + "303132333435363738393A3B3C3D3E3F" + "404142434445464748494A4B4C4D4E4F" + "505152535455565758595A5B5C5D5E5F" + "606162636465666768696A6B6C6D6E6F" + "707172737475767778797A7B7C7D7E7F" + "808182838485868788898A8B8C8D8E8F" + "909192939495969798999A9B9C9D9E9F" + "A0A1A2A3A4A5A6A7A8A9AAABACADAEAF" + "B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF" + "C0C1C2C3C4C5C6C7" +); + #[test] fn test_kmac128() { - let vectors: &[(&[u8], &[u8], &[u8], &[u8])] = &[ - ( - &[ - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, - 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, - 0x5C, 0x5D, 0x5E, 0x5F, - ], - &[0x00, 0x01, 0x02, 0x03], - &[], - &[ - 0xE5, 0x78, 0x0B, 0x0D, 0x3E, 0xA6, 0xF7, 0xD3, 0xA4, 0x29, 0xC5, 0x70, 0x6A, 0xA4, - 0x3A, 0x00, 0xFA, 0xDB, 0xD7, 0xD4, 0x96, 0x28, 0x83, 0x9E, 0x31, 0x87, 0x24, 0x3F, - 0x45, 0x6E, 0xE1, 0x4E, - ], - ), - ( - &[ - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, - 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, - 0x5C, 0x5D, 0x5E, 0x5F, - ], - &[0x00, 0x01, 0x02, 0x03], - b"My Tagged Application", - &[ - 0x3B, 0x1F, 0xBA, 0x96, 0x3C, 0xD8, 0xB0, 0xB5, 0x9E, 0x8C, 0x1A, 0x6D, 0x71, 0x88, - 0x8B, 0x71, 0x43, 0x65, 0x1A, 0xF8, 0xBA, 0x0A, 0x70, 0x70, 0xC0, 0x97, 0x9E, 0x28, - 0x11, 0x32, 0x4A, 0xA5, - ], - ), - ( - &[ - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, - 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, - 0x5C, 0x5D, 0x5E, 0x5F, - ], - &[ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, - 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, - 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, - 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, - 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, - 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, - 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, - 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, - 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, - 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, - 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, - 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, - 0xC4, 0xC5, 0xC6, 0xC7, - ], - b"My Tagged Application", - &[ - 0x1F, 0x5B, 0x4E, 0x6C, 0xCA, 0x02, 0x20, 0x9E, 0x0D, 0xCB, 0x5C, 0xA6, 0x35, 0xB8, - 0x9A, 0x15, 0xE2, 0x71, 0xEC, 0xC7, 0x60, 0x07, 0x1D, 0xFD, 0x80, 0x5F, 0xAA, 0x38, - 0xF9, 0x72, 0x92, 0x30, - ], - ), + let vectors: &[NistVector] = &[ + // KMAC Sample #1 + NistVector { + key: &KEY, + data: &DATA_SHORT, + customization: &[], + output: &hex!( + "E5780B0D3EA6F7D3A429C5706AA43A00" + "FADBD7D49628839E3187243F456EE14E" + ), + }, + // KMAC Sample #2 + NistVector { + key: &KEY, + data: &DATA_SHORT, + customization: b"My Tagged Application", + output: &hex!( + "3B1FBA963CD8B0B59E8C1A6D71888B71" + "43651AF8BA0A7070C0979E2811324AA5" + ), + }, + // KMAC Sample #3 + NistVector { + key: &KEY, + data: &DATA_LONG, + customization: b"My Tagged Application", + output: &hex!( + "1F5B4E6CCA02209E0DCB5CA635B89A15" + "E271ECC760071DFD805FAA38F9729230" + ), + }, ]; - for (i, &(key, data, s, expected)) in vectors.iter().enumerate() { - let mut hash = Kmac128::new_customization(key, s).unwrap(); - hash.update(data); + for (i, v) in vectors.iter().enumerate() { + let mut hash = Kmac128::new_customization(v.key, v.customization).unwrap(); + hash.update(v.data); let result = hash.finalize(); - assert_eq!(result.as_bytes().as_slice().len(), expected.len(), "#{i}"); - assert_eq!(result.as_bytes().as_slice(), expected, "#{i}"); + assert_eq!(result.as_bytes().as_slice().len(), v.output.len(), "#{i}"); + assert_eq!(result.as_bytes().as_slice(), v.output, "#{i}"); } } #[test] fn test_kmacxof128() { - let vectors: &[(&[u8], &[u8], &[u8], &[u8])] = &[ - ( - &[ - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, - 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, - 0x5C, 0x5D, 0x5E, 0x5F, - ], - &[0x00, 0x01, 0x02, 0x03], - &[], - &[ - 0xCD, 0x83, 0x74, 0x0B, 0xBD, 0x92, 0xCC, 0xC8, 0xCF, 0x03, 0x2B, 0x14, 0x81, 0xA0, - 0xF4, 0x46, 0x0E, 0x7C, 0xA9, 0xDD, 0x12, 0xB0, 0x8A, 0x0C, 0x40, 0x31, 0x17, 0x8B, - 0xAC, 0xD6, 0xEC, 0x35, - ], - ), - ( - &[ - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, - 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, - 0x5C, 0x5D, 0x5E, 0x5F, - ], - &[0x00, 0x01, 0x02, 0x03], - b"My Tagged Application", - &[ - 0x31, 0xA4, 0x45, 0x27, 0xB4, 0xED, 0x9F, 0x5C, 0x61, 0x01, 0xD1, 0x1D, 0xE6, 0xD2, - 0x6F, 0x06, 0x20, 0xAA, 0x5C, 0x34, 0x1D, 0xEF, 0x41, 0x29, 0x96, 0x57, 0xFE, 0x9D, - 0xF1, 0xA3, 0xB1, 0x6C, - ], - ), - ( - &[ - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, - 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, - 0x5C, 0x5D, 0x5E, 0x5F, - ], - &[ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, - 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, - 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, - 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, - 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, - 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, - 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, - 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, - 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, - 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, - 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, - 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, - 0xC4, 0xC5, 0xC6, 0xC7, - ], - b"My Tagged Application", - &[ - 0x47, 0x02, 0x6C, 0x7C, 0xD7, 0x93, 0x08, 0x4A, 0xA0, 0x28, 0x3C, 0x25, 0x3E, 0xF6, - 0x58, 0x49, 0x0C, 0x0D, 0xB6, 0x14, 0x38, 0xB8, 0x32, 0x6F, 0xE9, 0xBD, 0xDF, 0x28, - 0x1B, 0x83, 0xAE, 0x0F, - ], - ), + let vectors: &[NistVector] = &[ + // KMACXOF Sample #1 + NistVector { + key: &KEY, + data: &DATA_SHORT, + customization: &[], + output: &hex!( + "CD83740BBD92CCC8CF032B1481A0F446" + "0E7CA9DD12B08A0C4031178BACD6EC35" + ), + }, + // KMACXOF Sample #2 + NistVector { + key: &KEY, + data: &DATA_SHORT, + customization: b"My Tagged Application", + output: &hex!( + "31A44527B4ED9F5C6101D11DE6D26F06" + "20AA5C341DEF41299657FE9DF1A3B16C"), + }, + // KMACXOF Sample #3 + NistVector { + key: &KEY, + data: &DATA_LONG, + customization: b"My Tagged Application", + output: &hex!( + "47026C7CD793084AA0283C253EF65849" + "0C0DB61438B8326FE9BDDF281B83AE0F"), + }, ]; - for (i, &(key, data, s, expected)) in vectors.iter().enumerate() { - let mut hash = Kmac128::new_customization(key, s).unwrap(); - hash.update(data); + for (i, v) in vectors.iter().enumerate() { + let mut hash = Kmac128::new_customization(v.key, v.customization).unwrap(); + hash.update(v.data); let mut reader = hash.finalize_xof(); let mut result = [0u8; 32]; reader.read(&mut result); - assert_eq!(result.as_slice().len(), expected.len(), "#{i}"); - assert_eq!(result.as_slice(), expected, "#{i}"); + assert_eq!(result.as_slice().len(), v.output.len(), "#{i}"); + assert_eq!(result.as_slice(), v.output, "#{i}"); } } #[test] fn test_kmac256() { - let vectors: &[(&[u8], &[u8], &[u8], &[u8])] = &[ - ( - &[ - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, - 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, - 0x5C, 0x5D, 0x5E, 0x5F, - ], - &[0x00, 0x01, 0x02, 0x03], - b"My Tagged Application", - &[ - 0x20, 0xC5, 0x70, 0xC3, 0x13, 0x46, 0xF7, 0x03, 0xC9, 0xAC, 0x36, 0xC6, 0x1C, 0x03, - 0xCB, 0x64, 0xC3, 0x97, 0x0D, 0x0C, 0xFC, 0x78, 0x7E, 0x9B, 0x79, 0x59, 0x9D, 0x27, - 0x3A, 0x68, 0xD2, 0xF7, 0xF6, 0x9D, 0x4C, 0xC3, 0xDE, 0x9D, 0x10, 0x4A, 0x35, 0x16, - 0x89, 0xF2, 0x7C, 0xF6, 0xF5, 0x95, 0x1F, 0x01, 0x03, 0xF3, 0x3F, 0x4F, 0x24, 0x87, - 0x10, 0x24, 0xD9, 0xC2, 0x77, 0x73, 0xA8, 0xDD, - ], - ), - ( - &[ - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, - 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, - 0x5C, 0x5D, 0x5E, 0x5F, - ], - &[ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, - 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, - 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, - 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, - 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, - 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, - 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, - 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, - 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, - 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, - 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, - 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, - 0xC4, 0xC5, 0xC6, 0xC7, - ], - &[], - &[ - 0x75, 0x35, 0x8C, 0xF3, 0x9E, 0x41, 0x49, 0x4E, 0x94, 0x97, 0x07, 0x92, 0x7C, 0xEE, - 0x0A, 0xF2, 0x0A, 0x3F, 0xF5, 0x53, 0x90, 0x4C, 0x86, 0xB0, 0x8F, 0x21, 0xCC, 0x41, - 0x4B, 0xCF, 0xD6, 0x91, 0x58, 0x9D, 0x27, 0xCF, 0x5E, 0x15, 0x36, 0x9C, 0xBB, 0xFF, - 0x8B, 0x9A, 0x4C, 0x2E, 0xB1, 0x78, 0x00, 0x85, 0x5D, 0x02, 0x35, 0xFF, 0x63, 0x5D, - 0xA8, 0x25, 0x33, 0xEC, 0x6B, 0x75, 0x9B, 0x69, - ], - ), - ( - &[ - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, - 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, - 0x5C, 0x5D, 0x5E, 0x5F, - ], - &[ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, - 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, - 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, - 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, - 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, - 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, - 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, - 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, - 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, - 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, - 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, - 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, - 0xC4, 0xC5, 0xC6, 0xC7, - ], - b"My Tagged Application", - &[ - 0xB5, 0x86, 0x18, 0xF7, 0x1F, 0x92, 0xE1, 0xD5, 0x6C, 0x1B, 0x8C, 0x55, 0xDD, 0xD7, - 0xCD, 0x18, 0x8B, 0x97, 0xB4, 0xCA, 0x4D, 0x99, 0x83, 0x1E, 0xB2, 0x69, 0x9A, 0x83, - 0x7D, 0xA2, 0xE4, 0xD9, 0x70, 0xFB, 0xAC, 0xFD, 0xE5, 0x00, 0x33, 0xAE, 0xA5, 0x85, - 0xF1, 0xA2, 0x70, 0x85, 0x10, 0xC3, 0x2D, 0x07, 0x88, 0x08, 0x01, 0xBD, 0x18, 0x28, - 0x98, 0xFE, 0x47, 0x68, 0x76, 0xFC, 0x89, 0x65, - ], - ), + let vectors: &[NistVector] = &[ + // KMAC Sample #4 + NistVector { + key: &KEY, + data: &DATA_SHORT, + customization: b"My Tagged Application", + output: &hex!( + "20C570C31346F703C9AC36C61C03CB64" + "C3970D0CFC787E9B79599D273A68D2F7" + "F69D4CC3DE9D104A351689F27CF6F595" + "1F0103F33F4F24871024D9C27773A8DD" + ), + }, + // KMAC Sample #5 + NistVector { + key: &KEY, + data: &DATA_LONG, + customization: &[], + output: &hex!( + "75358CF39E41494E949707927CEE0AF2" + "0A3FF553904C86B08F21CC414BCFD691" + "589D27CF5E15369CBBFF8B9A4C2EB178" + "00855D0235FF635DA82533EC6B759B69" + ), + }, + // KMAC Sample #6 + NistVector { + key: &KEY, + data: &DATA_LONG, + customization: b"My Tagged Application", + output: &hex!( + "B58618F71F92E1D56C1B8C55DDD7CD18" + "8B97B4CA4D99831EB2699A837DA2E4D9" + "70FBACFDE50033AEA585F1A2708510C3" + "2D07880801BD182898FE476876FC8965" + ), + }, ]; - for (i, &(key, data, s, expected)) in vectors.iter().enumerate() { - let mut hash = Kmac256::new_customization(key, s).unwrap(); - hash.update(data); + for (i, v) in vectors.iter().enumerate() { + let mut hash = Kmac256::new_customization(v.key, v.customization).unwrap(); + hash.update(v.data); let result = hash.finalize(); - assert_eq!(result.as_bytes().as_slice().len(), expected.len(), "#{i}"); - assert_eq!(result.as_bytes().as_slice(), expected, "#{i}"); + assert_eq!(result.as_bytes().as_slice().len(), v.output.len(), "#{i}"); + assert_eq!(result.as_bytes().as_slice(), v.output, "#{i}"); } } #[test] fn test_kmacxof256() { - let vectors: &[(&[u8], &[u8], &[u8], &[u8])] = &[ - ( - &[ - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, - 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, - 0x5C, 0x5D, 0x5E, 0x5F, - ], - &[0x00, 0x01, 0x02, 0x03], - b"My Tagged Application", - &[ - 0x17, 0x55, 0x13, 0x3F, 0x15, 0x34, 0x75, 0x2A, 0xAD, 0x07, 0x48, 0xF2, 0xC7, 0x06, - 0xFB, 0x5C, 0x78, 0x45, 0x12, 0xCA, 0xB8, 0x35, 0xCD, 0x15, 0x67, 0x6B, 0x16, 0xC0, - 0xC6, 0x64, 0x7F, 0xA9, 0x6F, 0xAA, 0x7A, 0xF6, 0x34, 0xA0, 0xBF, 0x8F, 0xF6, 0xDF, - 0x39, 0x37, 0x4F, 0xA0, 0x0F, 0xAD, 0x9A, 0x39, 0xE3, 0x22, 0xA7, 0xC9, 0x20, 0x65, - 0xA6, 0x4E, 0xB1, 0xFB, 0x08, 0x01, 0xEB, 0x2B, - ], - ), - ( - &[ - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, - 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, - 0x5C, 0x5D, 0x5E, 0x5F, - ], - &[ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, - 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, - 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, - 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, - 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, - 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, - 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, - 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, - 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, - 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, - 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, - 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, - 0xC4, 0xC5, 0xC6, 0xC7, - ], - &[], - &[ - 0xFF, 0x7B, 0x17, 0x1F, 0x1E, 0x8A, 0x2B, 0x24, 0x68, 0x3E, 0xED, 0x37, 0x83, 0x0E, - 0xE7, 0x97, 0x53, 0x8B, 0xA8, 0xDC, 0x56, 0x3F, 0x6D, 0xA1, 0xE6, 0x67, 0x39, 0x1A, - 0x75, 0xED, 0xC0, 0x2C, 0xA6, 0x33, 0x07, 0x9F, 0x81, 0xCE, 0x12, 0xA2, 0x5F, 0x45, - 0x61, 0x5E, 0xC8, 0x99, 0x72, 0x03, 0x1D, 0x18, 0x33, 0x73, 0x31, 0xD2, 0x4C, 0xEB, - 0x8F, 0x8C, 0xA8, 0xE6, 0xA1, 0x9F, 0xD9, 0x8B, - ], - ), - ( - &[ - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, - 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, - 0x5C, 0x5D, 0x5E, 0x5F, - ], - &[ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, - 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, - 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, - 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, - 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, - 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, - 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, - 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, - 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, - 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, - 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, - 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, - 0xC4, 0xC5, 0xC6, 0xC7, - ], - b"My Tagged Application", - &[ - 0xD5, 0xBE, 0x73, 0x1C, 0x95, 0x4E, 0xD7, 0x73, 0x28, 0x46, 0xBB, 0x59, 0xDB, 0xE3, - 0xA8, 0xE3, 0x0F, 0x83, 0xE7, 0x7A, 0x4B, 0xFF, 0x44, 0x59, 0xF2, 0xF1, 0xC2, 0xB4, - 0xEC, 0xEB, 0xB8, 0xCE, 0x67, 0xBA, 0x01, 0xC6, 0x2E, 0x8A, 0xB8, 0x57, 0x8D, 0x2D, - 0x49, 0x9B, 0xD1, 0xBB, 0x27, 0x67, 0x68, 0x78, 0x11, 0x90, 0x02, 0x0A, 0x30, 0x6A, - 0x97, 0xDE, 0x28, 0x1D, 0xCC, 0x30, 0x30, 0x5D, - ], - ), + let vectors: &[NistVector] = &[ + // KMACXOF Sample #4 + NistVector { + key: &KEY, + data: &DATA_SHORT, + customization: b"My Tagged Application", + output: &hex!( + "1755133F1534752AAD0748F2C706FB5C" + "784512CAB835CD15676B16C0C6647FA9" + "6FAA7AF634A0BF8FF6DF39374FA00FAD" + "9A39E322A7C92065A64EB1FB0801EB2B" + ), + }, + // KMACXOF Sample #5 + NistVector { + key: &KEY, + data: &DATA_LONG, + customization: &[], + output: &hex!( + "FF7B171F1E8A2B24683EED37830EE797" + "538BA8DC563F6DA1E667391A75EDC02C" + "A633079F81CE12A25F45615EC8997203" + "1D18337331D24CEB8F8CA8E6A19FD98B" + ), + }, + // KMACXOF Sample #6 + NistVector { + key: &KEY, + data: &DATA_LONG, + customization: b"My Tagged Application", + output: &hex!( + "D5BE731C954ED7732846BB59DBE3A8E3" + "0F83E77A4BFF4459F2F1C2B4ECEBB8CE" + "67BA01C62E8AB8578D2D499BD1BB2767" + "68781190020A306A97DE281DCC30305D" + ), + }, ]; - for (i, &(key, data, s, expected)) in vectors.iter().enumerate() { - let mut hash = Kmac256::new_customization(key, s).unwrap(); - hash.update(data); + for (i, v) in vectors.iter().enumerate() { + let mut hash = Kmac256::new_customization(v.key, v.customization).unwrap(); + hash.update(v.data); let mut reader = hash.finalize_xof(); let mut result = [0u8; 64]; reader.read(&mut result); - assert_eq!(result.as_slice().len(), expected.len(), "#{i}"); - assert_eq!(result.as_slice(), expected, "#{i}"); + assert_eq!(result.as_slice().len(), v.output.len(), "#{i}"); + assert_eq!(result.as_slice(), v.output, "#{i}"); } }