Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_017ic4b7rnC7jppGGdHGESvy
70 lines
3.9 KiB
Java
70 lines
3.9 KiB
Java
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();
|
|
}
|
|
}
|