JDK-8213363 : X25519 private key PKCS#8 encoding/decoding is incorrect
  • Type: Bug
  • Component: security-libs
  • Sub-Component: javax.crypto
  • Affected Version: 11,12
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_10
  • CPU: x86_64
  • Submitted: 2018-11-04
  • Updated: 2018-11-22
  • Resolved: 2018-11-15
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 12
12 b21Fixed
Related Reports
CSR :  
Relates :  
Sub Tasks
JDK-8213946 :  
Description
ADDITIONAL SYSTEM INFORMATION :
java version "11.0.1" 2018-10-16 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.1+13-LTS)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.1+13-LTS, mixed mode)

A DESCRIPTION OF THE PROBLEM :
The encoder/decoder for X25519 private keys incorrectly assumes that the ASN.1 PrivateKey element is an OCTET STRING containing the 32 bytes of the key. While the PrivateKey element is an OCTET STRING the contents are a BER encoded OCTET STRING of the 32 byte key.

In other words, octetString(octetString(key bytes)) and not octetString(key bytes).

This is defined in RFC8410. The RFC also includes a sample base64 encoded example which is as follows:

-----BEGIN PRIVATE KEY-----
MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB
Z9w7lshQhqowtrbLDFw4rXAxZuE=
-----END PRIVATE KEY------

It should be possible to do the following, assuming the 'bytes' variable is the byte array resulting from decoding the above base64.

var keySpec = new PKCS8EncodedKeySpec(bytes);
var factory = KeyFactory.getInstance("X25519");
var privateKey = factory.generatePrivate(keySpec);

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Method 1:
var kpg = KeyPairGenerator.getInstance("X25519");
var kp = kpg.generateKeyPair();
var bytes = kp.getPrivate().getEncoded();

observe that bytes ends with 04 20 [32 bytes] but it should end with 04 24 04 20 [32 bytes]

Method 2:
Generate an X25519 key using openssl:
$ openssl genpkey -algorithm x25519 -out x25519.key.der -outform der

Inspect the contents of x25519.key.der and observe that it ends with 04 22 04 20 [32 bytes].

var bytes = Files.readAllBytes(Path.of("x25519.key.der"));
var keySpec = new PKCS8EncodedKeySpec(bytes);
var factory = KeyFactory.getInstance("X25519");
var privateKey = factory.generatePrivate(keySpec);

Note the exception.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The output of key.getEncoded() for an X25519 private key should be a valid X25519 PKCS#8 encoded key.

Also, it should be possible to read a valid X25519 PKCS#8 encoded key via ..
var keySpec = new PKCS8EncodedKeySpec(bytes);
var factory = KeyFactory.getInstance("X25519");
var privateKey = factory.generatePrivate(keySpec);
ACTUAL -
key.getEncoded() is not valid PKCS#8 for X25519 private keys.

When reading a valid X25519 key via PKCS#8...
Caused by: java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: key length must be 32
	at jdk.crypto.ec/sun.security.ec.XDHKeyFactory.engineGeneratePrivate(XDHKeyFactory.java:136)
	at java.base/java.security.KeyFactory.generatePrivate(KeyFactory.java:390)
	at net.larko.crypto.EllipticCurve.loadPrivateKey(EllipticCurve.java:119)
	at net.larko.crypto.EllipticCurve.loadPrivateKey(EllipticCurve.java:77)
	at net.larko.crypto.EllipticCurve.loadPrivateKey(EllipticCurve.java:48)
	at net.larko.crypto.EllipticCurveTest.lambda$0(EllipticCurveTest.java:23)
	at org.junit.jupiter.api.AssertDoesNotThrow.assertDoesNotThrow(AssertDoesNotThrow.java:71)
	... 42 more



Comments
Attached is the test case and the key generated using openssl which were used to reproduce the issue: JDK 11 GA - Fail JDK 11.0.1 - fail JDK 12-ea+16 - Fail Output: Exception in thread "main" java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: key length must be 32 at jdk.crypto.ec/sun.security.ec.XDHKeyFactory.engineGeneratePrivate(XDHKeyFactory.java:136) at java.base/java.security.KeyFactory.generatePrivate(KeyFactory.java:390) at JI9057957.main(JI9057957.java:15) Caused by: java.security.InvalidKeyException: key length must be 32 at jdk.crypto.ec/sun.security.ec.XDHPrivateKeyImpl.checkLength(XDHPrivateKeyImpl.java:68) at jdk.crypto.ec/sun.security.ec.XDHPrivateKeyImpl.<init>(XDHPrivateKeyImpl.java:61) at jdk.crypto.ec/sun.security.ec.XDHKeyFactory.generatePrivateImpl(XDHKeyFactory.java:169) at jdk.crypto.ec/sun.security.ec.XDHKeyFactory.engineGeneratePrivate(XDHKeyFactory.java:134) ... 2 more
05-11-2018