1import java.nio.charset.StandardCharsets;
2import java.nio.file.Files;
3import java.nio.file.Path;
4import java.security.KeyFactory;
5import java.security.spec.PKCS8EncodedKeySpec;
6import java.security.spec.X509EncodedKeySpec;
7import java.util.Base64;
8import java.util.HexFormat;
9import javax.crypto.KDF;
10import javax.crypto.KeyAgreement;
11import javax.crypto.spec.HKDFParameterSpec;
12
13public class Ecdh {
14 static byte[] pemBody(String file) throws Exception {
15 return Base64.getMimeDecoder().decode(Files.readString(Path.of(file)).replaceAll("-----[^-]+-----", ""));
16 }
17
18 /** keyType "XDH" + agreement "XDH" for X25519; "EC" + "ECDH" for P-256. */
19 static byte[] agree(String keyType, String agreement, String privateKeyFile, String peerPublicKeyFile) throws Exception {
20 KeyFactory factory = KeyFactory.getInstance(keyType);
21 KeyAgreement ka = KeyAgreement.getInstance(agreement);
22 ka.init(factory.generatePrivate(new PKCS8EncodedKeySpec(pemBody(privateKeyFile))));
23 ka.doPhase(factory.generatePublic(new X509EncodedKeySpec(pemBody(peerPublicKeyFile))), true);
24 return ka.generateSecret(); // the raw shared secret
25 }
26
27 /** HKDF-SHA256 with the KDF API of Java 25 (JEP 510); no salt means an all-zero salt. */
28 static byte[] deriveKey(byte[] sharedSecret) throws Exception {
29 KDF hkdf = KDF.getInstance("HKDF-SHA256");
30 return hkdf.deriveData(HKDFParameterSpec.ofExtract()
31 .addIKM(sharedSecret)
32 .thenExpand("AES-256-GCM key v1".getBytes(StandardCharsets.UTF_8), 32));
33 }
34
35 public static void main(String[] args) throws Exception {
36 HexFormat hex = HexFormat.of();
37 byte[] x = agree("XDH", "XDH", "alice-x25519.pem", "bob-x25519.pub.pem");
38 System.out.println("X25519 shared secret: " + hex.formatHex(x));
39 System.out.println("X25519 derived key: " + hex.formatHex(deriveKey(x)));
40
41 byte[] p = agree("EC", "ECDH", "alice-p256.pem", "bob-p256.pub.pem");
42 System.out.println("P-256 shared secret: " + hex.formatHex(p));
43 System.out.println("P-256 derived key: " + hex.formatHex(deriveKey(p)));
44 }
45}
46