From bf8cf449eaee93d5986c4f376bbfd5c4e1678b37 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Wed, 8 Apr 2026 00:15:28 +0200 Subject: [PATCH] sha3: make post-final misuse safe and deterministic --- src/libsodium/crypto_hash/sha3/hash_sha3.c | 50 ++++++++++----- test/default/hash_sha3256.c | 73 ++++++++++++++++++++++ test/default/hash_sha3256.exp | 2 + test/default/hash_sha3512.c | 73 ++++++++++++++++++++++ test/default/hash_sha3512.exp | 2 + 5 files changed, 183 insertions(+), 17 deletions(-) diff --git a/src/libsodium/crypto_hash/sha3/hash_sha3.c b/src/libsodium/crypto_hash/sha3/hash_sha3.c index 888555e5..842d19ff 100644 --- a/src/libsodium/crypto_hash/sha3/hash_sha3.c +++ b/src/libsodium/crypto_hash/sha3/hash_sha3.c @@ -37,6 +37,14 @@ sha3_update(sha3_state_internal *state, const unsigned char *in, size_t inlen) { size_t consumed = 0; size_t chunk_size; + int ret = 0; + + if (state->phase != SHA3_PHASE_ABSORBING) { + crypto_core_keccak1600_permute_24(&state->state); + state->phase = SHA3_PHASE_ABSORBING; + state->offset = 0; + ret = -1; + } while (consumed < inlen) { if (state->offset == state->rate) { @@ -52,36 +60,42 @@ sha3_update(sha3_state_internal *state, const unsigned char *in, size_t inlen) consumed += chunk_size; } - return 0; + return ret; } static int sha3_final(sha3_state_internal *state, unsigned char *out) { unsigned char pad; + int ret = 0; - if (state->offset == state->rate) { + if (state->phase != SHA3_PHASE_ABSORBING) { crypto_core_keccak1600_permute_24(&state->state); - state->offset = 0; - } - - if (state->offset == state->rate - 1) { - pad = (unsigned char) (SHA3_DOMAIN ^ 0x80); - crypto_core_keccak1600_xor_bytes(&state->state, &pad, state->offset, 1); + ret = -1; } else { - pad = SHA3_DOMAIN; - crypto_core_keccak1600_xor_bytes(&state->state, &pad, state->offset, 1); - pad = 0x80; - crypto_core_keccak1600_xor_bytes(&state->state, &pad, state->rate - 1, 1); - } + if (state->offset == state->rate) { + crypto_core_keccak1600_permute_24(&state->state); + state->offset = 0; + } - crypto_core_keccak1600_permute_24(&state->state); + if (state->offset == state->rate - 1) { + pad = (unsigned char) (SHA3_DOMAIN ^ 0x80); + crypto_core_keccak1600_xor_bytes(&state->state, &pad, state->offset, 1); + } else { + pad = SHA3_DOMAIN; + crypto_core_keccak1600_xor_bytes(&state->state, &pad, state->offset, 1); + pad = 0x80; + crypto_core_keccak1600_xor_bytes(&state->state, &pad, state->rate - 1, 1); + } + + crypto_core_keccak1600_permute_24(&state->state); + } crypto_core_keccak1600_extract_bytes(&state->state, out, 0, state->outlen); + state->offset = 0; + state->phase = SHA3_PHASE_FINALIZED; - sodium_memzero(state, sizeof *state); - - return 0; + return ret; } size_t @@ -125,6 +139,7 @@ crypto_hash_sha3256(unsigned char *out, const unsigned char *in, unsigned long l crypto_hash_sha3256_init(&state); crypto_hash_sha3256_update(&state, in, inlen); crypto_hash_sha3256_final(&state, out); + sodium_memzero(&state, sizeof state); return 0; } @@ -170,6 +185,7 @@ crypto_hash_sha3512(unsigned char *out, const unsigned char *in, unsigned long l crypto_hash_sha3512_init(&state); crypto_hash_sha3512_update(&state, in, inlen); crypto_hash_sha3512_final(&state, out); + sodium_memzero(&state, sizeof state); return 0; } diff --git a/test/default/hash_sha3256.c b/test/default/hash_sha3256.c index fef5660c..d922ae67 100644 --- a/test/default/hash_sha3256.c +++ b/test/default/hash_sha3256.c @@ -2,6 +2,8 @@ #define TEST_NAME "hash_sha3256" #include "cmptest.h" +#define TEST_SHA3_256_BYTES 32U + int main(void) { @@ -125,6 +127,77 @@ main(void) printf("Rate-1 boundary test passed\n"); } + { + static const unsigned char resumed_msg[] = { 0x78, 0x79 }; + static const unsigned char resumed_msg_other[] = { 0x78, 0x7a }; + unsigned char resumed_out1[TEST_SHA3_256_BYTES]; + unsigned char resumed_out2[TEST_SHA3_256_BYTES]; + unsigned char resumed_out3[TEST_SHA3_256_BYTES]; + crypto_hash_sha3256_state state2; + crypto_hash_sha3256_state state3; + int ret; + + crypto_hash_sha3256_init(&state); + crypto_hash_sha3256_init(&state2); + crypto_hash_sha3256_init(&state3); + crypto_hash_sha3256_update(&state, msg_abc, 3); + crypto_hash_sha3256_update(&state2, msg_abc, 3); + crypto_hash_sha3256_update(&state3, msg_abc, 3); + crypto_hash_sha3256_final(&state, out); + crypto_hash_sha3256_final(&state2, resumed_out2); + crypto_hash_sha3256_final(&state3, resumed_out3); + assert(memcmp(out, out_abc, 32) == 0); + assert(memcmp(resumed_out2, out_abc, 32) == 0); + assert(memcmp(resumed_out3, out_abc, 32) == 0); + + ret = crypto_hash_sha3256_update(&state, resumed_msg, sizeof resumed_msg); + assert(ret == -1); + ret = crypto_hash_sha3256_update(&state2, resumed_msg, sizeof resumed_msg); + assert(ret == -1); + ret = crypto_hash_sha3256_update(&state3, resumed_msg_other, sizeof resumed_msg_other); + assert(ret == -1); + + ret = crypto_hash_sha3256_final(&state, resumed_out1); + assert(ret == 0); + ret = crypto_hash_sha3256_final(&state2, resumed_out2); + assert(ret == 0); + ret = crypto_hash_sha3256_final(&state3, resumed_out3); + assert(ret == 0); + + assert(memcmp(resumed_out1, resumed_out2, TEST_SHA3_256_BYTES) == 0); + assert(memcmp(resumed_out1, resumed_out3, TEST_SHA3_256_BYTES) != 0); + printf("Update-after-final recovery test passed\n"); + } + + { + unsigned char repeated_out1[TEST_SHA3_256_BYTES]; + unsigned char repeated_out2[TEST_SHA3_256_BYTES]; + crypto_hash_sha3256_state state2; + int ret; + + memset(repeated_out1, 0xAA, sizeof repeated_out1); + memset(repeated_out2, 0xAA, sizeof repeated_out2); + + crypto_hash_sha3256_init(&state); + crypto_hash_sha3256_init(&state2); + crypto_hash_sha3256_update(&state, msg_abc, 3); + crypto_hash_sha3256_update(&state2, msg_abc, 3); + ret = crypto_hash_sha3256_final(&state, out); + assert(ret == 0); + ret = crypto_hash_sha3256_final(&state2, out); + assert(ret == 0); + + ret = crypto_hash_sha3256_final(&state, repeated_out1); + assert(ret == -1); + ret = crypto_hash_sha3256_final(&state2, repeated_out2); + assert(ret == -1); + + assert(memcmp(repeated_out1, repeated_out2, TEST_SHA3_256_BYTES) == 0); + assert(memcmp(repeated_out1, out, TEST_SHA3_256_BYTES) != 0); + assert(repeated_out1[0] != 0xAA); + printf("Final-after-final test passed\n"); + } + printf("OK\n"); return 0; diff --git a/test/default/hash_sha3256.exp b/test/default/hash_sha3256.exp index 01031e7a..1dda6273 100644 --- a/test/default/hash_sha3256.exp +++ b/test/default/hash_sha3256.exp @@ -6,4 +6,6 @@ Chunked update test passed Rate boundary test passed Rate+1 boundary test passed Rate-1 boundary test passed +Update-after-final recovery test passed +Final-after-final test passed OK diff --git a/test/default/hash_sha3512.c b/test/default/hash_sha3512.c index ae858cbd..cea99ce0 100644 --- a/test/default/hash_sha3512.c +++ b/test/default/hash_sha3512.c @@ -2,6 +2,8 @@ #define TEST_NAME "hash_sha3512" #include "cmptest.h" +#define TEST_SHA3_512_BYTES 64U + int main(void) { @@ -134,6 +136,77 @@ main(void) printf("Rate-1 boundary test passed\n"); } + { + static const unsigned char resumed_msg[] = { 0x78, 0x79 }; + static const unsigned char resumed_msg_other[] = { 0x78, 0x7a }; + unsigned char resumed_out1[TEST_SHA3_512_BYTES]; + unsigned char resumed_out2[TEST_SHA3_512_BYTES]; + unsigned char resumed_out3[TEST_SHA3_512_BYTES]; + crypto_hash_sha3512_state state2; + crypto_hash_sha3512_state state3; + int ret; + + crypto_hash_sha3512_init(&state); + crypto_hash_sha3512_init(&state2); + crypto_hash_sha3512_init(&state3); + crypto_hash_sha3512_update(&state, msg_abc, 3); + crypto_hash_sha3512_update(&state2, msg_abc, 3); + crypto_hash_sha3512_update(&state3, msg_abc, 3); + crypto_hash_sha3512_final(&state, out); + crypto_hash_sha3512_final(&state2, resumed_out2); + crypto_hash_sha3512_final(&state3, resumed_out3); + assert(memcmp(out, out_abc, 64) == 0); + assert(memcmp(resumed_out2, out_abc, 64) == 0); + assert(memcmp(resumed_out3, out_abc, 64) == 0); + + ret = crypto_hash_sha3512_update(&state, resumed_msg, sizeof resumed_msg); + assert(ret == -1); + ret = crypto_hash_sha3512_update(&state2, resumed_msg, sizeof resumed_msg); + assert(ret == -1); + ret = crypto_hash_sha3512_update(&state3, resumed_msg_other, sizeof resumed_msg_other); + assert(ret == -1); + + ret = crypto_hash_sha3512_final(&state, resumed_out1); + assert(ret == 0); + ret = crypto_hash_sha3512_final(&state2, resumed_out2); + assert(ret == 0); + ret = crypto_hash_sha3512_final(&state3, resumed_out3); + assert(ret == 0); + + assert(memcmp(resumed_out1, resumed_out2, TEST_SHA3_512_BYTES) == 0); + assert(memcmp(resumed_out1, resumed_out3, TEST_SHA3_512_BYTES) != 0); + printf("Update-after-final recovery test passed\n"); + } + + { + unsigned char repeated_out1[TEST_SHA3_512_BYTES]; + unsigned char repeated_out2[TEST_SHA3_512_BYTES]; + crypto_hash_sha3512_state state2; + int ret; + + memset(repeated_out1, 0xAA, sizeof repeated_out1); + memset(repeated_out2, 0xAA, sizeof repeated_out2); + + crypto_hash_sha3512_init(&state); + crypto_hash_sha3512_init(&state2); + crypto_hash_sha3512_update(&state, msg_abc, 3); + crypto_hash_sha3512_update(&state2, msg_abc, 3); + ret = crypto_hash_sha3512_final(&state, out); + assert(ret == 0); + ret = crypto_hash_sha3512_final(&state2, out); + assert(ret == 0); + + ret = crypto_hash_sha3512_final(&state, repeated_out1); + assert(ret == -1); + ret = crypto_hash_sha3512_final(&state2, repeated_out2); + assert(ret == -1); + + assert(memcmp(repeated_out1, repeated_out2, TEST_SHA3_512_BYTES) == 0); + assert(memcmp(repeated_out1, out, TEST_SHA3_512_BYTES) != 0); + assert(repeated_out1[0] != 0xAA); + printf("Final-after-final test passed\n"); + } + printf("OK\n"); return 0; diff --git a/test/default/hash_sha3512.exp b/test/default/hash_sha3512.exp index c19eb9d3..6dfe023d 100644 --- a/test/default/hash_sha3512.exp +++ b/test/default/hash_sha3512.exp @@ -6,4 +6,6 @@ Chunked update test passed Rate boundary test passed Rate+1 boundary test passed Rate-1 boundary test passed +Update-after-final recovery test passed +Final-after-final test passed OK