Skip to content

cSHAKE and KMAC diverge from SP800-185 when bytepad prefix is a rate multiple #63

Description

@hey-jj

CShake and Kmac produce digests that disagree with SP800-185 whenever the bytepad-encoded prefix length is an exact multiple of the rate. The crate returns no error, so callers get a digest that no conforming implementation will match.

Reproducer (tiny-keccak 2.0.2, cshake feature):

use tiny_keccak::{CShake, Hasher};

fn main() {
    let mut out = [0u8; 32];
    let mut h = CShake::v128(b"", &[0u8; 161]);
    h.update(b"hello");
    h.finalize(&mut out);
    println!("{}", out.iter().map(|b| format!("{:02x}", b)).collect::<String>());
}

Observed:

62299d1c9af7169fbc81a99abf6e0a5a05505d8d2a46388685d51867e24da94f

Expected:

dea803c5fcfd9d31669f57888e7d45ee9942823a009a185f28ae787183734083

The expected value comes from PyCryptodome cSHAKE128 with the same customization string and input. PyCryptodome was checked against the NIST SP800-185 cSHAKE sample vectors.

Only the exact-multiple case diverges. Holding everything else fixed and varying the customization length:

customization prefix len prefix mod 168 result
160 bytes 167 167 4f7ccd0f0a79479b16f693917f4c2366cd54ea8a3130f89a6bb2bd69224b8c6c, matches
161 bytes 168 0 62299d1c..., diverges
162 bytes 169 1 29dd695943d7eb695bcdc408942e197284db25ee013de695bcd88bba78f2532a, matches

For the failing case, left_encode(168) is 2 bytes, encode_string(b"") is 2 bytes, and encode_string of the 161-byte customization is 164 bytes. Total 168 bytes, exactly one rate block. SP800-185 section 2.3.3 defines bytepad as appending the minimum number of zero bytes to reach a rate multiple. Here that count is zero, so no extra permutation is defined.

Root cause: KeccakState::fill_block at src/lib.rs:464-467 calls keccak() unconditionally. CShake::new at src/cshake.rs:54 and Kmac::new at src/kmac.rs:51 call fill_block after absorbing the encoded prefix. When the prefix length is congruent to 0 mod rate, update() has already permuted at the block boundary and left offset at 0. The fill_block call permutes a second time, which is equivalent to absorbing one extra all-zero rate block that bytepad never emits.

Scope: cSHAKE128 when len(encode_string(N)) + len(encode_string(S)) + 2 is a multiple of 168, and cSHAKE256 for multiples of 136. KMAC128 with 163-byte keys and KMAC256 with 131-byte keys reproduce the same failure. TupleHash and ParallelHash build on CShake::new and inherit it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions