[PATCH 1/3] sntrup761: fix implicit rejection key in decapsulation
Jussi Kivilinna
jussi.kivilinna at iki.fi
Sat Nov 7 06:30:04 CET 2026
* cipher/sntrup761.c (sntrup761_dec): Move 'rho' to 'r_enc' on reject
instead of 'r_enc ^ rho'.
* tests/t-kem.c (test_kem_sntrup761_reject): New.
(test_kem_sntrup761): Check rejection key.
--
Commit 4e963e91 "sntrup761: use const-time helpers for memory
comparison and cond move" kept XOR of 'r_enc' and 'rho' as source for
conditional move, so rejection key was HashSession(0, r_enc ^ rho, C)
instead of HashSession(0, rho, C) of specification. Rejection key
stays unpredictable without 'rho', accept path is not affected.
GnuPG-bug-id: 8542
Reported-by: Bhabani Sankar Das <007bsd at proton.me>
Signed-off-by: Jussi Kivilinna <jussi.kivilinna at iki.fi>
---
cipher/sntrup761.c | 6 +-----
tests/t-kem.c | 45 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 46 insertions(+), 5 deletions(-)
diff --git a/cipher/sntrup761.c b/cipher/sntrup761.c
index bcb6e651..c8c8f78d 100644
--- a/cipher/sntrup761.c
+++ b/cipher/sntrup761.c
@@ -1027,16 +1027,12 @@ sntrup761_dec (unsigned char *k, const unsigned char *c, const unsigned char *sk
const unsigned char *cache = rho + Inputs_bytes;
Inputs r;
unsigned char r_enc[Inputs_bytes];
- unsigned char tmp[Inputs_bytes];
unsigned char cnew[Ciphertexts_bytes + Confirm_bytes];
int mask;
- int i;
ZDecrypt (r, c, sk);
Hide (cnew, r_enc, r, pk, cache);
mask = Ciphertexts_diff_mask (c, cnew);
- for (i = 0; i < Inputs_bytes; ++i)
- tmp[i] = r_enc[i] ^ rho[i];
- _gcry_ct_memmov_cond (r_enc, tmp, Inputs_bytes, mask & 1);
+ _gcry_ct_memmov_cond (r_enc, rho, Inputs_bytes, mask & 1);
HashSession (k, 1 + mask, r_enc, c);
}
diff --git a/tests/t-kem.c b/tests/t-kem.c
index 557e3473..6a2dde38 100644
--- a/tests/t-kem.c
+++ b/tests/t-kem.c
@@ -33,6 +33,49 @@
static int in_fips_mode;
+#define SNTRUP761_RHO_LEN 191
+#define SNTRUP761_CACHE_LEN 32
+#define SNTRUP761_HASH_LEN 32
+
+static void
+test_kem_sntrup761_reject (int testno, const uint8_t *seckey,
+ uint8_t *ciphertext)
+{
+ gcry_error_t err;
+ const uint8_t *rho = seckey + GCRY_KEM_SNTRUP761_SECKEY_LEN
+ - SNTRUP761_CACHE_LEN - SNTRUP761_RHO_LEN;
+ uint8_t buf[1 + SNTRUP761_HASH_LEN + GCRY_KEM_SNTRUP761_ENCAPS_LEN];
+ uint8_t digest[64];
+ uint8_t key[GCRY_KEM_SNTRUP761_SHARED_LEN];
+
+ ciphertext[GCRY_KEM_SNTRUP761_ENCAPS_LEN - 1] ^= 1 << (testno % 8);
+
+ err = gcry_kem_decap (GCRY_KEM_SNTRUP761,
+ seckey, GCRY_KEM_SNTRUP761_SECKEY_LEN,
+ ciphertext, GCRY_KEM_SNTRUP761_ENCAPS_LEN,
+ key, GCRY_KEM_SNTRUP761_SHARED_LEN,
+ NULL, 0);
+ if (err)
+ {
+ fail ("gcry_kem_decap reject %d: %s", testno, gpg_strerror (err));
+ return;
+ }
+
+ buf[0] = 3;
+ memcpy (buf + 1, rho, SNTRUP761_RHO_LEN);
+ gcry_md_hash_buffer (GCRY_MD_SHA512, digest, buf, 1 + SNTRUP761_RHO_LEN);
+
+ buf[0] = 0;
+ memcpy (buf + 1, digest, SNTRUP761_HASH_LEN);
+ memcpy (buf + 1 + SNTRUP761_HASH_LEN, ciphertext,
+ GCRY_KEM_SNTRUP761_ENCAPS_LEN);
+ gcry_md_hash_buffer (GCRY_MD_SHA512, digest, buf, sizeof buf);
+
+ if (memcmp (key, digest, GCRY_KEM_SNTRUP761_SHARED_LEN) != 0)
+ fail ("sntrup761 test %d failed: rejection key mismatch\n", testno);
+}
+
+
static void
test_kem_sntrup761 (int testno)
{
@@ -89,6 +132,8 @@ test_kem_sntrup761 (int testno)
fprintf (stderr, " %02x", key2[i]);
putc ('\n', stderr);
}
+
+ test_kem_sntrup761_reject (testno, seckey, ciphertext);
}
--
2.53.0
More information about the Gcrypt-devel
mailing list