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