From: Viktor Dukhovni Date: Thu, 26 Mar 2026 17:02:34 +0000 (+1100) Subject: Refactor ML-KEM decap, also cleanse failure_key X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=febac4fbf34d6506154795b91a9610da905f1fcb;p=thirdparty%2Fopenssl.git Refactor ML-KEM decap, also cleanse failure_key Pedantically cleanse the typically unused decap failure_key's stack copy. When actually used, it is copied into the caller's shared secret result, perhaps to be cleansed there after use, or not, that's the callers business. While at it, slightly refactor the internal decap() implementation to consolidate all the data to be cleansed into a single buffer, but now avoid copying the public key hash, instead, when computing "K || r" as "G(m || h)" include "h" via a separate EVP_DigestUpdate() call. Reviewed-by: Paul Dale Reviewed-by: Tomas Mraz MergeDate: Tue Mar 31 05:19:40 2026 (Merged from https://github.com/openssl/openssl/pull/30598) --- diff --git a/crypto/ml_kem/ml_kem.c b/crypto/ml_kem/ml_kem.c index babc2281c13..9ea824ccffd 100644 --- a/crypto/ml_kem/ml_kem.c +++ b/crypto/ml_kem/ml_kem.c @@ -1448,6 +1448,30 @@ static int encap(uint8_t *ctext, uint8_t secret[ML_KEM_SHARED_SECRET_BYTES], return ret; } +/* + * Hash the input message |m'| and public key digest |h| + * to obtain |K| and |r|. + */ +static int hash_kr(uint8_t *out, uint8_t *in, + EVP_MD_CTX *mdctx, const ML_KEM_KEY *key) +{ + unsigned int sz, wanted; + + wanted = ML_KEM_SHARED_SECRET_BYTES + ML_KEM_RANDOM_BYTES; + return (EVP_DigestInit_ex(mdctx, key->sha3_512_md, NULL) + && EVP_DigestUpdate(mdctx, in, ML_KEM_RANDOM_BYTES) + && EVP_DigestUpdate(mdctx, key->pkhash, ML_KEM_PKHASH_BYTES) + && EVP_DigestFinal_ex(mdctx, out, &sz) + && ossl_assert(sz == wanted)); +} + +/*- + * Decap needs space for: Kbar | K | r | m' + * We slice up a single buffer to hold them all. + * We don't need to cleanse the public pkhash value. + */ +#define DECAP_BUFFER_SZ (2 * ML_KEM_SHARED_SECRET_BYTES + 2 * ML_KEM_RANDOM_BYTES) + /* * FIPS 203, Section 6.3, Algorithm 18: ML-KEM.Decaps_internal * @@ -1463,11 +1487,11 @@ static int decap(uint8_t secret[ML_KEM_SHARED_SECRET_BYTES], const uint8_t *ctext, uint8_t *tmp_ctext, scalar *tmp, EVP_MD_CTX *mdctx, const ML_KEM_KEY *key) { - uint8_t decrypted[ML_KEM_SHARED_SECRET_BYTES + ML_KEM_PKHASH_BYTES]; - uint8_t failure_key[ML_KEM_RANDOM_BYTES]; - uint8_t Kr[ML_KEM_SHARED_SECRET_BYTES + ML_KEM_RANDOM_BYTES]; + uint8_t buf[DECAP_BUFFER_SZ]; + uint8_t *failure_key = buf; /* Kbar */ + uint8_t *Kr = failure_key + ML_KEM_SHARED_SECRET_BYTES; uint8_t *r = Kr + ML_KEM_SHARED_SECRET_BYTES; - const uint8_t *pkhash = key->pkhash; + uint8_t *m = r + ML_KEM_RANDOM_BYTES; /* m' */ const ML_KEM_VINFO *vinfo = key->vinfo; int i; uint8_t mask; @@ -1493,20 +1517,18 @@ static int decap(uint8_t secret[ML_KEM_SHARED_SECRET_BYTES], vinfo->algorithm_name); return 0; } - decrypt_cpa(decrypted, ctext, tmp, key); - memcpy(decrypted + ML_KEM_SHARED_SECRET_BYTES, pkhash, ML_KEM_PKHASH_BYTES); - if (!hash_g(Kr, decrypted, sizeof(decrypted), mdctx, key) - || !encrypt_cpa(tmp_ctext, decrypted, r, tmp, mdctx, key)) { + decrypt_cpa(m, ctext, tmp, key); + if (!hash_kr(Kr, m, mdctx, key) + || !encrypt_cpa(tmp_ctext, m, r, tmp, mdctx, key)) { memcpy(secret, failure_key, ML_KEM_SHARED_SECRET_BYTES); - OPENSSL_cleanse(decrypted, ML_KEM_SHARED_SECRET_BYTES); - return 1; + goto end; } mask = constant_time_eq_int_8(0, CRYPTO_memcmp(ctext, tmp_ctext, vinfo->ctext_bytes)); for (i = 0; i < ML_KEM_SHARED_SECRET_BYTES; i++) secret[i] = constant_time_select_8(mask, Kr[i], failure_key[i]); - OPENSSL_cleanse(decrypted, ML_KEM_SHARED_SECRET_BYTES); - OPENSSL_cleanse(Kr, sizeof(Kr)); +end: + OPENSSL_cleanse(buf, DECAP_BUFFER_SZ); return 1; }