Key Derivation Function API in Java
A key derivation function (KDF) is an algorithm that takes some secret starting material and produces one or more cryptographic keys from it. Programs rarely use a raw secret directly as a key. A secret agreed between two parties, or a master key held by an application, is first passed through a KDF to get keys that are the right size, uniformly random-looking, and separate for each purpose: one for encryption, another for signing. Java 25 added a standard API for this, the class javax.crypto.KDF (JEP 510), with the widely used HKDF algorithm built in. In this lesson, you will learn what a KDF is for, how HKDF works, and how to derive keys with the new API.
Why Derive Keys?
| Problem | How a KDF helps |
|---|---|
| A shared secret from a key exchange isn't evenly random | The KDF turns it into uniformly random key bytes |
| One secret, several needs (encrypt, sign, per-user, per-file) | The KDF derives a different key for each purpose from the same secret |
| The secret is the wrong length for the cipher | The KDF outputs exactly the length you ask for |
| If one derived key leaks | The other keys and the original secret stay safe |
How HKDF Works
HKDF (HMAC-based Key Derivation Function, RFC 5869) has two steps:
| Step | Input | Output |
|---|---|---|
| Extract | The secret material (IKM, "input keying material") and an optional salt | One strong, fixed-size key (a "pseudorandom key") |
| Expand | That key, an info label describing the purpose, and a length | As many key bytes as requested |
The info label is what separates keys: the same secret with the label encryption and with the label signing gives two unrelated keys.
The API
| Member | Description |
|---|---|
KDF.getInstance("HKDF-SHA256") | It is used to get a KDF object. HKDF-SHA384 and HKDF-SHA512 are also provided. |
HKDFParameterSpec.ofExtract() | It starts building the parameters. |
.addIKM(bytes) | It adds the secret input material. |
.addSalt(bytes) | It adds a salt. |
.thenExpand(info, length) | It finishes with extract-then-expand, for a given purpose and length in bytes. |
.extractOnly() | It finishes with only the extract step. |
HKDFParameterSpec.expandOnly(key, info, length) | It runs only the expand step on an existing strong key. |
kdf.deriveKey(algorithm, spec) | It derives a SecretKey for an algorithm such as AES. |
kdf.deriveData(spec) | It derives raw bytes. |
Example: Two Keys from One Secret
import java.nio.charset.StandardCharsets;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;
import java.util.HexFormat;
import javax.crypto.KDF;
import javax.crypto.SecretKey;
import javax.crypto.spec.HKDFParameterSpec;
public class Kdf {
public static void main(String[] args) throws Exception {
// A shared secret, for example the result of a key exchange.
byte[] secret = HexFormat.of().parseHex(
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
byte[] salt = "poolstring-demo-salt".getBytes(StandardCharsets.UTF_8);
KDF hkdf = KDF.getInstance("HKDF-SHA256");
AlgorithmParameterSpec forEncryption = HKDFParameterSpec.ofExtract()
.addIKM(secret)
.addSalt(salt)
.thenExpand("encryption".getBytes(StandardCharsets.UTF_8), 32);
SecretKey aesKey = hkdf.deriveKey("AES", forEncryption);
System.out.println("AES key: " + aesKey.getAlgorithm() + ", "
+ aesKey.getEncoded().length * 8 + " bits");
AlgorithmParameterSpec forSigning = HKDFParameterSpec.ofExtract()
.addIKM(secret)
.addSalt(salt)
.thenExpand("signing".getBytes(StandardCharsets.UTF_8), 32);
byte[] macKey = hkdf.deriveData(forSigning);
System.out.println("MAC key: " + macKey.length * 8 + " bits");
System.out.println("Different purpose, different key? "
+ !Arrays.equals(aesKey.getEncoded(), macKey));
byte[] again = KDF.getInstance("HKDF-SHA256").deriveData(forSigning);
System.out.println("Same inputs, same key? "
+ Arrays.equals(macKey, again));
System.out.println("MAC key starts with: "
+ HexFormat.of().formatHex(macKey, 0, 8));
}
}AES key: AES, 256 bits
MAC key: 256 bits
Different purpose, different key? true
Same inputs, same key? true
MAC key starts with: 397a2fcdd55f3df7Explanation:
- The secret is 32 bytes written out in the code so the example gives the same result every time. In real use it would come from a key exchange or a key store, never from source code.
KDF.getInstance("HKDF-SHA256")selects HKDF using SHA-256.- The first parameter set uses the info label
encryptionand asks for 32 bytes.deriveKey("AES", ...)returns a ready-to-use 256-bit AESSecretKey. - The second uses the same secret and salt with the label
signing.deriveDatareturns the raw bytes. - The two keys are different, even though they came from one secret. Learning one tells an attacker nothing about the other.
- Derivation is deterministic: the same inputs always give the same key. Both sides of a connection can therefore derive identical keys independently.
Before Java 25
Earlier versions had no general KDF API. Developers used a third-party library such as Bouncy Castle, or implemented HKDF by hand with the Mac class, which is easy to get subtly wrong. The API was a preview in Java 24 and became final in Java 25. It is also designed so that further algorithms can be added by security providers.
KDFs for Keys vs KDFs for Passwords
These are two different jobs, and they need different algorithms:
| HKDF | PBKDF2, bcrypt, scrypt, Argon2 | |
|---|---|---|
| Input | A secret that is already strong (high entropy) | A password chosen by a person (weak, guessable) |
| Speed | Fast, on purpose | Slow, on purpose, to resist guessing |
| Use for | Deriving session and encryption keys | Storing passwords; turning a password into a key |
Never use HKDF on a password. It is fast, so an attacker could test guesses at enormous speed. Password handling is the subject of "Storing Passwords Safely in Java".
Common Mistakes
Using one key for everything. Derive a separate key per purpose.
Using HKDF to hash passwords. It isn't slow enough; use a password hashing function.
Leaving out the info label. Without it, all derived keys of the same length are identical.
Hard-coding the input secret. The example does so only to be repeatable.
Practice
- Easy: Derive a third key with the label
loggingand confirm it differs from the other two. - Harder: Derive a 256-bit AES key with HKDF and use it with AES-GCM to encrypt and decrypt a message, as in "Encryption Basics in Java".
Key Takeaways
Was this tutorial helpful?