JDK-8368841 : X25519 implementation differs from the specification in RFC 7748
  • Type: Bug
  • Component: security-libs
  • Sub-Component: javax.crypto
  • Affected Version: 11
  • Priority: P3
  • Status: Open
  • Resolution: Unresolved
  • OS: generic
  • CPU: generic
  • Submitted: 2025-09-28
  • Updated: 2025-10-20
Related Reports
Relates :  
Description
ADDITIONAL SYSTEM INFORMATION :
Windows 11

A DESCRIPTION OF THE PROBLEM :
According to the specification in [RFC 7748, Chapter 5][1], the implementation MUST ensure that the most significant bit of the input u-coordinate is cleared for X25519.  
This is currently not the case, which is why the current implementation does not satisfy the second test vector for X25519 in [RFC 7748, Chapter 5.2][2].
If the most significant bit is explicitly cleared, the correct result is produced.

Please note that there is an erratum for this test vector ([Errata ID 5568][3]). However, this has been rejected, meaning that the original test vector is valid. This is also confirmed by processing with other libraries such as BouncyCastle (Java) or pyca/cryptography (Python), which produce the correct result for this test vector.

[1]: https://datatracker.ietf.org/doc/html/rfc7748#section-5
[2]: https://datatracker.ietf.org/doc/html/rfc7748#section-5.2
[3]: https://www.rfc-editor.org/errata/eid5568

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Step 1: Execute the code below for the input u-coordinate 0xe5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493, which corresponds to the official test vector from RFC 7748.  
Step 2: Set the most significant bit of the input u-coordinate to 0 (note that X25519 uses little endian), which changes the last byte of the input u-coordinate from 0x93 to 0x13: 0xe5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a413 and execute the code for this input u-coordinate again.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
In both cases, the output u-coordinate should be 0x95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957, which corresponds to the output u-coordinate of the official test vector from RFC 7748.
ACTUAL -
For step 1, the output u-coordinate is 0xd5f33573c9f6b8129483acce1e2534e95d3c41af6b00d0d30437b87cada57e4a, which does not correspond to the output u-coordinate of the official test vector from RFC 7748.
For step 2, the output u-coordinate is 0x95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957, which corresponds to the output u-coordinate of the official test vector from RFC 7748.

---------- BEGIN SOURCE ----------
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.NamedParameterSpec;
import java.security.spec.XECPrivateKeySpec;
import java.security.spec.XECPublicKeySpec;
import java.util.HexFormat;

import javax.crypto.KeyAgreement;

public class Main {

	// https://openjdk.org/jeps/324
	public static void main(String[] args) throws Exception {
		
		byte[] publicKey = HexFormat.of().parseHex("e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493");
		//byte[] publicKey = HexFormat.of().parseHex("e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a413");
		byte[] privateKey = HexFormat.of().parseHex("4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d");
		NamedParameterSpec paramSpec = new NamedParameterSpec("X25519");
		
		KeyFactory kf = KeyFactory.getInstance("XDH");
		reverse(publicKey);
		BigInteger u = new BigInteger(1, publicKey);
		XECPublicKeySpec pubSpec = new XECPublicKeySpec(paramSpec, u);
		PublicKey pubKey = kf.generatePublic(pubSpec);

		XECPrivateKeySpec privSpec = new XECPrivateKeySpec(paramSpec, privateKey);
		PrivateKey privKey = kf.generatePrivate(privSpec);

		KeyAgreement ka = KeyAgreement.getInstance("XDH");
		ka.init(privKey);
		ka.doPhase(pubKey, true);
		byte[] secret = ka.generateSecret();
		
		System.out.println( HexFormat.of().formatHex(secret));
	}
	
	private static void reverse(byte[] array) {
		for(int i = 0; i < array.length / 2; i++)
		{
		    byte temp = array[i];
		    array[i] = array[array.length - i - 1];
		    array[array.length - i - 1] = temp;
		}
	}
}
---------- END SOURCE ----------


Comments
The observations on Windows 11: JDK 17: Failed, the output is different from the expected result. JDK 25ea+6: Failed. Impact -> M (Somewhere in-between the extremes) Likelihood -> L (Happens on X255192) Workaround -> M (Somewhere in-between the extremes) Priority -> P4
29-09-2025