Key Derivation Function API in Java

Naveen AnamaneniUpdated · 6 min read

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?

ProblemHow a KDF helps
A shared secret from a key exchange isn't evenly randomThe 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 cipherThe KDF outputs exactly the length you ask for
If one derived key leaksThe other keys and the original secret stay safe

How HKDF Works

HKDF (HMAC-based Key Derivation Function, RFC 5869) has two steps:

StepInputOutput
ExtractThe secret material (IKM, "input keying material") and an optional saltOne strong, fixed-size key (a "pseudorandom key")
ExpandThat key, an info label describing the purpose, and a lengthAs 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

MemberDescription
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

Kdf.java▶ Try it
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));
    }
}
Output
AES key: AES, 256 bits
MAC key: 256 bits
Different purpose, different key? true
Same inputs, same key?           true
MAC key starts with: 397a2fcdd55f3df7

Explanation:

  1. 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.
  2. KDF.getInstance("HKDF-SHA256") selects HKDF using SHA-256.
  3. The first parameter set uses the info label encryption and asks for 32 bytes. deriveKey("AES", ...) returns a ready-to-use 256-bit AES SecretKey.
  4. The second uses the same secret and salt with the label signing. deriveData returns the raw bytes.
  5. The two keys are different, even though they came from one secret. Learning one tells an attacker nothing about the other.
  6. 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:

HKDFPBKDF2, bcrypt, scrypt, Argon2
InputA secret that is already strong (high entropy)A password chosen by a person (weak, guessable)
SpeedFast, on purposeSlow, on purpose, to resist guessing
Use forDeriving session and encryption keysStoring 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

  1. Easy: Derive a third key with the label logging and confirm it differs from the other two.
  2. 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".

Quick check: KDF API

4 questions
1What does a key derivation function do?
2In HKDF, what makes two derived keys different?
3In which Java version is the KDF API a final feature?
4Should HKDF be used to store passwords?

Key Takeaways

Was this tutorial helpful?

Lesson 512 of 578