import javax.crypto.KDF; import javax.crypto.SecretKey; import javax.crypto.spec.HKDFParameterSpec; import java.security.NoSuchAlgorithmException; import java.security.Provider; import java.security.Security; import java.util.Arrays; /** What the JDK 25 KDF API offers: algorithm names, the three spec shapes, and what each returns. */ public class ApiTour { public static void main(String[] args) throws Exception { Fx.header("KDF algorithms registered by the providers of this JDK"); for (Provider p : Security.getProviders()) for (Provider.Service s : p.getServices()) if (s.getType().equals("KDF")) System.out.println(p.getName() + " " + s.getAlgorithm()); Fx.header("getInstance: what works and what does not"); KDF kdf = KDF.getInstance("HKDF-SHA256"); System.out.println("algorithm=" + kdf.getAlgorithm() + " provider=" + kdf.getProviderName() + " parameters=" + kdf.getParameters()); for (String bad : new String[] {"HKDF", "HKDF-SHA1", "hkdf-sha256", "PBKDF2WithHmacSHA256", "Argon2"}) { try { KDF.getInstance(bad); System.out.println(bad + " -> available"); } catch (NoSuchAlgorithmException e) { System.out.println(bad + " -> NoSuchAlgorithmException: " + e.getMessage()); } } byte[] ikm = Fx.fill(0x0b, 22), salt = Fx.range(0, 13), info = Fx.range(0xf0, 10); Fx.header("Shape 1: extract then expand in one call (the common case)"); HKDFParameterSpec both = HKDFParameterSpec.ofExtract().addIKM(ikm).addSalt(salt).thenExpand(info, 42); byte[] okm = kdf.deriveData(both); System.out.println("deriveData -> byte[" + okm.length + "] " + Fx.hex(okm)); SecretKey k = kdf.deriveKey("AES", HKDFParameterSpec.ofExtract().addIKM(ikm).addSalt(salt).thenExpand(info, 32)); System.out.println("deriveKey -> algorithm=" + k.getAlgorithm() + " format=" + k.getFormat() + " bytes=" + k.getEncoded().length); Fx.header("Shape 2: extract only (returns the pseudorandom key, PRK)"); SecretKey prk = kdf.deriveKey("Generic", HKDFParameterSpec.ofExtract().addIKM(ikm).addSalt(salt).extractOnly()); System.out.println("PRK = " + Fx.hex(prk.getEncoded())); Fx.header("Shape 3: expand only (you already hold a uniformly random key)"); byte[] again = kdf.deriveData(HKDFParameterSpec.expandOnly(prk, info, 42)); Fx.check("extractOnly + expandOnly equals the one-call result", Arrays.equals(okm, again)); Fx.header("The same KDF object can be used again, and gives the same bytes"); Fx.check("second call with the same spec is identical", Arrays.equals(okm, kdf.deriveData(both))); Fx.header("Several IKM or salt pieces are concatenated in the order added"); byte[] split = kdf.deriveData(HKDFParameterSpec.ofExtract() .addIKM(Arrays.copyOfRange(ikm, 0, 10)).addIKM(Arrays.copyOfRange(ikm, 10, 22)) .addSalt(salt).thenExpand(info, 42)); Fx.check("addIKM(first 10) + addIKM(last 12) == addIKM(all 22)", Arrays.equals(okm, split)); Fx.header("deriveKey does not check key sizes; Cipher.init does"); for (int len : new int[] {16, 24, 32, 20}) { try { SecretKey a = kdf.deriveKey("AES", HKDFParameterSpec.ofExtract().addIKM(ikm).thenExpand(info, len)); System.out.println("deriveKey(\"AES\") length " + len + " -> ok, " + a.getEncoded().length + " bytes"); javax.crypto.Cipher.getInstance("AES/GCM/NoPadding").init(javax.crypto.Cipher.ENCRYPT_MODE, a); System.out.println(" Cipher.init -> ok"); } catch (Exception e) { System.out.println(" Cipher.init -> " + e.getClass().getSimpleName() + ": " + e.getMessage()); } } Fx.done(); } }