diff --git a/core-java-modules/core-java-25/src/main/java/com/baeldung/kdf/KdfApiJava25.java b/core-java-modules/core-java-25/src/main/java/com/baeldung/kdf/KdfApiJava25.java new file mode 100644 index 000000000000..b68cd3746588 --- /dev/null +++ b/core-java-modules/core-java-25/src/main/java/com/baeldung/kdf/KdfApiJava25.java @@ -0,0 +1,96 @@ +package com.baeldung.kdf; + +import javax.crypto.KDF; +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.HKDFParameterSpec; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import javax.crypto.Mac; + +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.InvalidKeyException; + +/** + * Examples of the Key Derivation Function (KDF) API introduced in Java 25. + */ +public class KdfApiJava25 { + + public void demonstrateArchitecture() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { + // Entry point: Creating a KDF instance + KDF kdf = KDF.getInstance("HKDF-SHA256"); + + // Mock parameters for demonstration + HKDFParameterSpec paramSpec = HKDFParameterSpec.ofExtract() + .addIKM(new byte[32]) + .thenExpand(new byte[16], 32); + + // Derive a typed SecretKey + SecretKey key = kdf.deriveKey("AES", paramSpec); + + // Derive raw byte material + byte[] rawKeyMaterial = kdf.deriveData(paramSpec); + } + + public void demonstrateDerivationMethods(byte[] ikm, byte[] salt, byte[] info, GCMParameterSpec gcmSpec) throws Exception { + + KDF kdf = KDF.getInstance("HKDF-SHA256"); + HKDFParameterSpec hkdfParams = HKDFParameterSpec.ofExtract() + .addIKM(ikm) + .addSalt(salt) + .thenExpand(info, 32); + + // Using deriveKey for immediately usable JCA objects + SecretKey aesKey = kdf.deriveKey("AES", hkdfParams); + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, aesKey, gcmSpec); + + // Using deriveData for raw octets/custom protocols + byte[] okm = kdf.deriveData(hkdfParams); + } + + public void demonstrateHkdfModes(byte[] ikm, byte[] salt, byte[] info, SecretKey prk) { + // 1. Extract-then-Expand (Most common) + HKDFParameterSpec params = HKDFParameterSpec.ofExtract() + .addIKM(ikm) + .addSalt(salt) + .thenExpand(info, 32); + + // 2. Extract-only (Produces a pseudorandom key) + HKDFParameterSpec extractOnly = HKDFParameterSpec.ofExtract() + .addIKM(ikm) + .addSalt(salt) + .extractOnly(); + + // 3. Expand-only (Uses a previously derived PRK) + HKDFParameterSpec expandOnly = HKDFParameterSpec.expandOnly(prk, info, 64); + } + + public void compareOldWay(byte[] salt, byte[] ikm, byte[] info) throws Exception { + // Manual implementation using Mac (The "Old Way") + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(salt, "HmacSHA256")); + byte[] prk = mac.doFinal(ikm); + + mac.init(new SecretKeySpec(prk, "HmacSHA256")); + byte[] t = new byte[0]; + byte[] okm = new byte[32]; + byte counter = 1; + mac.update(t); + mac.update(info); + mac.update(counter); + t = mac.doFinal(); + System.arraycopy(t, 0, okm, 0, 32); + SecretKey aesKey = new SecretKeySpec(okm, "AES"); + } + + public void compareNewWay(byte[] salt, byte[] ikm, byte[] info) throws Exception { + // Self-documenting construct (The "New Way") + KDF hkdf = KDF.getInstance("HKDF-SHA256"); + SecretKey aesKey = hkdf.deriveKey("AES", HKDFParameterSpec.ofExtract() + .addIKM(ikm) + .addSalt(salt) + .thenExpand(info, 32)); + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-25/src/test/java/com/baeldung/kdf/KdfApiJava25UnitTest.java b/core-java-modules/core-java-25/src/test/java/com/baeldung/kdf/KdfApiJava25UnitTest.java new file mode 100644 index 000000000000..c254eec9357b --- /dev/null +++ b/core-java-modules/core-java-25/src/test/java/com/baeldung/kdf/KdfApiJava25UnitTest.java @@ -0,0 +1,110 @@ +package com.baeldung.kdf; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.KDF; +import javax.crypto.SecretKey; +import javax.crypto.spec.HKDFParameterSpec; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the Key Derivation Function (KDF) API introduced in Java 25. + */ +class KdfApiUnitTest { + + private byte[] ikm; + private byte[] salt; + private byte[] info; + + @BeforeEach + void setUp() { + // Initialize sample input key material, salt, and info context + ikm = new byte[32]; + salt = "standard-salt".getBytes(); + info = "encryption-context".getBytes(); + } + + @Test + void givenHkdfAlgorithm_whenDeriveKeyForAes_thenReturnsValidSecretKey() + throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { + + // given + KDF kdf = KDF.getInstance("HKDF-SHA256"); + HKDFParameterSpec params = HKDFParameterSpec.ofExtract() + .addIKM(ikm) + .addSalt(salt) + .thenExpand(info, 32); + + // when + SecretKey aesKey = kdf.deriveKey("AES", params); + + // then + assertThat(aesKey).isNotNull(); + assertThat(aesKey.getAlgorithm()).isEqualTo("AES"); + assertThat(aesKey.getEncoded()).hasSize(32); + } + + @Test + void givenHkdfAlgorithm_whenDeriveData_thenReturnsRawBytes() + throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { + + // given + KDF kdf = KDF.getInstance("HKDF-SHA256"); + HKDFParameterSpec params = HKDFParameterSpec.ofExtract() + .addIKM(ikm) + .addSalt(salt) + .thenExpand(info, 64); + + // when + byte[] derivedData = kdf.deriveData(params); + + // then + assertThat(derivedData) + .isNotNull() + .hasSize(64); + } + + @Test + void givenExtractOnlyMode_whenDeriveKey_thenReturnsPseudorandomKey() + throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { + + // given + KDF kdf = KDF.getInstance("HKDF-SHA256"); + HKDFParameterSpec extractOnlyParams = HKDFParameterSpec.ofExtract() + .addIKM(ikm) + .addSalt(salt) + .extractOnly(); + + // when + SecretKey prk = kdf.deriveKey("HKDF-SHA256", extractOnlyParams); + + // then + assertThat(prk).isNotNull(); + assertThat(prk.getAlgorithm()).isEqualTo("HKDF-SHA256"); + } + + @Test + void givenInvalidAlgorithm_whenGetInstance_thenThrowsNoSuchAlgorithmException() { + // when & then + assertThatThrownBy(() -> KDF.getInstance("INVALID-KDF")) + .isInstanceOf(NoSuchAlgorithmException.class); + } + + @Test + void givenIncompatibleParameters_whenDeriveKey_thenThrowsException() + throws NoSuchAlgorithmException { + + // given + KDF kdf = KDF.getInstance("HKDF-SHA256"); + // Providing null or invalid spec + + // when & then + assertThatThrownBy(() -> kdf.deriveKey("AES", null)) + .isInstanceOf(InvalidAlgorithmParameterException.class); + } +}