From 5ba9ad218c29bacd6f33b1eefc859e48e7ef984f Mon Sep 17 00:00:00 2001 From: zzz Date: Mon, 13 Apr 2026 08:09:49 -0400 Subject: [PATCH] Crypto: Use constant-time comparison in ElG decrypt and HMAC verification check for zero output in X25519 DH as recommmended by RFC 7748 reported by: bottomlineit.co.za --- .../com/southernstorm/noise/crypto/x25519/Curve25519.java | 5 +++++ core/java/src/net/i2p/crypto/ElGamalEngine.java | 2 +- core/java/src/net/i2p/crypto/HMAC256Generator.java | 2 +- core/java/src/net/i2p/crypto/x25519/X25519DH.java | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/core/java/src/com/southernstorm/noise/crypto/x25519/Curve25519.java b/core/java/src/com/southernstorm/noise/crypto/x25519/Curve25519.java index cf0a6121f..082ff4953 100644 --- a/core/java/src/com/southernstorm/noise/crypto/x25519/Curve25519.java +++ b/core/java/src/com/southernstorm/noise/crypto/x25519/Curve25519.java @@ -471,6 +471,7 @@ public final class Curve25519 { * @param publicKey The public key to use in the evaluation, or null * MUST have MSB high bit cleared, i.e. publicKey[31] & 0x80 == 0 * if the base point of the curve should be used. + * @throws IllegalArgumentException on low-order input see RFC 7748 */ public static void eval(byte[] result, int offset, byte[] privateKey, byte[] publicKey) { @@ -518,6 +519,7 @@ public final class Curve25519 { state.mul(state.x_2, state.x_2, state.z_3); // Convert x_2 into little-endian in the result buffer. + byte b = 0; for (int index = 0; index < 32; ++index) { int bit = (index * 8) % 26; int word = (index * 8) / 26; @@ -525,7 +527,10 @@ public final class Curve25519 { result[offset + index] = (byte)(state.x_2[word] >> bit); else result[offset + index] = (byte)((state.x_2[word] >> bit) | (state.x_2[word + 1] << (26 - bit))); + b |= result[offset + index]; } + if (b == 0) + throw new IllegalArgumentException("low order input RFC 7748"); } finally { // Clean up all temporary state before we exit. state.destroy(); diff --git a/core/java/src/net/i2p/crypto/ElGamalEngine.java b/core/java/src/net/i2p/crypto/ElGamalEngine.java index 33930a06d..699805c0c 100644 --- a/core/java/src/net/i2p/crypto/ElGamalEngine.java +++ b/core/java/src/net/i2p/crypto/ElGamalEngine.java @@ -246,7 +246,7 @@ public final class ElGamalEngine { // we reuse buf here for the calculated hash _context.sha().calculateHash(rv, 0, payloadLen, buf, 0); - boolean ok = DataHelper.eq(buf, 0, val, i + 1, Hash.HASH_LENGTH); + boolean ok = DataHelper.eqCT(buf, 0, val, i + 1, Hash.HASH_LENGTH); SimpleByteCache.release(buf); long end = _context.clock().now(); diff --git a/core/java/src/net/i2p/crypto/HMAC256Generator.java b/core/java/src/net/i2p/crypto/HMAC256Generator.java index 50257516e..e687d6cf4 100644 --- a/core/java/src/net/i2p/crypto/HMAC256Generator.java +++ b/core/java/src/net/i2p/crypto/HMAC256Generator.java @@ -90,7 +90,7 @@ public final class HMAC256Generator extends HMACGenerator { byte origMAC[], int origMACOffset, int origMACLength) { byte calc[] = acquireTmp(); calculate(key, curData, curOffset, curLength, calc, 0); - boolean eq = DataHelper.eq(calc, 0, origMAC, origMACOffset, origMACLength); + boolean eq = DataHelper.eqCT(calc, 0, origMAC, origMACOffset, origMACLength); releaseTmp(calc); return eq; } diff --git a/core/java/src/net/i2p/crypto/x25519/X25519DH.java b/core/java/src/net/i2p/crypto/x25519/X25519DH.java index b2b9cc1d4..7f3fd5de8 100644 --- a/core/java/src/net/i2p/crypto/x25519/X25519DH.java +++ b/core/java/src/net/i2p/crypto/x25519/X25519DH.java @@ -24,7 +24,7 @@ public class X25519DH { * * @param pub MUST have MSB high bit cleared, i.e. pub.getData()[31] & 0x80 == 0 * @return ECIES_X25519 - * @throws IllegalArgumentException if not ECIES_X25519 + * @throws IllegalArgumentException if not ECIES_X25519 or on low-order input see RFC 7748 */ public static SessionKey dh(PrivateKey priv, PublicKey pub) { if (priv.getType() != TYPE || pub.getType() != TYPE)