[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