CVE-2024-14041 Overview
CVE-2024-14041 is a timing side-channel vulnerability in the Bouncy Castle cryptography library for Java. The flaw affects the ML-KEM (CRYSTALS-Kyber) implementation in versions 1.73 through 1.77. Three routines divide secret-derived polynomial coefficients by the modulus q: Poly.toMsg (KyberSlash1) plus Poly.compressPoly and PolyVec.compressPolyVec (KyberSlash2). An attacker who measures the timing of many decapsulations performed with the same long-term private key can recover that key. This weakness is classified as CWE-208: Observable Timing Discrepancy.
Critical Impact
Repeated timing measurements against a static ML-KEM private key allow full key recovery, undermining the confidentiality guarantees of the post-quantum key encapsulation mechanism.
Affected Products
- Bouncy Castle for Java version 1.73
- Bouncy Castle for Java versions 1.74 through 1.77
- Applications embedding vulnerable bc-java releases that expose ML-KEM decapsulation with a persistent private key
Discovery Timeline
- 2026-07-28 - CVE-2024-14041 published to NVD
- 2026-07-30 - Last updated in NVD database
Technical Details for CVE-2024-14041
Vulnerability Analysis
The vulnerability sits inside Bouncy Castle's ML-KEM implementation in the org.bouncycastle.pqc.crypto.crystals.kyber package. Three routines performed integer division by the Kyber modulus KyberQ (3329) on values derived from secret material. On many JVM and CPU combinations, this division is not constant-time. Execution latency correlates with the secret coefficient value being processed.
An adversary who can issue many decapsulation requests against the same long-term key pair and measure response timing can recover per-coefficient information. Aggregating measurements across the polynomial reconstructs the private key. The public KyberSlash research at kyberslash.cr.yp.to documents the same class of leak across multiple Kyber implementations.
Compression during encapsulation is unaffected because it operates on values that will become the public ciphertext, not on secret material.
Root Cause
The root cause is the use of hardware integer division on secret-dependent operands. Both Poly.toMsg (message decoding after decapsulation) and the ciphertext compression routines executed expressions of the form (x + KyberQ/2) / KyberQ, where x is derived from the shared secret. Division timing on typical CPUs varies with operand values, producing an observable side channel [CWE-208].
Attack Vector
Exploitation requires network reachability to a service that performs ML-KEM decapsulation with a fixed private key and returns a timing-observable response. The attacker sends a large volume of chosen or replayed ciphertexts and records decapsulation latency. Statistical analysis of latency distributions across many samples reveals secret coefficients. No authentication, user interaction, or local access is required, though attack complexity is high because of the measurement volume needed.
// Patch: replace secret-dependent division with constant-time multiply-shift
// Source: https://github.com/bcgit/bc-java/commit/5adb2c5c5b462a332b01a012bea0784b40b904e5
// File: core/src/main/java/org/bouncycastle/pqc/crypto/crystals/kyber/Poly.java (toMsg)
public byte[] toMsg()
{
byte[] outMsg = new byte[KyberEngine.getKyberIndCpaMsgBytes()];
this.conditionalSubQ();
for (int i = 0; i < KyberEngine.KyberN / 8; i++)
{
outMsg[i] = 0;
for (int j = 0; j < 8; j++)
{
// Avoid a division on secret input, which may generate a
// non-constant-time DIV instruction.
int t = this.getCoeffIndex(8 * i + j) & 0xFFFF;
t <<= 1;
t += 1665;
t *= 80635;
t >>= 28;
t &= 1;
outMsg[i] |= (byte)(t << j);
}
}
return outMsg;
}
// Patch: constant-time replacement in PolyVec.compressPolyVec
// Source: https://github.com/bcgit/bc-java/commit/1590247178f2280defa36421475f015175dfbe9e
// File: core/src/main/java/org/bouncycastle/pqc/crypto/crystals/kyber/PolyVec.java
for (k = 0; k < 4; k++)
{
// Fix for KyberSlash2: division by KyberQ above is not constant time.
long t_k = this.getVectorIndex(i).getCoeffIndex(4 * j + k);
t_k <<= 10;
t_k += 1665;
t_k *= 1290167;
t_k >>= 32;
t_k &= 0x3ff;
t[k] = (short)t_k;
}
Detection Methods for CVE-2024-14041
Indicators of Compromise
- Sustained high-volume decapsulation traffic from a single client toward a service that terminates ML-KEM key exchange with a static key
- Repeated identical or near-identical ciphertexts submitted to Kyber decapsulation endpoints
- Presence of bcprov-jdk*-1.73.jar through bcprov-jdk*-1.77.jar on production hosts running post-quantum TLS or KEM services
Detection Strategies
- Inventory Java applications with software composition analysis and flag any bc-java artifact between 1.73 and 1.77 that references the org.bouncycastle.pqc.crypto.crystals.kyber package
- Instrument decapsulation endpoints to log request rate per source identity and alert when a single peer exceeds an operational baseline
- Review dependency lock files (pom.xml, build.gradle, ivy.xml) in CI pipelines for vulnerable Bouncy Castle versions before build promotion
Monitoring Recommendations
- Track outbound and inbound traffic volume to services exposing ML-KEM decapsulation and alert on statistical anomalies indicative of oracle probing
- Monitor JVM process telemetry for prolonged high-CPU periods correlated with a single remote peer performing repeated KEM operations
- Correlate application logs for repeated decapsulation failures or unusual ciphertext replay patterns from the same source
How to Mitigate CVE-2024-14041
Immediate Actions Required
- Upgrade Bouncy Castle for Java to version 1.78 or later, which replaces the vulnerable divisions with constant-time multiply-shift sequences
- Rotate any long-term ML-KEM private key used by a service that ran a vulnerable bc-java release and was reachable by untrusted clients
- Rebuild and redeploy shaded or fat JARs that bundle the vulnerable bcprov artifact so no stale copy remains in production
Patch Information
The fix ships in Bouncy Castle for Java 1.78. See the bc-java CVE-2024-14041 advisory and the upstream commits 5adb2c5c and 15902471. Additional context on the underlying issue is documented at the KyberSlash research page.
Workarounds
- Rate-limit decapsulation requests per client identity to reduce the number of timing samples an attacker can collect
- Where feasible, use ephemeral ML-KEM key pairs so that any recovered key is discarded before enough samples are collected
- Front-end KEM services with an authenticated channel that binds requests to identities, enabling revocation of abusive peers
# Maven: pin Bouncy Castle to the fixed release
mvn versions:use-dep-version -Dincludes=org.bouncycastle:bcprov-jdk18on -DdepVersion=1.78 -DforceVersion=true
# Gradle: verify no vulnerable transitive versions remain
./gradlew dependencyInsight --dependency bcprov-jdk18on --configuration runtimeClasspath
# Runtime check
java -cp bcprov-jdk18on-1.78.jar org.bouncycastle.LICENSE | head -n 1
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

