diff --git a/src/java.base/share/classes/com/sun/crypto/provider/HKDFKeyDerivation.java b/src/java.base/share/classes/com/sun/crypto/provider/HKDFKeyDerivation.java
new file mode 100644
index 00000000000..a9988bbc115
--- /dev/null
+++ b/src/java.base/share/classes/com/sun/crypto/provider/HKDFKeyDerivation.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.crypto.provider;
+
+import javax.crypto.KDFSpi;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.HKDFParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.ByteArrayOutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import javax.crypto.KDFParameters;
+import java.security.NoSuchAlgorithmException;
+import java.security.ProviderException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * KDF implementation for the HKDF function.
+ *
+ * This class implements the HKDF-Extract and HKDF-Expand functions from RFC
+ * 5869. This implementation provides the complete Extract-then-Expand HKDF
+ * function as well as Extract-only and Expand-only variants.
+ *
+ * @spec https://www.rfc-editor.org/info/rfc5869
+ * RFC 5869: HMAC-based Extract-and-Expand Key Derivation Function (HKDF)
+ */
+abstract class HKDFKeyDerivation extends KDFSpi {
+
+ private final int hmacLen;
+ private final String hmacAlgName;
+
+ private enum SupportedHmac {
+ SHA256("HmacSHA256", 32),
+ SHA384("HmacSHA384", 48),
+ SHA512("HmacSHA512", 64);
+
+ private final String hmacAlg;
+ private final int hmacLen;
+ SupportedHmac(String hmacAlg, int hmacLen) {
+ this.hmacAlg = hmacAlg;
+ this.hmacLen = hmacLen;
+ }
+ };
+
+ /**
+ * The sole constructor.
+ *
+ * @param kdfParameters
+ * the initialization parameters (may be {@code null})
+ *
+ * @throws InvalidAlgorithmParameterException
+ * if the initialization parameters are inappropriate for this
+ * {@code KDFSpi}
+ */
+ private HKDFKeyDerivation(SupportedHmac supportedHmac,
+ KDFParameters kdfParameters)
+ throws InvalidAlgorithmParameterException {
+ super(kdfParameters);
+ if (kdfParameters != null) {
+ throw new InvalidAlgorithmParameterException(
+ supportedHmac.hmacAlg + " does not support parameters");
+ }
+ this.hmacAlgName = supportedHmac.hmacAlg;
+ this.hmacLen = supportedHmac.hmacLen;
+ }
+
+ /**
+ * Derive a key, returned as a {@code SecretKey} object.
+ *
+ * @return a derived {@code SecretKey} object of the specified algorithm
+ *
+ * @throws InvalidAlgorithmParameterException
+ * if the information contained within the {@code derivationSpec} is
+ * invalid or if the combination of {@code alg} and the
+ * {@code derivationSpec} results in something invalid
+ * @throws NoSuchAlgorithmException
+ * if {@code alg} is empty
+ * @throws NullPointerException
+ * if {@code alg} is {@code null}
+ */
+ @Override
+ protected SecretKey engineDeriveKey(String alg,
+ AlgorithmParameterSpec derivationSpec)
+ throws InvalidAlgorithmParameterException,
+ NoSuchAlgorithmException {
+
+ if (alg == null) {
+ throw new NullPointerException(
+ "the algorithm for the SecretKey return value must not be"
+ + " null");
+ }
+ if (alg.isEmpty()) {
+ throw new NoSuchAlgorithmException(
+ "the algorithm for the SecretKey return value must not be "
+ + "empty");
+ }
+
+ return new SecretKeySpec(engineDeriveData(derivationSpec), alg);
+
+ }
+
+ /**
+ * Obtain raw data from a key derivation function.
+ *
+ * @return a derived {@code byte[]}
+ *
+ * @throws InvalidAlgorithmParameterException
+ * if the information contained within the {@code KDFParameterSpec}
+ * is invalid or incorrect for the type of key to be derived
+ * @throws UnsupportedOperationException
+ * if the derived keying material is not extractable
+ */
+ @Override
+ protected byte[] engineDeriveData(AlgorithmParameterSpec derivationSpec)
+ throws InvalidAlgorithmParameterException {
+ List ikms, salts;
+ byte[] inputKeyMaterial, salt, pseudoRandomKey, info;
+ int length;
+ if (derivationSpec instanceof HKDFParameterSpec.Extract anExtract) {
+ ikms = anExtract.ikms();
+ salts = anExtract.salts();
+ // we should be able to combine both of the above Lists of key
+ // segments into one SecretKey object each, unless we were passed
+ // something bogus or an unexportable P11 key
+ inputKeyMaterial = null;
+ salt = null;
+ try {
+ inputKeyMaterial = consolidateKeyMaterial(ikms);
+ salt = consolidateKeyMaterial(salts);
+
+ // perform extract
+ return hkdfExtract(inputKeyMaterial, salt);
+ } catch (InvalidKeyException ike) {
+ throw new InvalidAlgorithmParameterException(
+ "an HKDF Extract could not be initialized with the "
+ + "given key or salt material", ike);
+ } catch (NoSuchAlgorithmException nsae) {
+ // This is bubbling up from the getInstance of the Mac/Hmac.
+ // Since we're defining these values internally, it is unlikely.
+ throw new ProviderException(
+ "could not instantiate a Mac with the provided "
+ + "algorithm",
+ nsae);
+ } finally {
+ if (inputKeyMaterial != null) {
+ Arrays.fill(inputKeyMaterial, (byte) 0x00);
+ }
+ if (salt != null) {
+ Arrays.fill(salt, (byte) 0x00);
+ }
+ }
+ } else if (derivationSpec instanceof HKDFParameterSpec.Expand anExpand) {
+ // set this value in the "if"
+ if ((pseudoRandomKey = anExpand.prk().getEncoded()) == null) {
+ throw new AssertionError(
+ "PRK is required for HKDFParameterSpec.Expand");
+ }
+ // set this value in the "if"
+ if ((info = anExpand.info()) == null) {
+ info = new byte[0];
+ }
+ length = anExpand.length();
+ if (length > (hmacLen * 255)) {
+ throw new InvalidAlgorithmParameterException(
+ "Requested length exceeds maximum allowed length");
+ }
+ // perform expand
+ try {
+ return hkdfExpand(pseudoRandomKey, info, length);
+ } catch (InvalidKeyException ike) {
+ throw new InvalidAlgorithmParameterException(
+ "an HKDF Expand could not be initialized with the "
+ + "given keying material", ike);
+ } catch (NoSuchAlgorithmException nsae) {
+ // This is bubbling up from the getInstance of the Mac/Hmac.
+ // Since we're defining these values internally, it is unlikely.
+ throw new ProviderException(
+ "could not instantiate a Mac with the provided "
+ + "algorithm",
+ nsae);
+ } finally {
+ Arrays.fill(pseudoRandomKey, (byte) 0x00);
+ }
+ } else if (derivationSpec instanceof HKDFParameterSpec.ExtractThenExpand anExtractThenExpand) {
+ ikms = anExtractThenExpand.ikms();
+ salts = anExtractThenExpand.salts();
+ // we should be able to combine both of the above Lists of key
+ // segments into one SecretKey object each, unless we were passed
+ // something bogus or an unexportable P11 key
+ inputKeyMaterial = null;
+ salt = null;
+ pseudoRandomKey = null;
+ try {
+ inputKeyMaterial = consolidateKeyMaterial(ikms);
+ salt = consolidateKeyMaterial(salts);
+
+ // set this value in the "if"
+ if ((info = anExtractThenExpand.info()) == null) {
+ info = new byte[0];
+ }
+ length = anExtractThenExpand.length();
+ if (length > (hmacLen * 255)) {
+ throw new InvalidAlgorithmParameterException(
+ "Requested length exceeds maximum allowed length");
+ }
+
+ // perform extract and then expand
+ pseudoRandomKey = hkdfExtract(inputKeyMaterial, salt);
+ return hkdfExpand(pseudoRandomKey, info, length);
+ } catch (InvalidKeyException ike) {
+ throw new InvalidAlgorithmParameterException(
+ "an HKDF ExtractThenExpand could not be initialized "
+ + "with the given key or salt material", ike);
+ } catch (NoSuchAlgorithmException nsae) {
+ // This is bubbling up from the getInstance of the Mac/HMAC.
+ // Since we're defining these values internally, it is unlikely.
+ throw new ProviderException(
+ "could not instantiate a Mac with the provided "
+ + "algorithm",
+ nsae);
+ } finally {
+ if (inputKeyMaterial != null) {
+ Arrays.fill(inputKeyMaterial, (byte) 0x00);
+ }
+ if (salt != null) {
+ Arrays.fill(salt, (byte) 0x00);
+ }
+ if (pseudoRandomKey != null) {
+ Arrays.fill(pseudoRandomKey, (byte) 0x00);
+ }
+ }
+ }
+ throw new InvalidAlgorithmParameterException(
+ "an HKDF derivation requires a valid HKDFParameterSpec");
+ }
+
+ // throws an InvalidKeyException if any key is unextractable
+ private byte[] consolidateKeyMaterial(List keys)
+ throws InvalidKeyException {
+ if (keys != null && !keys.isEmpty()) {
+ ArrayList localKeys = new ArrayList<>(keys);
+ if (localKeys.size() == 1) {
+ // return this element
+ SecretKey checkIt = localKeys.get(0);
+ return CipherCore.getKeyBytes(checkIt);
+ } else {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ for (SecretKey workItem : localKeys) {
+ os.writeBytes(CipherCore.getKeyBytes(workItem));
+ }
+ // deliberately omitting os.flush(), since we are writing to
+ // memory, and toByteArray() reads like there isn't an explicit
+ // need for this call
+ return os.toByteArray();
+ }
+ } else if (keys != null) {
+ return new byte[0];
+ } else {
+ throw new InvalidKeyException(
+ "List of key segments could not be consolidated");
+ }
+ }
+
+ /**
+ * Perform the HKDF-Extract operation.
+ *
+ * @param inputKeyMaterial
+ * the input keying material used for the HKDF-Extract operation.
+ * @param salt
+ * the salt value used for HKDF-Extract
+ *
+ * @return a byte array containing the pseudorandom key (PRK)
+ *
+ * @throws InvalidKeyException
+ * if an invalid salt was provided through the
+ * {@code HKDFParameterSpec}
+ */
+ private byte[] hkdfExtract(byte[] inputKeyMaterial, byte[] salt)
+ throws InvalidKeyException, NoSuchAlgorithmException {
+
+ // salt will not be null
+ if (salt.length == 0) {
+ salt = new byte[hmacLen];
+ }
+ Mac hmacObj = Mac.getInstance(hmacAlgName);
+ hmacObj.init(new SecretKeySpec(salt, hmacAlgName));
+
+ // inputKeyMaterial will not be null
+ return hmacObj.doFinal(inputKeyMaterial);
+ }
+
+ /**
+ * Perform the HKDF-Expand operation.
+ *
+ * @param prk
+ * the pseudorandom key used for HKDF-Expand
+ * @param info
+ * optional context and application specific information or
+ * {@code null} if no info data is provided.
+ * @param outLen
+ * the length in bytes of the required output
+ *
+ * @return a byte array containing the complete {@code KDF} output. This
+ * will be at least as long as the requested length in the
+ * {@code outLen} parameter, but will be rounded up to the nearest
+ * multiple of the HMAC output length.
+ *
+ * @throws InvalidKeyException
+ * if an invalid PRK was provided through the
+ * {@code HKDFParameterSpec} or derived during the extract phase.
+ */
+ private byte[] hkdfExpand(byte[] prk, byte[] info, int outLen)
+ throws InvalidKeyException, NoSuchAlgorithmException {
+ byte[] kdfOutput;
+
+ if (prk == null || prk.length < hmacLen) {
+ throw new InvalidKeyException(
+ "prk must be at least " + hmacLen + " bytes");
+ }
+
+ SecretKey pseudoRandomKey = new SecretKeySpec(prk, hmacAlgName);
+
+ Mac hmacObj = Mac.getInstance(hmacAlgName);
+
+ // Calculate the number of rounds of HMAC that are needed to
+ // meet the requested data. Then set up the buffers we will need.
+ hmacObj.init(pseudoRandomKey);
+ int rounds = (outLen + hmacLen - 1) / hmacLen;
+ kdfOutput = new byte[outLen];
+ int i = 0;
+ int offset = 0;
+ try {
+ while (i < rounds) {
+ if (i > 0) {
+ hmacObj.update(kdfOutput, offset - hmacLen,
+ hmacLen); // add T(i-1)
+ }
+ hmacObj.update(info); // Add info
+ hmacObj.update((byte) ++i); // Add round number
+ if (i == rounds && (outLen - offset < hmacLen)) {
+ // special handling for last chunk
+ byte[] tmp = hmacObj.doFinal();
+ System.arraycopy(tmp, 0, kdfOutput, offset,
+ outLen - offset);
+ Arrays.fill(tmp, (byte) 0x00);
+ offset = outLen;
+ } else {
+ hmacObj.doFinal(kdfOutput, offset);
+ offset += hmacLen;
+ }
+ }
+ } catch (ShortBufferException sbe) {
+ // This really shouldn't happen given that we've
+ // sized the buffers to their largest possible size up-front,
+ // but just in case...
+ throw new ProviderException(sbe);
+ }
+ return kdfOutput;
+ }
+
+ protected KDFParameters engineGetParameters() {
+ return null;
+ }
+
+ public static final class HKDFSHA256 extends HKDFKeyDerivation {
+ public HKDFSHA256(KDFParameters kdfParameters)
+ throws InvalidAlgorithmParameterException {
+ super(SupportedHmac.SHA256, kdfParameters);
+ }
+ }
+
+ public static final class HKDFSHA384 extends HKDFKeyDerivation {
+ public HKDFSHA384(KDFParameters kdfParameters)
+ throws InvalidAlgorithmParameterException {
+ super(SupportedHmac.SHA384, kdfParameters);
+ }
+ }
+
+ public static final class HKDFSHA512 extends HKDFKeyDerivation {
+ public HKDFSHA512(KDFParameters kdfParameters)
+ throws InvalidAlgorithmParameterException {
+ super(SupportedHmac.SHA512, kdfParameters);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java
index 9bb1fa7ff82..c0766077ba9 100644
--- a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java
+++ b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java
@@ -457,6 +457,16 @@ public final class SunJCE extends Provider {
"com.sun.crypto.provider.DHKeyAgreement",
attrs);
+ /*
+ * Key Derivation engines
+ */
+ ps("KDF", "HKDF-SHA256",
+ "com.sun.crypto.provider.HKDFKeyDerivation$HKDFSHA256");
+ ps("KDF", "HKDF-SHA384",
+ "com.sun.crypto.provider.HKDFKeyDerivation$HKDFSHA384");
+ ps("KDF", "HKDF-SHA512",
+ "com.sun.crypto.provider.HKDFKeyDerivation$HKDFSHA512");
+
/*
* Algorithm Parameter engines
*/
diff --git a/src/java.base/share/classes/java/security/Provider.java b/src/java.base/share/classes/java/security/Provider.java
index adfbd9a5957..c4dac74c680 100644
--- a/src/java.base/share/classes/java/security/Provider.java
+++ b/src/java.base/share/classes/java/security/Provider.java
@@ -27,6 +27,7 @@ package java.security;
import jdk.internal.event.SecurityProviderServiceEvent;
+import javax.crypto.KDFParameters;
import javax.security.auth.login.Configuration;
import java.io.*;
import java.security.cert.CertStoreParameters;
@@ -1604,6 +1605,7 @@ public abstract class Provider extends Properties {
addEngine("KeyGenerator", false, null);
addEngine("SecretKeyFactory", false, null);
addEngine("KEM", true, null);
+ addEngine("KDF", false, KDFParameters.class);
// JSSE
addEngine("KeyManagerFactory", false, null);
addEngine("SSLContext", false, null);
diff --git a/src/java.base/share/classes/javax/crypto/KDF.java b/src/java.base/share/classes/javax/crypto/KDF.java
new file mode 100644
index 00000000000..ea2a0375813
--- /dev/null
+++ b/src/java.base/share/classes/javax/crypto/KDF.java
@@ -0,0 +1,681 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package javax.crypto;
+
+import jdk.internal.javac.PreviewFeature;
+import sun.security.jca.GetInstance;
+import sun.security.jca.GetInstance.Instance;
+import sun.security.util.Debug;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Security;
+import java.security.Provider;
+import java.security.Provider.Service;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Iterator;
+import java.util.Objects;
+
+/**
+ * This class provides the functionality of a Key Derivation Function (KDF),
+ * which is a cryptographic algorithm for deriving additional keys from input
+ * keying material (IKM) and (optionally) other data.
+ *
+ * {@code KDF} objects are instantiated with the {@code getInstance} family of
+ * methods.
+ *
+ * The class has two derive methods, {@code deriveKey} and {@code deriveData}.
+ * The {@code deriveKey} method accepts an algorithm name and returns a
+ * {@code SecretKey} object with the specified algorithm. The {@code deriveData}
+ * method returns a byte array of raw data.
+ *
+ * Unless otherwise documented by an implementation, the methods defined in this
+ * class are not thread-safe. Multiple threads that need to access a single
+ * object concurrently should synchronize amongst themselves and provide the
+ * necessary locking. Multiple threads each manipulating separate objects need
+ * not synchronize.
+ *
+ *
+ * If a provider is not specified when calling one of the {@code getInstance}
+ * methods, the implementation delays the selection of the provider until the
+ * {@code deriveKey} or {@code deriveData} method is called. This is called
+ * delayed provider selection. The primary reason this is done is to
+ * ensure that the selected provider can handle the key material that is passed
+ * to those methods - for example, the key material may reside on a hardware
+ * device that only a specific {@code KDF} provider can utilize. The {@code
+ * getInstance} method returns a {@code KDF} object as long as there exists
+ * at least one registered security provider that implements the algorithm
+ * and supports the optional parameters. The delayed provider selection
+ * process traverses the list of registered security providers, starting with
+ * the most preferred {@code Provider}. The first provider that supports the
+ * specified algorithm, optional parameters, and key material is selected.
+ *
+ * If the {@code getProviderName} or {@code getParameters} method is called
+ * before the {@code deriveKey} or {@code deriveData} methods, the first
+ * provider supporting the {@code KDF} algorithm and optional
+ * {@code KDFParameters} is chosen. This provider may not support the key
+ * material that is subsequently passed to the {@code deriveKey} or
+ * {@code deriveData} methods. Therefore, it is recommended not to call the
+ * {@code getProviderName} or {@code getParameters} methods until after a key
+ * derivation operation. Once a provider is selected, it cannot be changed.
+ *
+ * @see KDFParameters
+ * @see SecretKey
+ * @since 24
+ */
+@PreviewFeature(feature = PreviewFeature.Feature.KEY_DERIVATION)
+public final class KDF {
+
+ private static final Debug pdebug = Debug.getInstance("provider",
+ "Provider");
+ private static final boolean skipDebug = Debug.isOn("engine=")
+ && !Debug.isOn("kdf");
+
+ private record Delegate(KDFSpi spi, Provider provider) {}
+
+ //guarded by 'lock'
+ private Delegate theOne;
+ //guarded by 'lock'
+ private final Delegate candidate;
+
+ // The name of the KDF algorithm.
+ private final String algorithm;
+
+ // Additional KDF configuration parameters
+ private final KDFParameters kdfParameters;
+
+ // remaining services to try in provider selection
+ // null once provider is selected
+ private final Iterator serviceIterator;
+
+ // This lock provides mutual exclusion, preventing multiple threads from
+ // concurrently initializing the same instance (delayed provider selection)
+ // in a way which would corrupt the internal state.
+ private final Object lock = new Object();
+
+
+ // Instantiates a {@code KDF} object. This constructor is called when a
+ // provider is supplied to {@code getInstance}.
+ //
+ // @param delegate the delegate
+ // @param algorithm the algorithm
+ // @param kdfParameters the parameters
+ private KDF(Delegate delegate, String algorithm) {
+ this.theOne = delegate;
+ this.algorithm = algorithm;
+ // note that the parameters are being passed to the impl in getInstance
+ this.kdfParameters = null;
+ this.candidate = null;
+ serviceIterator = null;
+ }
+
+ // Instantiates a {@code KDF} object. This constructor is called when a
+ // provider is not supplied to {@code getInstance}.
+ //
+ // @param firstPairOfSpiAndProv the delegate
+ // @param t the service iterator
+ // @param algorithm the algorithm
+ // @param kdfParameters the algorithm parameters
+ private KDF(Delegate firstPairOfSpiAndProv, Iterator t,
+ String algorithm,
+ KDFParameters kdfParameters) {
+ this.candidate = firstPairOfSpiAndProv;
+ serviceIterator = t;
+ this.algorithm = algorithm;
+ this.kdfParameters = kdfParameters;
+ }
+
+ /**
+ * Returns the algorithm name of this {@code KDF} object.
+ *
+ * @return the algorithm name of this {@code KDF} object
+ */
+ public String getAlgorithm() {
+ return this.algorithm;
+ }
+
+ /**
+ * Returns the name of the provider.
+ *
+ * @return the name of the provider
+ *
+ * @see Delayed Provider
+ * Selection
+ */
+ public String getProviderName() {
+ useFirstSpi();
+ return theOne.provider().getName();
+ }
+
+ /**
+ * Returns the {@code KDFParameters} used with this {@code KDF} object.
+ *
+ * The returned parameters may be the same that were used to initialize
+ * this {@code KDF} object, or may contain additional default or
+ * random parameter values used by the underlying KDF algorithm.
+ * If the required parameters were not supplied and can be generated by
+ * the {@code KDF} object, the generated parameters are returned;
+ * otherwise {@code null} is returned.
+ *
+ * @return the parameters used with this {@code KDF} object, or
+ * {@code null}
+ *
+ * @see Delayed Provider
+ * Selection
+ */
+ public KDFParameters getParameters() {
+ useFirstSpi();
+ return theOne.spi().engineGetParameters();
+ }
+
+ /**
+ * Returns a {@code KDF} object that implements the specified algorithm.
+ *
+ * @implNote The JDK Reference Implementation additionally uses the
+ * {@code jdk.security.provider.preferred}
+ * {@link Security#getProperty(String) Security} property to
+ * determine the preferred provider order for the specified
+ * algorithm. This may be different than the order of providers
+ * returned by
+ * {@link Security#getProviders() Security.getProviders()}.
+ *
+ * @param algorithm
+ * the key derivation algorithm to use. See the {@code KDF} section
+ * in the
+ * Java Security Standard Algorithm Names Specification for
+ * information about standard KDF algorithm names.
+ *
+ * @return a {@code KDF} object
+ *
+ * @throws NoSuchAlgorithmException
+ * if no {@code Provider} supports a {@code KDF} implementation for
+ * the specified algorithm
+ * @throws NullPointerException
+ * if {@code algorithm} is {@code null}
+ * @see Delayed Provider
+ * Selection
+ */
+ public static KDF getInstance(String algorithm)
+ throws NoSuchAlgorithmException {
+ Objects.requireNonNull(algorithm, "algorithm must not be null");
+ try {
+ return getInstance(algorithm, (KDFParameters) null);
+ } catch (InvalidAlgorithmParameterException e) {
+ throw new NoSuchAlgorithmException(
+ "No implementation found using null KDFParameters", e);
+ }
+ }
+
+ /**
+ * Returns a {@code KDF} object that implements the specified algorithm from
+ * the specified security provider. The specified provider must be
+ * registered in the security provider list.
+ *
+ * @param algorithm
+ * the key derivation algorithm to use. See the {@code KDF} section
+ * in the
+ * Java Security Standard Algorithm Names Specification for
+ * information about standard KDF algorithm names.
+ * @param provider
+ * the provider to use for this key derivation
+ *
+ * @return a {@code KDF} object
+ *
+ * @throws NoSuchAlgorithmException
+ * if the specified provider does not support the specified
+ * {@code KDF} algorithm
+ * @throws NoSuchProviderException
+ * if the specified provider is not registered in the security
+ * provider list
+ * @throws NullPointerException
+ * if {@code algorithm} or {@code provider} is {@code null}
+ */
+ public static KDF getInstance(String algorithm, String provider)
+ throws NoSuchAlgorithmException, NoSuchProviderException {
+ Objects.requireNonNull(algorithm, "algorithm must not be null");
+ Objects.requireNonNull(provider, "provider must not be null");
+ try {
+ return getInstance(algorithm, null, provider);
+ } catch (InvalidAlgorithmParameterException e) {
+ throw new NoSuchAlgorithmException(
+ "No implementation found using null KDFParameters", e);
+ }
+ }
+
+ /**
+ * Returns a {@code KDF} object that implements the specified algorithm from
+ * the specified security provider.
+ *
+ * @param algorithm
+ * the key derivation algorithm to use. See the {@code KDF} section
+ * in the
+ * Java Security Standard Algorithm Names Specification for
+ * information about standard KDF algorithm names.
+ * @param provider
+ * the provider to use for this key derivation
+ *
+ * @return a {@code KDF} object
+ *
+ * @throws NoSuchAlgorithmException
+ * if the specified provider does not support the specified
+ * {@code KDF} algorithm
+ * @throws NullPointerException
+ * if {@code algorithm} or {@code provider} is {@code null}
+ */
+ public static KDF getInstance(String algorithm, Provider provider)
+ throws NoSuchAlgorithmException {
+ Objects.requireNonNull(algorithm, "algorithm must not be null");
+ Objects.requireNonNull(provider, "provider must not be null");
+ try {
+ return getInstance(algorithm, null, provider);
+ } catch (InvalidAlgorithmParameterException e) {
+ throw new NoSuchAlgorithmException(
+ "No implementation found using null KDFParameters", e);
+ }
+ }
+
+ /**
+ * Returns a {@code KDF} object that implements the specified algorithm and
+ * is initialized with the specified parameters.
+ *
+ * @implNote The JDK Reference Implementation additionally uses the
+ * {@code jdk.security.provider.preferred}
+ * {@link Security#getProperty(String) Security} property to
+ * determine the preferred provider order for the specified
+ * algorithm. This may be different than the order of providers
+ * returned by
+ * {@link Security#getProviders() Security.getProviders()}.
+ *
+ * @param algorithm
+ * the key derivation algorithm to use. See the {@code KDF} section
+ * in the
+ * Java Security Standard Algorithm Names Specification for
+ * information about standard KDF algorithm names.
+ * @param kdfParameters
+ * the {@code KDFParameters} used to configure the derivation
+ * algorithm or {@code null} if no parameters are provided
+ *
+ * @return a {@code KDF} object
+ *
+ * @throws NoSuchAlgorithmException
+ * if no {@code Provider} supports a {@code KDF} implementation for
+ * the specified algorithm
+ * @throws InvalidAlgorithmParameterException
+ * if at least one {@code Provider} supports a {@code KDF}
+ * implementation for the specified algorithm but none of them
+ * support the specified parameters
+ * @throws NullPointerException
+ * if {@code algorithm} is {@code null}
+ * @see Delayed Provider
+ * Selection
+ */
+ public static KDF getInstance(String algorithm,
+ KDFParameters kdfParameters)
+ throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
+ Objects.requireNonNull(algorithm, "algorithm must not be null");
+ // make sure there is at least one service from a signed provider
+ Iterator t = GetInstance.getServices("KDF", algorithm);
+
+ Delegate d = getNext(t, kdfParameters);
+ return (t.hasNext() ?
+ new KDF(d, t, algorithm, kdfParameters) :
+ new KDF(d, algorithm));
+ }
+
+ /**
+ * Returns a {@code KDF} object that implements the specified algorithm from
+ * the specified provider and is initialized with the specified parameters.
+ * The specified provider must be registered in the security provider list.
+ *
+ * @param algorithm
+ * the key derivation algorithm to use. See the {@code KDF} section
+ * in the
+ * Java Security Standard Algorithm Names Specification for
+ * information about standard KDF algorithm names.
+ * @param kdfParameters
+ * the {@code KDFParameters} used to configure the derivation
+ * algorithm or {@code null} if no parameters are provided
+ * @param provider
+ * the provider to use for this key derivation
+ *
+ * @return a {@code KDF} object
+ *
+ * @throws NoSuchAlgorithmException
+ * if the specified provider does not support the specified
+ * {@code KDF} algorithm
+ * @throws NoSuchProviderException
+ * if the specified provider is not registered in the security
+ * provider list
+ * @throws InvalidAlgorithmParameterException
+ * if the specified provider supports the specified {@code KDF}
+ * algorithm but does not support the specified parameters
+ * @throws NullPointerException
+ * if {@code algorithm} or {@code provider} is {@code null}
+ */
+ public static KDF getInstance(String algorithm,
+ KDFParameters kdfParameters,
+ String provider)
+ throws NoSuchAlgorithmException, NoSuchProviderException,
+ InvalidAlgorithmParameterException {
+ Objects.requireNonNull(algorithm, "algorithm must not be null");
+ Objects.requireNonNull(provider, "provider must not be null");
+
+ Instance instance = GetInstance.getInstance("KDF", KDFSpi.class,
+ algorithm,
+ kdfParameters,
+ provider);
+ if (!JceSecurity.canUseProvider(instance.provider)) {
+ String msg = "JCE cannot authenticate the provider "
+ + instance.provider.getName();
+ throw new NoSuchProviderException(msg);
+ }
+ return new KDF(new Delegate((KDFSpi) instance.impl,
+ instance.provider), algorithm
+ );
+ }
+
+ /**
+ * Returns a {@code KDF} object that implements the specified algorithm from
+ * the specified provider and is initialized with the specified parameters.
+ *
+ * @param algorithm
+ * the key derivation algorithm to use. See the {@code KDF} section
+ * in the
+ * Java Security Standard Algorithm Names Specification for
+ * information about standard KDF algorithm names.
+ * @param kdfParameters
+ * the {@code KDFParameters} used to configure the derivation
+ * algorithm or {@code null} if no parameters are provided
+ * @param provider
+ * the provider to use for this key derivation
+ *
+ * @return a {@code KDF} object
+ *
+ * @throws NoSuchAlgorithmException
+ * if the specified provider does not support the specified
+ * {@code KDF} algorithm
+ * @throws InvalidAlgorithmParameterException
+ * if the specified provider supports the specified {@code KDF}
+ * algorithm but does not support the specified parameters
+ * @throws NullPointerException
+ * if {@code algorithm} or {@code provider} is {@code null}
+ */
+ public static KDF getInstance(String algorithm,
+ KDFParameters kdfParameters,
+ Provider provider)
+ throws NoSuchAlgorithmException,
+ InvalidAlgorithmParameterException {
+ Objects.requireNonNull(algorithm, "algorithm must not be null");
+ Objects.requireNonNull(provider, "provider must not be null");
+ Instance instance = GetInstance.getInstance("KDF", KDFSpi.class,
+ algorithm,
+ kdfParameters,
+ provider);
+ if (!JceSecurity.canUseProvider(instance.provider)) {
+ String msg = "JCE cannot authenticate the provider "
+ + instance.provider.getName();
+ throw new SecurityException(msg);
+ }
+ return new KDF(new Delegate((KDFSpi) instance.impl,
+ instance.provider), algorithm
+ );
+ }
+
+ /**
+ * Derives a key, returned as a {@code SecretKey} object.
+ *
+ * @param alg
+ * the algorithm of the resultant {@code SecretKey} object
+ * @param derivationSpec
+ * the object describing the inputs to the derivation function
+ *
+ * @return the derived key
+ *
+ * @throws InvalidAlgorithmParameterException
+ * if the information contained within the {@code derivationSpec} is
+ * invalid or if the combination of {@code alg} and the
+ * {@code derivationSpec} results in something invalid
+ * @throws NoSuchAlgorithmException
+ * if {@code alg} is empty or invalid
+ * @throws NullPointerException
+ * if {@code alg} or {@code derivationSpec} is null
+ *
+ * @see Delayed Provider
+ * Selection
+ *
+ */
+ public SecretKey deriveKey(String alg,
+ AlgorithmParameterSpec derivationSpec)
+ throws InvalidAlgorithmParameterException,
+ NoSuchAlgorithmException {
+ if (alg == null) {
+ throw new NullPointerException(
+ "the algorithm for the SecretKey return value must not be"
+ + " null");
+ }
+ if (alg.isEmpty()) {
+ throw new NoSuchAlgorithmException(
+ "the algorithm for the SecretKey return value must not be "
+ + "empty");
+ }
+ Objects.requireNonNull(derivationSpec);
+ if (checkSpiNonNull(theOne)) {
+ return theOne.spi().engineDeriveKey(alg, derivationSpec);
+ } else {
+ return (SecretKey) chooseProvider(alg, derivationSpec);
+ }
+ }
+
+ /**
+ * Derives a key, returns raw data as a byte array.
+ *
+ * @param derivationSpec
+ * the object describing the inputs to the derivation function
+ *
+ * @return the derived key in its raw bytes
+ *
+ * @throws InvalidAlgorithmParameterException
+ * if the information contained within the {@code derivationSpec} is
+ * invalid
+ * @throws UnsupportedOperationException
+ * if the derived keying material is not extractable
+ * @throws NullPointerException
+ * if {@code derivationSpec} is null
+ *
+ * @see Delayed Provider
+ * Selection
+ *
+ */
+ public byte[] deriveData(AlgorithmParameterSpec derivationSpec)
+ throws InvalidAlgorithmParameterException {
+
+ Objects.requireNonNull(derivationSpec);
+ if (checkSpiNonNull(theOne)) {
+ return theOne.spi().engineDeriveData(derivationSpec);
+ } else {
+ try {
+ return (byte[]) chooseProvider(null, derivationSpec);
+ } catch (NoSuchAlgorithmException e) {
+ // this will never be thrown in the deriveData case
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ /**
+ * Use the firstSpi as the chosen KDFSpi and set the fields accordingly
+ */
+ private void useFirstSpi() {
+ if (checkSpiNonNull(theOne)) return;
+
+ synchronized (lock) {
+ if (!checkSpiNonNull(theOne)) {
+ theOne = candidate;
+ }
+ }
+ }
+
+ /**
+ * Selects the provider which supports the passed {@code algorithm} and
+ * {@code derivationSpec} values, and assigns the global spi and provider
+ * variables if they have not been assigned yet.
+ *
+ * If the spi has already been set, it will just return the result.
+ */
+ private Object chooseProvider(String algorithm,
+ AlgorithmParameterSpec derivationSpec)
+ throws InvalidAlgorithmParameterException,
+ NoSuchAlgorithmException {
+
+ boolean isDeriveData = (algorithm == null);
+
+ synchronized (lock) {
+ if (checkSpiNonNull(theOne)) {
+ return (isDeriveData) ?
+ theOne.spi().engineDeriveData(derivationSpec) :
+ theOne.spi().engineDeriveKey(algorithm, derivationSpec);
+ }
+
+ Exception lastException = null;
+ if (!checkSpiNonNull(candidate)) {
+ throw new AssertionError("Unexpected Error: candidate is null!");
+ }
+ Delegate currOne = candidate;
+ try {
+ while (true) {
+ try {
+ Object result = (isDeriveData) ?
+ currOne.spi().engineDeriveData(derivationSpec) :
+ currOne.spi().engineDeriveKey(algorithm,
+ derivationSpec);
+ // found a working KDFSpi
+ this.theOne = currOne;
+ return result;
+ } catch (Exception e) {
+ if (!skipDebug && pdebug != null) {
+ pdebug.println("A " + this.getAlgorithm()
+ + " derivation cannot be performed "
+ + "using the supplied derivation "
+ + "inputs, using "
+ + currOne.provider().getName()
+ + ". Another Provider will be "
+ + "attempted.");
+ e.printStackTrace(pdebug.getPrintStream());
+ }
+ if (lastException == null) {
+ lastException = e;
+ }
+ // try next one if available
+ assert serviceIterator != null : "serviceIterator was null";
+ currOne = getNext(serviceIterator, kdfParameters);
+ }
+ }
+ } catch (InvalidAlgorithmParameterException e) {
+ throw e; // getNext reached end and have seen IAPE
+ } catch (NoSuchAlgorithmException e) {
+ if (!skipDebug && pdebug != null) {
+ pdebug.println(
+ "All available Providers have been examined "
+ + "without a match for performing the "
+ + this.getAlgorithm()
+ + " derivation using the supplied derivation "
+ + "inputs. Therefore, the caught "
+ + "NoSuchAlgorithmException will be logged, and "
+ + "an InvalidAlgorithmParameterException will "
+ + "then be thrown with the last known Exception.");
+ e.printStackTrace(pdebug.getPrintStream());
+ }
+ // getNext reached end without finding an implementation
+ throw new InvalidAlgorithmParameterException(lastException);
+ }
+ }
+ }
+
+ private static Delegate getNext(Iterator serviceIter,
+ KDFParameters kdfParameters)
+ throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
+ // fetch next one if available
+ boolean hasOne = false;
+ while (serviceIter.hasNext()) {
+ Service s = serviceIter.next();
+ Provider prov = s.getProvider();
+ if (!JceSecurity.canUseProvider(prov)) {
+ // continue to next iteration
+ continue;
+ }
+ hasOne = true;
+ try {
+ Object obj = s.newInstance(kdfParameters);
+ if (!(obj instanceof KDFSpi)) {
+ if (!skipDebug && pdebug != null) {
+ pdebug.println(
+ "obj was not an instance of KDFSpi (should not "
+ + "happen)");
+ }
+ continue;
+ }
+ return new Delegate((KDFSpi) obj, prov);
+ } catch (NoSuchAlgorithmException nsae) {
+ // continue to the next provider
+ if (!skipDebug && pdebug != null) {
+ pdebug.println("A derivation cannot be performed "
+ + "using the supplied KDFParameters, using "
+ + prov.getName()
+ + ". Another Provider will be attempted.");
+ nsae.printStackTrace(pdebug.getPrintStream());
+ }
+ }
+ }
+ if (!skipDebug && pdebug != null) {
+ pdebug.println(
+ "No provider can be found that supports the "
+ + "specified algorithm and parameters");
+ }
+ if (hasOne) throw new InvalidAlgorithmParameterException(
+ "The KDFParameters supplied could not be used in combination "
+ + "with the supplied algorithm for the selected Provider");
+ else throw new NoSuchAlgorithmException();
+ }
+
+ private static boolean checkSpiNonNull(Delegate d) {
+ // d.spi() cannot be null if d != null
+ return (d != null);
+ }
+}
diff --git a/src/java.base/share/classes/javax/crypto/KDFParameters.java b/src/java.base/share/classes/javax/crypto/KDFParameters.java
new file mode 100644
index 00000000000..5f83204f3c4
--- /dev/null
+++ b/src/java.base/share/classes/javax/crypto/KDFParameters.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package javax.crypto;
+
+import jdk.internal.javac.PreviewFeature;
+
+/**
+ * A specification of Key Derivation Function ({@link KDF}) parameters.
+ *
+ * The purpose of this interface is to group (and provide type safety for) all
+ * {@code KDF} parameter specifications. All {@code KDF} parameter
+ * specifications must implement this interface.
+ *
+ * When supplied, the
+ * {@link KDF#getInstance(String, KDFParameters) KDF.getInstance} methods return
+ * a {@code KDF} that is initialized with the specified parameters.
+ *
+ * The {@code KDFParameters} used for initialization are returned by
+ * {@link KDF#getParameters()} and may contain additional default or random
+ * parameter values used by the underlying KDF implementation.
+ *
+ * @see KDF#getInstance(String, KDFParameters)
+ * @see KDF#getParameters()
+ * @see KDF
+ * @since 24
+ */
+@PreviewFeature(feature = PreviewFeature.Feature.KEY_DERIVATION)
+public interface KDFParameters {}
diff --git a/src/java.base/share/classes/javax/crypto/KDFSpi.java b/src/java.base/share/classes/javax/crypto/KDFSpi.java
new file mode 100644
index 00000000000..dcd2029c0c0
--- /dev/null
+++ b/src/java.base/share/classes/javax/crypto/KDFSpi.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package javax.crypto;
+
+import jdk.internal.javac.PreviewFeature;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * This class defines the Service Provider Interface (SPI) for the
+ * Key Derivation Function ({@link KDF}) class.
+ *
+ * All the abstract methods in this class must be implemented by each
+ * cryptographic service provider who wishes to supply the implementation of a
+ * particular key derivation function algorithm.
+ *
+ * Implementations must provide a public constructor which accepts a {@code
+ * KDFParameters} object if they depend on the default implementation of
+ * {@code Provider.Service.newInstance} to construct {@code KDFSpi} instances.
+ * The constructor must call {@code super(params)} passing the parameters
+ * supplied. The constructor must also throw an
+ * {@code InvalidAlgorithmParameterException} if the supplied parameters are
+ * inappropriate. If a {@code KDF} object is instantiated with one of the
+ * {@code getInstance} methods that contains a {@code KDFParameters} parameter,
+ * the user-provided {@code KDFParameters} object will be passed to the
+ * constructor of the {@code KDFSpi} implementation. Otherwise, if it is
+ * instantiated with one of the {@code getInstance} methods without a
+ * {@code KDFParameters} parameter, a {@code null} value will be passed to the
+ * constructor.
+ *
+ * Implementations which do not support {@code KDFParameters} must require
+ * {@code null} to be passed, otherwise an
+ * {@code InvalidAlgorithmParameterException} will be thrown. On the other hand,
+ * implementations which require {@code KDFParameters} should throw an
+ * {@code InvalidAlgorithmParameterException} upon receiving a {@code null}
+ * value if default parameters cannot be generated or upon receiving {@code
+ * KDFParameters} which are not supported by the implementation.
+ *
+ * To aid the caller, implementations may return parameters with additional
+ * default values or supply random values as used by the underlying {@code KDF}
+ * algorithm. See {@link KDFSpi#engineGetParameters()} for more details.
+ *
+ * @see KDF
+ * @see KDFParameters
+ * @see KDF#getParameters()
+ * @see SecretKey
+ * @since 24
+ */
+@PreviewFeature(feature = PreviewFeature.Feature.KEY_DERIVATION)
+public abstract class KDFSpi {
+
+ /**
+ * The sole constructor.
+ *
+ * A {@code KDFParameters} object may be specified for KDF algorithms that
+ * support initialization parameters.
+ *
+ * @param kdfParameters
+ * the initialization parameters for the {@code KDF} algorithm (may
+ * be {@code null})
+ *
+ * @throws InvalidAlgorithmParameterException
+ * if the initialization parameters are inappropriate for this
+ * {@code KDFSpi}
+ * @see KDF#getParameters()
+ */
+ protected KDFSpi(KDFParameters kdfParameters)
+ throws InvalidAlgorithmParameterException {}
+
+ /**
+ * Returns the {@code KDFParameters} used with this {@code KDF} object.
+ *
+ * The returned parameters may be the same that were used to initialize
+ * this {@code KDF} object, or may contain additional default or
+ * random parameter values used by the underlying KDF algorithm.
+ * If the required parameters were not supplied and can be generated by
+ * the {@code KDF} object, the generated parameters are returned;
+ * otherwise {@code null} is returned.
+ *
+ * @return the parameters used with this {@code KDF} object, or
+ * {@code null}
+ */
+ protected abstract KDFParameters engineGetParameters();
+
+ /**
+ * Derives a key, returned as a {@code SecretKey} object.
+ *
+ * @implNote If the resultant key is extractable, then its
+ * {@code getEncoded} value should have the same content as the
+ * result of {@code deriveData}.
+ *
+ * @param alg
+ * the algorithm of the resultant {@code SecretKey} object
+ * @param derivationSpec
+ * derivation parameters
+ *
+ * @return the derived key.
+ *
+ * @throws InvalidAlgorithmParameterException
+ * if the information contained within the {@code derivationSpec} is
+ * invalid or if the combination of {@code alg} and the
+ * {@code derivationSpec} results in something invalid
+ * @throws NoSuchAlgorithmException
+ * if {@code alg} is empty or invalid
+ * @throws NullPointerException
+ * if {@code alg} or {@code derivationSpec} is null
+ */
+ protected abstract SecretKey engineDeriveKey(String alg,
+ AlgorithmParameterSpec derivationSpec)
+ throws InvalidAlgorithmParameterException, NoSuchAlgorithmException;
+
+ /**
+ * Derives a key, returns raw data as a byte array.
+ *
+ * @param derivationSpec
+ * derivation parameters
+ *
+ * @return the derived key in its raw bytes.
+ *
+ * @throws InvalidAlgorithmParameterException
+ * if the information contained within the {@code derivationSpec} is
+ * invalid
+ * @throws UnsupportedOperationException
+ * if the derived keying material is not extractable
+ * @throws NullPointerException
+ * if {@code derivationSpec} is null
+ */
+ protected abstract byte[] engineDeriveData(
+ AlgorithmParameterSpec derivationSpec)
+ throws InvalidAlgorithmParameterException;
+
+}
\ No newline at end of file
diff --git a/src/java.base/share/classes/javax/crypto/spec/HKDFParameterSpec.java b/src/java.base/share/classes/javax/crypto/spec/HKDFParameterSpec.java
new file mode 100644
index 00000000000..8f697d12e60
--- /dev/null
+++ b/src/java.base/share/classes/javax/crypto/spec/HKDFParameterSpec.java
@@ -0,0 +1,510 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package javax.crypto.spec;
+
+import jdk.internal.javac.PreviewFeature;
+
+import javax.crypto.SecretKey;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Parameters for the combined Extract, Expand, or Extract-then-Expand
+ * operations of the HMAC-based Key Derivation Function (HKDF). The HKDF
+ * function is defined in RFC
+ * 5869.
+ *
+ * In the Extract and Extract-then-Expand cases, users may call the {@code
+ * addIKM} and/or {@code addSalt} methods repeatedly (and chain these calls).
+ * This provides for use-cases where a portion of the input keying material
+ * (IKM) resides in a non-extractable {@code SecretKey} and the whole IKM
+ * cannot be provided as a single object. The same feature is available for
+ * salts.
+ *
+ * The above feature is particularly useful for "labeled" HKDF Extract used in
+ * TLS 1.3 and HPKE, where the IKM consists of concatenated components, which
+ * may include both byte arrays and (possibly non-extractable) secret keys.
+ *
+ * Examples:
+ * {@snippet lang = java:
+ * // this usage depicts the initialization of an HKDF-Extract AlgorithmParameterSpec
+ * AlgorithmParameterSpec derivationSpec =
+ * HKDFParameterSpec.ofExtract()
+ * .addIKM(label)
+ * .addIKM(ikm)
+ * .addSalt(salt).extractOnly();
+ *}
+ * {@snippet lang = java:
+ * // this usage depicts the initialization of an HKDF-Expand AlgorithmParameterSpec
+ * AlgorithmParameterSpec derivationSpec =
+ * HKDFParameterSpec.expandOnly(prk, info, 32);
+ *}
+ * {@snippet lang = java:
+ * // this usage depicts the initialization of an HKDF-ExtractExpand AlgorithmParameterSpec
+ * AlgorithmParameterSpec derivationSpec =
+ * HKDFParameterSpec.ofExtract()
+ * .addIKM(ikm)
+ * .addSalt(salt).thenExpand(info, 32);
+ *}
+ *
+ * @spec https://www.rfc-editor.org/info/rfc5869
+ * RFC 5869: HMAC-based Extract-and-Expand Key Derivation Function (HKDF)
+ * @see javax.crypto.KDF
+ * @since 24
+ */
+@PreviewFeature(feature = PreviewFeature.Feature.KEY_DERIVATION)
+public interface HKDFParameterSpec extends AlgorithmParameterSpec {
+
+ /**
+ * This {@code Builder} builds {@code Extract} and {@code ExtractThenExpand}
+ * objects.
+ *
+ * The {@code Builder} is initialized via the {@code ofExtract} method of
+ * {@code HKDFParameterSpec}. As stated in the class description,
+ * {@code addIKM} and/or {@code addSalt} may be called as needed. Finally,
+ * an object is "built" by calling either {@code extractOnly} or
+ * {@code thenExpand} for {@code Extract} and {@code ExtractThenExpand}
+ * use-cases respectively. Note that the {@code Builder} is not
+ * thread-safe.
+ */
+ @PreviewFeature(feature = PreviewFeature.Feature.KEY_DERIVATION)
+ final class Builder {
+
+ private List ikms = new ArrayList<>();
+ private List salts = new ArrayList<>();
+
+ private Builder() {}
+
+ /**
+ * Builds an {@code Extract} object from the current state of the
+ * {@code Builder}.
+ *
+ * @return an immutable {@code Extract} object
+ */
+ public Extract extractOnly() {
+ return new Extract(ikms, salts);
+ }
+
+ /**
+ * Builds an {@code ExtractThenExpand} object from the current state of
+ * the {@code Builder}.
+ *
+ * @implNote HKDF implementations will enforce that the length
+ * is not greater than 255 * HMAC length. HKDF implementations
+ * will also enforce that a {code null} info value is treated as
+ * zero-length byte array.
+ *
+ * @param info
+ * the optional context and application specific information
+ * (may be {@code null}); the byte array is cloned to prevent
+ * subsequent modification
+ * @param length
+ * the length of the output keying material (must be greater
+ * than 0)
+ *
+ * @return an immutable {@code ExtractThenExpand} object
+ *
+ * @throws IllegalArgumentException
+ * if {@code length} is not greater than 0
+ */
+ public ExtractThenExpand thenExpand(byte[] info, int length) {
+ return new ExtractThenExpand(
+ extractOnly(), info,
+ length);
+ }
+
+ /**
+ * Adds input keying material (IKM) to the builder.
+ *
+ * Users may call {@code addIKM} multiple times when the input keying
+ * material value is to be assembled piece-meal or if part of the IKM is
+ * to be supplied by a hardware crypto device. The {@code ikms()}
+ * method of the {@code Extract} or {@code ExtractThenExpand} object
+ * that is subsequently built returns the assembled input keying
+ * material as a list of {@code SecretKey} objects.
+ *
+ * @param ikm
+ * the input keying material (IKM) value
+ *
+ * @return this builder
+ *
+ * @throws NullPointerException
+ * if the {@code ikm} argument is null
+ */
+ public Builder addIKM(SecretKey ikm) {
+ Objects.requireNonNull(ikm, "ikm must not be null");
+ ikms.add(ikm);
+ return this;
+ }
+
+ /**
+ * Adds input keying material (IKM) to the builder. Note that an
+ * {@code ikm} byte array of length zero will be discarded.
+ *
+ * Users may call {@code addIKM} multiple times when the input keying
+ * material value is to be assembled piece-meal or if part of the IKM is
+ * to be supplied by a hardware crypto device. The {@code ikms()}
+ * method of the {@code Extract} or {@code ExtractThenExpand} object
+ * that is subsequently built returns the assembled input keying
+ * material as a list of {@code SecretKey} objects.
+ *
+ * @param ikm
+ * the input keying material (IKM) value; the {@code ikm}
+ * byte array will be converted to a {@code SecretKeySpec},
+ * which means that the byte array will be cloned inside the
+ * {@code SecretKeySpec} constructor
+ *
+ * @return this builder
+ *
+ * @throws NullPointerException
+ * if the {@code ikm} argument is null
+ */
+ public Builder addIKM(byte[] ikm) {
+ Objects.requireNonNull(ikm, "ikm must not be null");
+ if (ikm.length != 0) {
+ return addIKM(new SecretKeySpec(ikm, "Generic"));
+ } else {
+ return this;
+ }
+ }
+
+ /**
+ * Adds a salt to the builder.
+ *
+ * Users may call {@code addSalt} multiple times when the salt value is
+ * to be assembled piece-meal or if part of the salt is to be supplied
+ * by a hardware crypto device. The {@code salts()} method of the
+ * {@code Extract} or {@code ExtractThenExpand} object that is
+ * subsequently built returns the assembled salt as a list of
+ * {@code SecretKey} objects.
+ *
+ * @param salt
+ * the salt value
+ *
+ * @return this builder
+ *
+ * @throws NullPointerException
+ * if the {@code salt} is null
+ */
+ public Builder addSalt(SecretKey salt) {
+ Objects.requireNonNull(salt, "salt must not be null");
+ salts.add(salt);
+ return this;
+ }
+
+ /**
+ * Adds a salt to the builder. Note that a {@code salt} byte array of
+ * length zero will be discarded.
+ *
+ * Users may call {@code addSalt} multiple times when the salt value is
+ * to be assembled piece-meal or if part of the salt is to be supplied
+ * by a hardware crypto device. The {@code salts()} method of the
+ * {@code Extract} or {@code ExtractThenExpand} object that is
+ * subsequently built returns the assembled salt as a list of
+ * {@code SecretKey} objects.
+ *
+ * @param salt
+ * the salt value; the {@code salt} byte array will be
+ * converted to a {@code SecretKeySpec}, which means that the
+ * byte array will be cloned inside the {@code SecretKeySpec}
+ * constructor
+ *
+ * @return this builder
+ *
+ * @throws NullPointerException
+ * if the {@code salt} is null
+ */
+ public Builder addSalt(byte[] salt) {
+ Objects.requireNonNull(salt, "salt must not be null");
+ if (salt.length != 0) {
+ return addSalt(new SecretKeySpec(salt, "Generic"));
+ } else {
+ return this;
+ }
+ }
+ }
+
+ /**
+ * Returns a {@code Builder} for building {@code Extract} and
+ * {@code ExtractThenExpand} objects.
+ *
+ * @return a new {@code Builder}
+ */
+ static Builder ofExtract() {
+ return new Builder();
+ }
+
+ /**
+ * Creates an {@code Expand} object.
+ *
+ * @implNote HKDF implementations will enforce that the length is
+ * not greater than 255 * HMAC length. Implementations will also
+ * enforce that the prk argument is at least as many bytes as the
+ * HMAC length. Implementations will also enforce that a {code null}
+ * info value is treated as zero-length byte array.
+ *
+ * @param prk
+ * the pseudorandom key (PRK); must not be {@code null}
+ * @param info
+ * the optional context and application specific information (may be
+ * {@code null}); the byte array is cloned to prevent subsequent
+ * modification
+ * @param length
+ * the length of the output keying material (must be greater than
+ * 0)
+ *
+ * @return an {@code Expand} object
+ *
+ * @throws NullPointerException
+ * if the {@code prk} argument is {@code null}
+ * @throws IllegalArgumentException
+ * if {@code length} is not greater than 0
+ */
+ static Expand expandOnly(SecretKey prk, byte[] info, int length) {
+ if (prk == null) {
+ throw new NullPointerException("prk must not be null");
+ }
+ return new Expand(prk, info, length);
+ }
+
+ /**
+ * Defines the input parameters of an Extract operation as defined in RFC 5869.
+ */
+ @PreviewFeature(feature = PreviewFeature.Feature.KEY_DERIVATION)
+ final class Extract implements HKDFParameterSpec {
+
+ // HKDF-Extract(salt, IKM) -> PRK
+ private final List ikms;
+ private final List salts;
+
+ private Extract(List ikms, List salts) {
+ this.ikms = List.copyOf(ikms);
+ this.salts = List.copyOf(salts);
+ }
+
+ /**
+ * Returns an unmodifiable {@code List} of input keying material values
+ * in the order they were added. Returns an empty list if there are no
+ * input keying material values.
+ *
+ * Input keying material values added by {@link Builder#addIKM(byte[])}
+ * are converted to a {@code SecretKeySpec} object. Empty arrays are
+ * discarded.
+ *
+ * @implNote An HKDF implementation should concatenate the input
+ * keying materials into a single value to be used in
+ * HKDF-Extract.
+ *
+ * @return the unmodifiable {@code List} of input keying material
+ * values
+ */
+ public List ikms() {
+ return ikms;
+ }
+
+ /**
+ * Returns an unmodifiable {@code List} of salt values in the order they
+ * were added. Returns an empty list if there are no salt values.
+ *
+ * Salt values added by {@link Builder#addSalt(byte[])} are converted to
+ * a {@code SecretKeySpec} object. Empty arrays are discarded.
+ *
+ * @implNote An HKDF implementation should concatenate the salts
+ * into a single value to be used in HKDF-Extract.
+ *
+ * @return the unmodifiable {@code List} of salt values
+ */
+ public List salts() {
+ return salts;
+ }
+
+ }
+
+ /**
+ * Defines the input parameters of an Expand operation as defined in RFC 5869.
+ */
+ @PreviewFeature(feature = PreviewFeature.Feature.KEY_DERIVATION)
+ final class Expand implements HKDFParameterSpec {
+
+ // HKDF-Expand(PRK, info, L) -> OKM
+ private final SecretKey prk;
+ private final byte[] info;
+ private final int length;
+
+ /**
+ * Constructor that may be used to initialize an {@code Expand} object
+ *
+ * @param prk
+ * the pseudorandom key (PRK); in the case of
+ * {@code ExtractThenExpand}, the {@code prk} argument may be
+ * {@null} since the output of extract phase is used
+ * @param info
+ * the optional context and application specific information
+ * (may be {@code null}); the byte array is cloned to prevent
+ * subsequent modification
+ * @param length
+ * the length of the output keying material
+ *
+ * @throws IllegalArgumentException
+ * if {@code length} not greater than 0
+ */
+ private Expand(SecretKey prk, byte[] info, int length) {
+ // a null prk argument could be indicative of ExtractThenExpand
+ this.prk = prk;
+ this.info = (info == null) ? null : info.clone();
+ if (!(length > 0)) {
+ throw new IllegalArgumentException("length must be > 0");
+ }
+ this.length = length;
+ }
+
+ /**
+ * Returns the pseudorandom key (PRK).
+ *
+ * @return the pseudorandom key
+ */
+ public SecretKey prk() {
+ return prk;
+ }
+
+ /**
+ * Returns the optional context and application specific information.
+ *
+ * @return a clone of the optional context and application specific
+ * information, or {@code null} if not specified
+ */
+ public byte[] info() {
+ return (info == null) ? null : info.clone();
+ }
+
+ /**
+ * Returns the length of the output keying material.
+ *
+ * @return the length of the output keying material
+ */
+ public int length() {
+ return length;
+ }
+
+ }
+
+ /**
+ * Defines the input parameters of an Extract-then-Expand operation as
+ * defined in RFC 5869.
+ */
+ @PreviewFeature(feature = PreviewFeature.Feature.KEY_DERIVATION)
+ final class ExtractThenExpand implements HKDFParameterSpec {
+ private final Extract ext;
+ private final Expand exp;
+
+ /**
+ * Constructor that may be used to initialize an
+ * {@code ExtractThenExpand} object
+ *
+ * @param ext
+ * a pre-generated {@code Extract}
+ * @param info
+ * the optional context and application specific information
+ * (may be {@code null}); the byte array is cloned to prevent
+ * subsequent modification
+ * @param length
+ * the length of the output keying material
+ *
+ * @throws IllegalArgumentException
+ * if {@code length} is not greater than 0
+ */
+ private ExtractThenExpand(Extract ext, byte[] info, int length) {
+ Objects.requireNonNull(ext, "Extract object must not be null");
+ this.ext = ext;
+ // - null prk argument is ok here (it's a signal)
+ // - {@code Expand} constructor can deal with a null info
+ // - length is checked in {@code Expand} constructor
+ this.exp = new Expand(null, info, length);
+ }
+
+ /**
+ * Returns an unmodifiable {@code List} of input keying material values
+ * in the order they were added. Returns an empty list if there are no
+ * input keying material values.
+ *
+ * Input keying material values added by {@link Builder#addIKM(byte[])}
+ * are converted to a {@code SecretKeySpec} object. Empty arrays are
+ * discarded.
+ *
+ * @implNote An HKDF implementation should concatenate the input
+ * keying materials into a single value to be used in the
+ * HKDF-Extract phase.
+ *
+ * @return the unmodifiable {@code List} of input keying material
+ * values
+ */
+ public List ikms() {
+ return ext.ikms();
+ }
+
+ /**
+ * Returns an unmodifiable {@code List} of salt values in the order they
+ * were added. Returns an empty list if there are no salt values.
+ *
+ * Salt values added by {@link Builder#addSalt(byte[])} are converted to
+ * a {@code SecretKeySpec} object. Empty arrays are discarded.
+ *
+ * @implNote An HKDF implementation should concatenate the salts
+ * into a single value to be used in the HKDF-Extract phase.
+ *
+ * @return the unmodifiable {@code List} of salt values
+ *
+ */
+ public List salts() {
+ return ext.salts();
+ }
+
+ /**
+ * Returns the optional context and application specific information.
+ *
+ * @return a clone of the optional context and application specific
+ * information, or {@code null} if not specified
+ */
+ public byte[] info() {
+ return exp.info();
+ }
+
+ /**
+ * Returns the length of the output keying material.
+ *
+ * @return the length of the output keying material
+ */
+ public int length() {
+ return exp.length();
+ }
+
+ }
+
+}
diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java
index 6ac3bfd8bce..483093e66eb 100644
--- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java
+++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java
@@ -80,6 +80,8 @@ public @interface PreviewFeature {
STREAM_GATHERERS,
@JEP(number=476, title="Module Import Declarations", status="Preview")
MODULE_IMPORTS,
+ @JEP(number=478, title="Key Derivation Function API", status="Preview")
+ KEY_DERIVATION,
LANGUAGE_MODEL,
/**
* A key for testing.
diff --git a/src/java.base/share/classes/sun/security/util/Debug.java b/src/java.base/share/classes/sun/security/util/Debug.java
index f1484145f5c..65e121421b8 100644
--- a/src/java.base/share/classes/sun/security/util/Debug.java
+++ b/src/java.base/share/classes/sun/security/util/Debug.java
@@ -139,7 +139,7 @@ public class Debug {
System.err.println("engine=");
System.err.println(" only dump output for the specified list");
System.err.println(" of JCA engines. Supported values:");
- System.err.println(" Cipher, KeyAgreement, KeyGenerator,");
+ System.err.println(" Cipher, KDF, KeyAgreement, KeyGenerator,");
System.err.println(" KeyPairGenerator, KeyStore, Mac,");
System.err.println(" MessageDigest, SecureRandom, Signature.");
System.err.println();
diff --git a/test/jdk/com/sun/crypto/provider/KDF/HKDFBasicFunctionsTest.java b/test/jdk/com/sun/crypto/provider/KDF/HKDFBasicFunctionsTest.java
new file mode 100644
index 00000000000..2697ba57641
--- /dev/null
+++ b/test/jdk/com/sun/crypto/provider/KDF/HKDFBasicFunctionsTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8331008
+ * @summary basic HKDF operations
+ * @library /test/lib
+ * @enablePreview
+ */
+
+import java.util.HexFormat;
+import javax.crypto.KDF;
+import javax.crypto.spec.HKDFParameterSpec;
+import jdk.test.lib.Asserts;
+
+public class HKDFBasicFunctionsTest {
+ public static void main(String[] args) throws Exception {
+ var ikm = HexFormat.of().parseHex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+ var salt = HexFormat.of().parseHex("000102030405060708090a0b0c");
+ var info = HexFormat.of().parseHex("f0f1f2f3f4f5f6f7f8f9");
+ var len = 42;
+
+ var kdf = KDF.getInstance("HKDF-SHA256");
+ var expectedPrk = HexFormat.of().parseHex("077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5");
+ var expectedOkm = HexFormat.of().parseHex("3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865");
+
+ var extractOnly = HKDFParameterSpec.ofExtract().addIKM(ikm).addSalt(salt).extractOnly();
+ var prk = kdf.deriveKey("PRK", extractOnly);
+ var expandOnly = HKDFParameterSpec.expandOnly(prk, info, len);
+ var okm1 = kdf.deriveKey("OKM", expandOnly);
+ var extractAndExpand = HKDFParameterSpec.ofExtract().addIKM(ikm).addSalt(salt).thenExpand(info, len);
+ var okm2 = kdf.deriveKey("OKM", extractAndExpand);
+
+ Asserts.assertEqualsByteArray(prk.getEncoded(), expectedPrk,
+ "the PRK must match the expected value");
+
+ Asserts.assertEqualsByteArray(okm1.getEncoded(), expectedOkm,
+ "the OKM must match the expected value "
+ + "(expand)");
+
+ Asserts.assertEqualsByteArray(okm2.getEncoded(), expectedOkm,
+ "the OKM must match the expected value "
+ + "(extract expand)");
+
+ // test empty extract
+ test(HKDFParameterSpec.ofExtract().extractOnly());
+ // test expand with empty info
+ test(HKDFParameterSpec.ofExtract().thenExpand(new byte[0], 32));
+ // test expand with null info
+ test(HKDFParameterSpec.ofExtract().thenExpand(null, 32));
+ // test extract with zero-length salt
+ test(HKDFParameterSpec.ofExtract().addIKM(ikm).addSalt(new byte[0]).extractOnly());
+ }
+
+ static void test(HKDFParameterSpec p) throws Exception {
+ var kdf = KDF.getInstance("HKDF-SHA256");
+ System.out.println(HexFormat.of().formatHex(kdf.deriveData(p)));
+ }
+}
diff --git a/test/jdk/com/sun/crypto/provider/KDF/HKDFExhaustiveTest.java b/test/jdk/com/sun/crypto/provider/KDF/HKDFExhaustiveTest.java
new file mode 100644
index 00000000000..0088fe54a80
--- /dev/null
+++ b/test/jdk/com/sun/crypto/provider/KDF/HKDFExhaustiveTest.java
@@ -0,0 +1,556 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8331008
+ * @summary KDF API tests
+ * @library /test/lib
+ * @run main/othervm -Djava.security.egd=file:/dev/urandom -Djava.security.debug=provider,engine=kdf HKDFExhaustiveTest
+ * @enablePreview
+ */
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.Security;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+import java.util.ArrayList;
+import java.util.List;
+import javax.crypto.KDF;
+import javax.crypto.KDFParameters;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.HKDFParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import jdk.test.lib.Asserts;
+import jdk.test.lib.Utils;
+
+public class HKDFExhaustiveTest {
+
+ private static final String JDK_HKDF_SHA256 = "HKDF-SHA256";
+ private static final String JDK_HKDF_SHA384 = "HKDF-SHA384";
+ private static final String JDK_HKDF_SHA512 = "HKDF-SHA512";
+ private static final String[] KDF_ALGORITHMS = {
+ JDK_HKDF_SHA256, JDK_HKDF_SHA384, JDK_HKDF_SHA512
+ };
+ private static final String SUNJCE = "SunJCE";
+
+ // SECRET_KEY_SPEC_KEYS and RAW_DATA holds valid values for IKM and SALTS
+ private static final List SECRET_KEY_SPEC_KEYS =
+ List.of(
+ new SecretKeySpec(new byte[] {0}, "HKDF-IKM"),
+ new SecretKeySpec("IKM".getBytes(), "HKDF-IKM"));
+ private static final List RAW_DATA = List.of(new byte[] {0}, "RAW".getBytes());
+
+ private static final byte[] EMPTY = new byte[0];
+ private static final int SHORT_LENGTH = 42;
+ private static final int LARGE_LENGTH = 1000;
+ private static final int NEGATIVE_LENGTH = -1;
+
+ private static final KdfVerifier KdfGetInstanceVerifier =
+ (a, p, s) -> {
+
+ // Test KDF getInstance methods, all should have same algo and provider
+ KDF k1 = KDF.getInstance(a);
+ KDF k2 = KDF.getInstance(a, p);
+ KDF k3 = KDF.getInstance(a, Security.getProvider(p));
+ Asserts.assertEquals(k1.getAlgorithm(), k2.getAlgorithm());
+ Asserts.assertEquals(k2.getAlgorithm(), k3.getAlgorithm());
+ Asserts.assertEquals(k1.getProviderName(), k2.getProviderName());
+ Asserts.assertEquals(k2.getProviderName(), k3.getProviderName());
+ Asserts.assertEquals(k1.getParameters(), k2.getParameters());
+ Asserts.assertEquals(k2.getParameters(), k3.getParameters());
+
+ // Test KDF getInstance methods with parameters
+ KDFParameters spec = (KDFParameters) s;
+ k1 = KDF.getInstance(a, spec);
+ k2 = KDF.getInstance(a, spec, p);
+ k3 = KDF.getInstance(a, spec, Security.getProvider(p));
+ Asserts.assertEquals(k1.getAlgorithm(), k2.getAlgorithm());
+ Asserts.assertEquals(k2.getAlgorithm(), k3.getAlgorithm());
+ Asserts.assertEquals(k1.getProviderName(), k2.getProviderName());
+ Asserts.assertEquals(k2.getProviderName(), k3.getProviderName());
+ Asserts.assertEquals(k1.getParameters(), k2.getParameters());
+ Asserts.assertEquals(k2.getParameters(), k3.getParameters());
+ };
+
+ private static final KdfExtractVerifier