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..4a61f60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,6 +91,12 @@ dependencies = [ "magma", ] +[[package]] +name = "const-oid" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dabb6555f92fb9ee4140454eb5dcd14c7960e1225c6d1a6cc361f032947713e" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -135,16 +141,23 @@ checksum = "dac89f8a64533a9b0eaa73a68e424db0fb1fd6271c74cc0125336a05f090568d" dependencies = [ "blobby", "block-buffer", + "const-oid", "crypto-common", "subtle", "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" @@ -177,6 +190,25 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "keccak" +version = "0.2.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d546793a04a1d3049bd192856f804cfe96356e2cf36b54b4e575155babe9f41" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "kmac" +version = "0.1.0" +dependencies = [ + "digest", + "hex", + "hex-literal", + "sha3", +] + [[package]] name = "kuznyechik" version = "0.9.0-rc.1" @@ -255,6 +287,16 @@ dependencies = [ "digest", ] +[[package]] +name = "sha3" +version = "0.11.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2103ca0e6f4e9505eae906de5e5883e06fc3b2232fb5d6914890c7bbcb62f478" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "streebog" version = "0.11.0-rc.2" 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..4dad3f6 --- /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::crypto_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..8fcb41a --- /dev/null +++ b/kmac/src/traits.rs @@ -0,0 +1,50 @@ +use digest::HashMarker; +use digest::block_api::{ + BlockSizeUser, BufferKindUser, CoreProxy, Eager, ExtendableOutputCore, 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: BlockSizeUser { + /// Block-level core type of the hash. + type Core: HashMarker + + CShake + + UpdateCore + + ExtendableOutputCore + + BlockSizeUser::BlockSize> + + BufferKindUser + + Default + + Clone; +} + +impl EagerHash for T +where + T: CoreProxy + BlockSizeUser, + ::Core: HashMarker + + CShake + + UpdateCore + + ExtendableOutputCore + + BlockSizeUser::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}"); + } +}