Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions .github/workflows/kmac.yml
Original file line number Diff line number Diff line change
@@ -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
46 changes: 44 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"cbc-mac",
"cmac",
"hmac",
"kmac",
"pmac",
"retail-mac",
]
Expand Down
22 changes: 22 additions & 0 deletions kmac/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
124 changes: 124 additions & 0 deletions kmac/README.md
Original file line number Diff line number Diff line change
@@ -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
61 changes: 61 additions & 0 deletions kmac/benches/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
);
Loading