mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-26 14:24:46 +02:00
8331008: Implement JEP 478: Key Derivation Function API (Preview)
Co-authored-by: Rajan Halade <rhalade@openjdk.org> Co-authored-by: Weijun Wang <weijun@openjdk.org> Co-authored-by: Valerie Peng <valeriep@openjdk.org> Reviewed-by: weijun, valeriep
This commit is contained in:
parent
847cc5ebac
commit
2a1ae0ff89
17 changed files with 3321 additions and 1 deletions
|
@ -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.
|
||||
* <p>
|
||||
* 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<SecretKey> 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<SecretKey> keys)
|
||||
throws InvalidKeyException {
|
||||
if (keys != null && !keys.isEmpty()) {
|
||||
ArrayList<SecretKey> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
*/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue