Java 25 Key Derivation Function (KDF) API Tutorial
Introduction
Cryptographic protocols rarely use a shared secret directly as an encryption key. A secret produced by a key exchange or a key encapsulation mechanismClick to read tutorial: Java 24 Quantum-Resistant Crypto: ML-KEM and ML-DSA may not be uniformly random, may be the wrong length, and is usually needed for several purposes - one key for encryption, another for authentication, separate keys for each direction of a connection. A key derivation function (KDF) turns one secret into as many strong, independent keys as needed. TLS 1.3, Signal, HPKE and WireGuard all rely on one, HKDF. Until now, Java had no standard API for this, so code used third-party libraries or wrote HKDF by hand with
Checking the API against the RFC 5869 test vector
RFC 5869 publishes test vectors for HKDF. The program below runs the first one through the new API and compares the result with the expected output -
OUTPUT of the above code
OUTPUT of the above code
Explanation of the code
Where the KDF API fits
Mac. After a preview in Java 24, Java 25Click to read tutorial: Overview of New Features in Java 25 (JEP 510) finalized the Key Derivation Function API, javax.crypto.KDF. This tutorial explains key derivation and HKDF, and shows how to derive keys with the new API with runnable examples, checked against the official test vector.
HKDF: extract and expand
HKDF (HMAC-based Key Derivation Function, RFC 5869) works in two steps -
- Extract - takes the input keying material (IKM), such as a shared secret, and an optional salt, and condenses them into a fixed-length, uniformly random pseudorandom key (PRK).
- Expand - takes the PRK and an info string describing the purpose, such as
"encryption key", and produces output keying material of the requested length. Different info strings give independent keys from the same PRK.
HKDFParameterSpec, and is available with SHA-256 and SHA-512 as HKDF-SHA256 and HKDF-SHA512.
HKDF-SHA256 with the RFC 5869 test case 1
//HkdfTestVector.java
import java.security.spec.AlgorithmParameterSpec;
import java.util.HexFormat;
import javax.crypto.KDF;
import javax.crypto.spec.HKDFParameterSpec;
public class HkdfTestVector {
public static void main(String[] args) throws Exception {
HexFormat hex = HexFormat.of();
byte[] ikm = hex.parseHex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
byte[] salt = hex.parseHex("000102030405060708090a0b0c");
byte[] info = hex.parseHex("f0f1f2f3f4f5f6f7f8f9");
String expected = "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865";
KDF hkdf = KDF.getInstance("HKDF-SHA256");
AlgorithmParameterSpec params = HKDFParameterSpec.ofExtract()
.addIKM(ikm)
.addSalt(salt)
.thenExpand(info, 42); // extract, then expand to 42 bytes
byte[] okm = hkdf.deriveData(params);
System.out.println("OKM : " + hex.formatHex(okm));
System.out.println("Matches RFC 5869 test case 1: " + hex.formatHex(okm).equals(expected));
}
}
OKM : 3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865 Matches RFC 5869 test case 1: true
deriveData() returns raw bytes - useful for checking test vectors or for derived values which are not keys. The output matches the RFC exactly.
Deriving several keys from one shared secret
In practice, the input is a secret from a key exchange, and the outputs are SecretKeys for specific algorithms. The example establishes a shared secret with ML-KEM, then derives separate AES keys for each direction of a connection with deriveKey() -
From a key exchange to independent AES keys
//DeriveKeysDemo.java
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.util.Arrays;
import javax.crypto.KDF;
import javax.crypto.KEM;
import javax.crypto.SecretKey;
import javax.crypto.spec.HKDFParameterSpec;
public class DeriveKeysDemo {
static SecretKey derive(SecretKey shared, byte[] salt, String purpose) throws Exception {
KDF hkdf = KDF.getInstance("HKDF-SHA256");
return hkdf.deriveKey("AES", HKDFParameterSpec.ofExtract()
.addIKM(shared)
.addSalt(salt)
.thenExpand(purpose.getBytes(StandardCharsets.UTF_8), 32)); // a 256-bit AES key
}
public static void main(String[] args) throws Exception {
// A shared secret from a post-quantum key encapsulation (Java 24)
KeyPair server = KeyPairGenerator.getInstance("ML-KEM").generateKeyPair();
KEM kem = KEM.getInstance("ML-KEM");
KEM.Encapsulated enc = kem.newEncapsulator(server.getPublic()).encapsulate();
SecretKey clientShared = enc.key();
SecretKey serverShared = kem.newDecapsulator(server.getPrivate()).decapsulate(enc.encapsulation());
byte[] salt = "session-42".getBytes(StandardCharsets.UTF_8);
SecretKey clientToServer = derive(clientShared, salt, "client to server");
SecretKey serverToClient = derive(clientShared, salt, "server to client");
SecretKey serverSideCopy = derive(serverShared, salt, "client to server");
System.out.println("Key algorithm / size : " + clientToServer.getAlgorithm() + " / " + clientToServer.getEncoded().length * 8 + " bits");
System.out.println("Directions use different keys: " + !Arrays.equals(clientToServer.getEncoded(), serverToClient.getEncoded()));
System.out.println("Both sides derive the same key: " + Arrays.equals(clientToServer.getEncoded(), serverSideCopy.getEncoded()));
}
}
Key algorithm / size : AES / 256 bits Directions use different keys: true Both sides derive the same key: true
- Both sides of the ML-KEM exchange hold the same shared secret. Each derives keys from it with HKDF, so they end up with the same keys without ever sending them.
- The two directions use different info strings,
"client to server"and"server to client", so their keys are independent - learning one key reveals nothing about the other. deriveKey("AES", ...)returns aSecretKeyready forCipher, andaddIKM()accepts aSecretKeydirectly, so the secret's bytes never need to be handled by application code.
- Together with the KEM API (Java 21)Click to read: KEM API (Java 21) and ML-KEM (Java 24)Click to read tutorial: Java 24 Quantum-Resistant Crypto: ML-KEM and ML-DSA, the KDF API completes what is needed to implement modern hybrid and post-quantum schemes such as HPKE in pure Java.
- The API is designed for other KDFs too: security providers can add algorithms such as Argon2, and
KDF.getInstance()selects them by name, like any other Java cryptography service. - Do not use HKDF for passwords. HKDF assumes its input is already a strong secret. To turn a password into a key, use a deliberately slow password-hashing function such as PBKDF2 (
SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")).
deriveData(), and derived separate AES keys for each direction of a connection from an ML-KEM shared secret with deriveKey(). Lastly, we saw where the API fits, and why it is not for passwords. To see all the changes in this release, read the overview of new features in Java 25Click to read tutorial: Overview of New Features in Java 25.Continue Reading Java 22 to 25 Tutorials
Overview of New Features in Java 25Click to read tutorial: Overview of New Features in Java 25 Java 25 Compact Source Files and Instance main MethodsClick to read tutorial: Java 25 Compact Source Files and Instance main Methods Java 25 Module Import Declarations with ExamplesClick to read tutorial: Java 25 Module Import Declarations with Examples Java 25 Flexible Constructor Bodies with ExamplesClick to read tutorial: Java 25 Flexible Constructor Bodies with Examples Java 25 Scoped Values Tutorial with ExamplesClick to read tutorial: Java 25 Scoped Values Tutorial with Examples Java 25 Compact Object Headers ExplainedClick to read tutorial: Java 25 Compact Object Headers Explained Overview of New Features in Java 22Click to read tutorial: Overview of New Features in Java 22 Overview of New Features in Java 23Click to read tutorial: Overview of New Features in Java 23 Overview of New Features in Java 24Click to read tutorial: Overview of New Features in Java 24
Overview of New Features in Java 25Click to read tutorial: Overview of New Features in Java 25 Java 25 Compact Source Files and Instance main MethodsClick to read tutorial: Java 25 Compact Source Files and Instance main Methods Java 25 Module Import Declarations with ExamplesClick to read tutorial: Java 25 Module Import Declarations with Examples Java 25 Flexible Constructor Bodies with ExamplesClick to read tutorial: Java 25 Flexible Constructor Bodies with Examples Java 25 Scoped Values Tutorial with ExamplesClick to read tutorial: Java 25 Scoped Values Tutorial with Examples Java 25 Compact Object Headers ExplainedClick to read tutorial: Java 25 Compact Object Headers Explained Overview of New Features in Java 22Click to read tutorial: Overview of New Features in Java 22 Overview of New Features in Java 23Click to read tutorial: Overview of New Features in Java 23 Overview of New Features in Java 24Click to read tutorial: Overview of New Features in Java 24