From ef4f13c0540e10ef2893e75873ac1b7505941c9a Mon Sep 17 00:00:00 2001 From: Jason Katonica Date: Wed, 15 Jan 2025 11:18:00 -0500 Subject: [PATCH 1/2] Add support for PBKDF2 algorithms The following algorithms will be added to the OpenJCEPlusFIPS provider: - PBKDF2WithHmacSHA224 - PBKDF2WithHmacSHA256 - PBKDF2WithHmacSHA384 - PBKDF2WithHmacSHA512 The following algorithms will be added to the OpenJCEPlus provider: - PBKDF2WithHmacSHA1 - PBKDF2WithHmacSHA224 - PBKDF2WithHmacSHA256 - PBKDF2WithHmacSHA384 - PBKDF2WithHmacSHA512 Updates required include: - Adding a new PBKDF2Core class based on OpenJDK. - Adding a new PBKDF2KeyImpl class based on OpenJDK yet modified to make use of the OCKC library to perform PBKDF2 key derivations. - New JNI code to call a new native method to derive a key using PBKDF2 from a given password, salt, iteration count, and desired key length. - Updates in makefiles to allow the new PBKDF.c file to be built into the JNI dll. - A new set of interoperability test was created to enforce that we get the same results from SunJCE, OpenJCEPlus, OpenJCEPlusFIPS for the KeyFactory methods `generateSecret`, `translateKey`, and `getKeySpec`. - A new test was introduce to drive various KAT and error paths. Signed-off-by: Jason Katonica --- README.md | 7 +- .../ibm/crypto/plus/provider/OpenJCEPlus.java | 37 ++- .../crypto/plus/provider/OpenJCEPlusFIPS.java | 29 +- .../ibm/crypto/plus/provider/PBKDF2Core.java | 177 ++++++++++++ .../crypto/plus/provider/PBKDF2KeyImpl.java | 265 ++++++++++++++++++ .../plus/provider/ock/NativeInterface.java | 9 +- .../ibm/crypto/plus/provider/ock/PBKDF.java | 74 +++++ src/main/native/PBKDF.c | 169 +++++++++++ src/main/native/jgskit.mac.mak | 5 +- src/main/native/jgskit.mak | 5 +- src/main/native/jgskit.win64.cygwin.mak | 5 +- src/main/native/jgskit.win64.mak | 5 +- .../jceplus/junit/base/BaseTestPBKDF2.java | 190 +++++++++++++ .../junit/base/BaseTestPBKDF2Interop.java | 244 ++++++++++++++++ .../jceplus/junit/openjceplus/TestAll.java | 4 +- .../jceplus/junit/openjceplus/TestPBKDF2.java | 24 ++ .../junit/openjceplus/TestPBKDF2Interop.java | 25 ++ .../junit/openjceplusfips/TestAll.java | 4 +- .../junit/openjceplusfips/TestPBKDF2.java | 30 ++ .../openjceplusfips/TestPBKDF2Interop.java | 25 ++ 20 files changed, 1317 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/ibm/crypto/plus/provider/PBKDF2Core.java create mode 100644 src/main/java/com/ibm/crypto/plus/provider/PBKDF2KeyImpl.java create mode 100644 src/main/java/com/ibm/crypto/plus/provider/ock/PBKDF.java create mode 100644 src/main/native/PBKDF.c create mode 100644 src/test/java/ibm/jceplus/junit/base/BaseTestPBKDF2.java create mode 100644 src/test/java/ibm/jceplus/junit/base/BaseTestPBKDF2Interop.java create mode 100644 src/test/java/ibm/jceplus/junit/openjceplus/TestPBKDF2.java create mode 100644 src/test/java/ibm/jceplus/junit/openjceplus/TestPBKDF2Interop.java create mode 100644 src/test/java/ibm/jceplus/junit/openjceplusfips/TestPBKDF2.java create mode 100644 src/test/java/ibm/jceplus/junit/openjceplusfips/TestPBKDF2Interop.java diff --git a/README.md b/README.md index 8f241fea..d8af034a 100644 --- a/README.md +++ b/README.md @@ -253,7 +253,7 @@ KeyFactory | X25519 | |X KeyFactory | X448 | |X | KeyFactory | XDH | |X | KeyGenerator | AES |X |X | -KeyGenerator | ChaCha20 | |X | +KeyGenerator | ChaCha20 | |X | KeyGenerator | DESede | |X | KeyGenerator | HmacMD5 | |X | KeyGenerator | HmacSHA1 | |X | @@ -314,6 +314,11 @@ MessageDigest | SHA3-512 |X |X SecretKeyFactory | AES |X |X | SecretKeyFactory | ChaCha20 | |X | SecretKeyFactory | DESede | |X | +SecretKeyFactory | PBKDF2WithHmacSHA1 | |X | +SecretKeyFactory | PBKDF2WithHmacSHA224 |X |X | +SecretKeyFactory | PBKDF2WithHmacSHA256 |X |X | +SecretKeyFactory | PBKDF2WithHmacSHA384 |X |X | +SecretKeyFactory | PBKDF2WithHmacSHA512 |X |X | SecureRandom | SHA256DRBG |X |X | SecureRandom | SHA512DRBG |X |X | Signature | Ed25519 | |X | diff --git a/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlus.java b/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlus.java index 08077751..73823b13 100644 --- a/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlus.java +++ b/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlus.java @@ -1,5 +1,5 @@ /* - * Copyright IBM Corp. 2023, 2024 + * Copyright IBM Corp. 2023, 2025 * * Licensed under the Apache License 2.0 (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy @@ -46,7 +46,7 @@ public final class OpenJCEPlus extends OpenJCEPlusProvider { + "Message authentication code (MAC) : HmacMD5, HmacSHA1, HmacSHA224, HmacSHA256, HmacSHA384, HmacSHA512\n" + " , HmacSHA3-224, HmacSHA3-256, HmacSHA3-384, HmacSHA3-512\n" + "Message digest : MD5, SHA-1, SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, SHA-512/256, SHA3-224, SHA3-256, SHA3-384, SHA3-512\n" - + "Secret key factory : AES, ChaCha20, DESede\n" + + "Secret key factory : AES, ChaCha20, DESede, PBKDF2WithHmacSHA1, PBKDF2WithHmacSHA224, PBKDF2WithHmacSHA256, PBKDF2WithHmacSHA384, PBKDF2WithHmacSHA512\n" + "Secure random : HASHDRBG, SHA256DRBG, SHA512DRBG\n" + "Signature algorithms : NONEwithDSA, SHA1withDSA, SHA224withDSA, SHA256withDSA,\n" + " SHA3-224withDSA, SHA3-256withDSA, SHA3-384withDSA, SHA3-512withDSA,\n" @@ -597,6 +597,39 @@ private void registerAlgorithms(Provider jce) { putService(new OpenJCEPlusService(jce, "SecretKeyFactory", "AES", "com.ibm.crypto.plus.provider.AESKeyFactory", aliases)); + aliases = null; + putService(new OpenJCEPlusService(jce, + "SecretKeyFactory", + "PBKDF2WithHmacSHA1", + "com.ibm.crypto.plus.provider.PBKDF2Core$HmacSHA1", + aliases)); + + aliases = null; + putService(new OpenJCEPlusService(jce, + "SecretKeyFactory", + "PBKDF2WithHmacSHA224", + "com.ibm.crypto.plus.provider.PBKDF2Core$HmacSHA224", + aliases)); + + aliases = null; + putService(new OpenJCEPlusService(jce, + "SecretKeyFactory", + "PBKDF2WithHmacSHA256", + "com.ibm.crypto.plus.provider.PBKDF2Core$HmacSHA256", + aliases)); + aliases = null; + putService(new OpenJCEPlusService(jce, + "SecretKeyFactory", + "PBKDF2WithHmacSHA384", + "com.ibm.crypto.plus.provider.PBKDF2Core$HmacSHA384", + aliases)); + aliases = null; + putService(new OpenJCEPlusService(jce, + "SecretKeyFactory", + "PBKDF2WithHmacSHA512", + "com.ibm.crypto.plus.provider.PBKDF2Core$HmacSHA512", + aliases)); + aliases = new String[] {"TripleDES", "3DES"}; putService(new OpenJCEPlusService(jce, "SecretKeyFactory", "DESede", "com.ibm.crypto.plus.provider.DESedeKeyFactory", aliases)); diff --git a/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlusFIPS.java b/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlusFIPS.java index 84116aaa..2af883ca 100644 --- a/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlusFIPS.java +++ b/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlusFIPS.java @@ -1,5 +1,5 @@ /* - * Copyright IBM Corp. 2023, 2024 + * Copyright IBM Corp. 2023, 2025 * * Licensed under the Apache License 2.0 (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy @@ -48,7 +48,7 @@ public final class OpenJCEPlusFIPS extends OpenJCEPlusProvider { + " HmacSHA384, HmacSHA512\n" + " HmacSHA3-224, HmacSHA3-256, HmacSHA3-384, HmacSHA3-512\n" + "Message digest : SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, SHA-512/256, SHA3-224, SHA3-256, SHA3-384, SHA3-512\n" - + "Secret key factory : AES\n" + + "Secret key factory : AES, PBKDF2WithHmacSHA224, PBKDF2WithHmacSHA256, PBKDF2WithHmacSHA384, PBKDF2WithHmacSHA512\n" + "Secure random : HASHDRBG, SHA256DRBG, SHA512DRBG\n" + "Signature algorithms : NONEwithDSA, SHA224withDSA, SHA256withDSA,\n" + " NONEwithECDSA, SHA224withECDSA,\n" @@ -510,6 +510,31 @@ private void registerAlgorithms(Provider jce) { putService(new OpenJCEPlusService(jce, "SecretKeyFactory", "AES", "com.ibm.crypto.plus.provider.AESKeyFactory", aliases)); + aliases = null; + putService(new OpenJCEPlusService(jce, + "SecretKeyFactory", + "PBKDF2WithHmacSHA224", + "com.ibm.crypto.plus.provider.PBKDF2Core$HmacSHA224", + aliases)); + + aliases = null; + putService(new OpenJCEPlusService(jce, + "SecretKeyFactory", + "PBKDF2WithHmacSHA256", + "com.ibm.crypto.plus.provider.PBKDF2Core$HmacSHA256", + aliases)); + aliases = null; + putService(new OpenJCEPlusService(jce, + "SecretKeyFactory", + "PBKDF2WithHmacSHA384", + "com.ibm.crypto.plus.provider.PBKDF2Core$HmacSHA384", + aliases)); + aliases = null; + putService(new OpenJCEPlusService(jce, + "SecretKeyFactory", + "PBKDF2WithHmacSHA512", + "com.ibm.crypto.plus.provider.PBKDF2Core$HmacSHA512", + aliases)); /* Not yet supported in FIPS mode * aliases = null; diff --git a/src/main/java/com/ibm/crypto/plus/provider/PBKDF2Core.java b/src/main/java/com/ibm/crypto/plus/provider/PBKDF2Core.java new file mode 100644 index 00000000..75823a21 --- /dev/null +++ b/src/main/java/com/ibm/crypto/plus/provider/PBKDF2Core.java @@ -0,0 +1,177 @@ +/* + * Copyright IBM Corp. 2025 + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution. + */ + +package com.ibm.crypto.plus.provider; + +import java.security.InvalidKeyException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.Arrays; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactorySpi; +import javax.crypto.spec.PBEKeySpec; + +/** + * This class implements a key factory for PBE keys derived using + * PBKDF2 with HmacSHA1/HmacSHA224/HmacSHA256/HmacSHA384/HmacSHA512 + * pseudo random function (PRF) as defined in PKCS#5 v2.1. + * + * @author Valerie Peng + * + * See also same named class from OpenJDK. This class makes use of similar code. + */ +abstract class PBKDF2Core extends SecretKeyFactorySpi { + + private final String prfAlgo; + + /** + * Provider associated with this service instance. + */ + private OpenJCEPlusProvider provider = null; + + PBKDF2Core(OpenJCEPlusProvider provider, String prfAlgo) { + this.provider = provider; + this.prfAlgo = prfAlgo; + } + + /** + * Generates a SecretKey object from the provided key + * specification (key material). + * + * @param keySpec the specification (key material) of the secret key + * + * @return the secret key + * + * @exception InvalidKeySpecException if the given key specification + * is inappropriate for this key factory to produce a public key. + */ + protected SecretKey engineGenerateSecret(KeySpec keySpec) throws InvalidKeySpecException { + if (keySpec instanceof PBEKeySpec ks) { + return new PBKDF2KeyImpl(this.provider, ks, prfAlgo); + } else { + throw new InvalidKeySpecException("Only PBEKeySpec is accepted"); + } + } + + /** + * Returns a specification (key material) of the given key + * in the requested format. + * + * @param key the key + * + * @param keySpecCl the requested format in which the key material shall be + * returned + * + * @return the underlying key specification (key material) in the + * requested format + * + * @exception InvalidKeySpecException if the requested key + * specification is inappropriate for the given key, or the + * given key cannot be processed (e.g., the given key has an + * unrecognized algorithm or format). + */ + protected KeySpec engineGetKeySpec(SecretKey key, Class keySpecCl) + throws InvalidKeySpecException { + if (key instanceof javax.crypto.interfaces.PBEKey pKey) { + // Check if requested key spec is amongst the valid ones + if ((keySpecCl != null) && keySpecCl.isAssignableFrom(PBEKeySpec.class)) { + char[] passwd = pKey.getPassword(); + byte[] encoded = pKey.getEncoded(); + try { + return new PBEKeySpec(passwd, pKey.getSalt(), pKey.getIterationCount(), + encoded.length * 8); + } finally { + if (passwd != null) { + Arrays.fill(passwd, (char) 0); + } + Arrays.fill(encoded, (byte) 0); + } + } else { + throw new InvalidKeySpecException("Only PBEKeySpec is accepted"); + } + } else { + throw new InvalidKeySpecException("Only PBEKey is accepted"); + } + } + + /** + * Translates a SecretKey object, whose provider may be + * unknown or potentially untrusted, into a corresponding + * SecretKey object of this key factory. + * + * @param key the key whose provider is unknown or untrusted + * + * @return the translated key + * + * @exception InvalidKeyException if the given key cannot be processed by + * this key factory. + */ + protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException { + if ((key != null) && (key.getAlgorithm().equalsIgnoreCase("PBKDF2With" + prfAlgo)) + && (key.getFormat().equalsIgnoreCase("RAW"))) { + + // Check if key originates from this factory, if true simply return it. + if (key instanceof com.ibm.crypto.plus.provider.PBKDF2KeyImpl) { + return key; + } + + // Check if key implements the PBEKey + if (key instanceof javax.crypto.interfaces.PBEKey pKey) { + char[] password = pKey.getPassword(); + byte[] encoding = pKey.getEncoded(); + PBEKeySpec spec = new PBEKeySpec(password, pKey.getSalt(), pKey.getIterationCount(), + encoding.length * 8); + try { + return new PBKDF2KeyImpl(this.provider, spec, prfAlgo); + } catch (InvalidKeySpecException re) { + throw new InvalidKeyException("Invalid key component(s)", re); + } finally { + if (password != null) { + Arrays.fill(password, (char) 0); + spec.clearPassword(); + } + Arrays.fill(encoding, (byte) 0); + } + } else { + throw new InvalidKeyException("Only PBEKey is accepted"); + } + } + throw new InvalidKeyException( + "Only PBKDF2With" + prfAlgo + " key with RAW format is accepted"); + } + + public static final class HmacSHA1 extends PBKDF2Core { + public HmacSHA1(OpenJCEPlusProvider provider) { + super(provider, "HmacSHA1"); + } + } + + public static final class HmacSHA224 extends PBKDF2Core { + public HmacSHA224(OpenJCEPlusProvider provider) { + super(provider, "HmacSHA224"); + } + } + + public static final class HmacSHA256 extends PBKDF2Core { + public HmacSHA256(OpenJCEPlusProvider provider) { + super(provider, "HmacSHA256"); + } + } + + public static final class HmacSHA384 extends PBKDF2Core { + public HmacSHA384(OpenJCEPlusProvider provider) { + super(provider, "HmacSHA384"); + } + } + + public static final class HmacSHA512 extends PBKDF2Core { + public HmacSHA512(OpenJCEPlusProvider provider) { + super(provider, "HmacSHA512"); + } + } +} diff --git a/src/main/java/com/ibm/crypto/plus/provider/PBKDF2KeyImpl.java b/src/main/java/com/ibm/crypto/plus/provider/PBKDF2KeyImpl.java new file mode 100644 index 00000000..af74bf7b --- /dev/null +++ b/src/main/java/com/ibm/crypto/plus/provider/PBKDF2KeyImpl.java @@ -0,0 +1,265 @@ +/* + * Copyright IBM Corp. 2025 + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution. + */ + +package com.ibm.crypto.plus.provider; + +import com.ibm.crypto.plus.provider.ock.OCKException; +import com.ibm.crypto.plus.provider.ock.PBKDF; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectStreamException; +import java.lang.ref.Reference; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.security.KeyRep; +import java.security.MessageDigest; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; +import java.util.Locale; +import javax.crypto.SecretKey; +import javax.crypto.spec.PBEKeySpec; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * This class represents a PBE key derived using PBKDF2 defined + * in PKCS#5 v2.0. meaning that + * 1) the password must consist of characters which will be converted + * to bytes using UTF-8 character encoding. + * 2) salt, iteration count, and to be derived key length are supplied + * + * @author Valerie Peng + * + * See also same named class from OpenJDK. This class makes use of similar code. + */ +final class PBKDF2KeyImpl implements javax.crypto.interfaces.PBEKey { + + @java.io.Serial + private static final long serialVersionUID = -2234868909660948157L; + + private char[] passwd; + private byte[] salt; + private final int iterCount; + private byte[] key; + private String prfAlgorithm; + + private static byte[] getPasswordBytes(char[] passwd) { + CharBuffer cb = CharBuffer.wrap(passwd); + ByteBuffer bb = UTF_8.encode(cb); + + int len = bb.limit(); + byte[] passwdBytes = new byte[len]; + bb.get(passwdBytes, 0, len); + bb.clear().put(new byte[len]); + + return passwdBytes; + } + + /** + * Creates a PBE key from a given PBE key specification. + * + * @param keySpec the given PBE key specification + * @param prfAlgo the given PBE key algorithm + */ + PBKDF2KeyImpl(OpenJCEPlusProvider provider, PBEKeySpec keySpec, String prfAlgo) + throws InvalidKeySpecException { + this.passwd = keySpec.getPassword(); + // Convert the password from char[] to byte[] + byte[] passwdBytes = getPasswordBytes(this.passwd); + + try { + this.salt = keySpec.getSalt(); + if (salt == null) { + throw new InvalidKeySpecException("Salt not found"); + } + this.iterCount = keySpec.getIterationCount(); + if (iterCount == 0) { + throw new InvalidKeySpecException("Iteration count not found"); + } else if (iterCount < 0) { + throw new InvalidKeySpecException("Iteration count is negative"); + } + int keyLength = keySpec.getKeyLength(); + if (keyLength == 0) { + throw new InvalidKeySpecException("Key length not found"); + } else if (keyLength < 0) { + throw new InvalidKeySpecException("Key length is negative"); + } + + // Perform extra FIPS 140-3 related input checks. + if (provider.getName().equalsIgnoreCase("OpenJCEPlusFIPS")) { + // Key length must be higher then 112 bits. + if (keyLength < 112) { + throw new InvalidKeySpecException("Key length must be 112 bits or higher when using the OpenJCEPlusFIPS provider."); + } + + // Salt must be 128 bits. + if (salt.length * 8 < 128) { + throw new InvalidKeySpecException("Salt must be 128 bits or higher when using the OpenJCEPlusFIPS provider."); + } + + // Iteration count must be 1000 or higher. + if (iterCount < 1000) { + throw new InvalidKeySpecException("Iteration count must be 1000 or higher when using the OpenJCEPlusFIPS provider."); + } + + // Password length must be 10 characters or more. + if (this.passwd.length < 10) { + throw new InvalidKeySpecException("Password must be 10 characters or higher when using the OpenJCEPlusFIPS provider."); + } + } + + this.prfAlgorithm = prfAlgo; + + // Convert key length to bytes and derive key using OCKC. + try { + this.key = PBKDF.PBKDF2derive(provider.getOCKContext(), this.prfAlgorithm, + passwdBytes, salt, iterCount, keyLength / 8); + } catch (OCKException e) { + throw new InvalidKeySpecException( + "Error while deriving PBKDF2 key from a given PBEKeySpec.", e); + } + + } finally { + Arrays.fill(passwdBytes, (byte) 0x00); + if (key == null) { + Arrays.fill(passwd, '\0'); + } + } + } + + public byte[] getEncoded() { + try { + return key.clone(); + } finally { + // prevent this from being cleaned for the above block + Reference.reachabilityFence(this); + } + } + + public String getAlgorithm() { + return "PBKDF2With" + prfAlgorithm; + } + + public int getIterationCount() { + return iterCount; + } + + public char[] getPassword() { + try { + return passwd.clone(); + } finally { + // prevent this from being cleaned for the above block + Reference.reachabilityFence(this); + } + } + + public byte[] getSalt() { + return salt.clone(); + } + + public String getFormat() { + return "RAW"; + } + + /** + * Calculates a hash code value for the object. + * Objects that are equal will also have the same hashcode. + */ + @Override + public int hashCode() { + try { + return Arrays.hashCode(this.key) + ^ getAlgorithm().toLowerCase(Locale.ENGLISH).hashCode(); + } finally { + // prevent this from being cleaned for the above block + Reference.reachabilityFence(this); + } + } + + public boolean equals(Object obj) { + try { + if (obj == this) { + return true; + } + + if (!(obj instanceof SecretKey)) { + return false; + } + + SecretKey that = (SecretKey) obj; + + if (!(that.getAlgorithm().equalsIgnoreCase(getAlgorithm()))) { + return false; + } + if (!(that.getFormat().equalsIgnoreCase("RAW"))) { + return false; + } + byte[] thatEncoded = that.getEncoded(); + boolean ret = MessageDigest.isEqual(key, thatEncoded); + Arrays.fill(thatEncoded, (byte) 0x00); + return ret; + } finally { + // prevent this from being cleaned for the above block + Reference.reachabilityFence(this); + } + } + + /** + * Replace the PBE key to be serialized. + * + * @return the standard KeyRep object to be serialized + * + * @throws ObjectStreamException if a new object representing + * this PBE key could not be created + */ + @java.io.Serial + private Object writeReplace() throws ObjectStreamException { + try { + return new KeyRep(KeyRep.Type.SECRET, getAlgorithm(), getFormat(), key); + } finally { + // prevent this from being cleaned for the above block + Reference.reachabilityFence(this); + } + } + + /** + * Restores the state of this object from the stream. + *

+ * Deserialization of this class is not supported. + * + * @param stream the {@code ObjectInputStream} from which data is read + * @throws IOException if an I/O error occurs + * @throws ClassNotFoundException if a serialized class cannot be loaded + */ + @java.io.Serial + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + throw new InvalidObjectException("PBKDF2KeyImpl keys are not directly deserializable"); + } + + /** + * Cleans all sensitive information associated with this instance. + */ + protected void finalize() throws Throwable { + try { + if (this.key != null) { + java.util.Arrays.fill(this.key, (byte) 0x00); + this.key = null; + } + if (this.passwd != null) { + java.util.Arrays.fill(this.passwd, '0'); + this.passwd = null; + } + if (this.salt != null) { + java.util.Arrays.fill(this.salt, (byte) 0x00); + this.salt = null; + } + } finally { + super.finalize(); + } + } +} diff --git a/src/main/java/com/ibm/crypto/plus/provider/ock/NativeInterface.java b/src/main/java/com/ibm/crypto/plus/provider/ock/NativeInterface.java index d1c920de..7289e91f 100644 --- a/src/main/java/com/ibm/crypto/plus/provider/ock/NativeInterface.java +++ b/src/main/java/com/ibm/crypto/plus/provider/ock/NativeInterface.java @@ -1,5 +1,5 @@ /* - * Copyright IBM Corp. 2023, 2024 + * Copyright IBM Corp. 2023, 2025 * * Licensed under the Apache License 2.0 (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy @@ -846,4 +846,11 @@ static public native byte[] HKDF_derive(long ockContextId, long hkdfId, byte[] s static public native void HKDF_delete(long ockContextId, long hkdfId) throws OCKException; static public native int HKDF_size(long ockContextId, long hkdfId) throws OCKException; + + // ========================================================================= + // Password based key derivation functions ( PBKDF ) + // ========================================================================= + + static public native byte[] PBKDF2_derive(long ockContextId, String hashAlgorithm, byte[] password, byte[] salt, + int iterations, int keyLength) throws OCKException; } diff --git a/src/main/java/com/ibm/crypto/plus/provider/ock/PBKDF.java b/src/main/java/com/ibm/crypto/plus/provider/ock/PBKDF.java new file mode 100644 index 00000000..cb9c3238 --- /dev/null +++ b/src/main/java/com/ibm/crypto/plus/provider/ock/PBKDF.java @@ -0,0 +1,74 @@ +/* + * Copyright IBM Corp. 2025 + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution. + */ + +package com.ibm.crypto.plus.provider.ock; + +/** + * Provides native implementations for password based key derivation related functions. + */ +public final class PBKDF { + + /** + * Derives a key from a password using PBKDF2 defined + * in PKCS#5 v2.0. + * + * @param ockContext The OCKC context to use for deriving a key. + * @param algorithmName The has to use in associated with PBDKF2, for example HmacSHA512. + * @param password The password to derive a key from. + * @param salt A salt + * @param iterations The number of iterations to use when deriving the key. + * @param keyLength The desired length of the key to be derived. + * @return An array of bytes representing the key that was derived. + * @throws OCKException If input parameters are incorrect or an error occurs in OCKC deriving the key. + */ + public static byte[] PBKDF2derive(OCKContext ockContext, String algorithmName, + final byte[] password, byte[] salt, int iterations, int keyLength) throws OCKException { + + if ((!algorithmName.equalsIgnoreCase("HmacSHA512")) + && (!algorithmName.equalsIgnoreCase("HmacSHA384")) + && (!algorithmName.equalsIgnoreCase("HmacSHA256")) + && (!algorithmName.equalsIgnoreCase("HmacSHA224")) + && (!algorithmName.equalsIgnoreCase("HmacSHA1"))) { + throw new OCKException("Algorithm name not recognized: " + algorithmName); + } + String algorithmHashName = algorithmName.substring(4).toUpperCase(); + + if (keyLength <= 0) { + throw new OCKException("Key length is less then or equal to 0"); + } + + if (ockContext == null) { + throw new OCKException("Context is null"); + } + + if (algorithmName == null || algorithmName.isEmpty()) { + throw new OCKException("Hash algorithm is null or empty"); + } + + if (password == null) { + throw new OCKException("Password is null"); + } + + if ((salt == null) || (salt.length == 0)) { + throw new OCKException("Salt is null or length 0"); + } + + if (iterations <= 0) { + throw new OCKException("Iterations is less then or equal to 0"); + } + + byte[] key = NativeInterface.PBKDF2_derive(ockContext.getId(), algorithmHashName, password, + salt, iterations, keyLength); + + if (null == key) { + throw new OCKException("Error deriving key using PBKDF2. Key is null."); + } + + return key; + } +} diff --git a/src/main/native/PBKDF.c b/src/main/native/PBKDF.c new file mode 100644 index 00000000..15552c20 --- /dev/null +++ b/src/main/native/PBKDF.c @@ -0,0 +1,169 @@ +/* + * Copyright IBM Corp. 2025 + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "com_ibm_crypto_plus_provider_ock_NativeInterface.h" +#include "Utils.h" +#include + +//============================================================================ +/* + * Class: com_ibm_crypto_plus_provider_ock_NativeInterface + * Method: PBKDF2_derive + */ +JNIEXPORT jbyteArray JNICALL +Java_com_ibm_crypto_plus_provider_ock_NativeInterface_PBKDF2_1derive( + JNIEnv *env, jclass thisObj, jlong contextId, jstring hashAlgorithm, + jbyteArray password, jbyteArray salt, jint iterations, jint keyLength) { + static const char *functionName = "NativeInterface.PBKDF2_derive"; + ICC_CTX *ockCtx = (ICC_CTX *)((intptr_t)contextId); + const char *hashAlgorithmChars = NULL; + unsigned char *saltNative = NULL; + const char *passwordNative = NULL; + const ICC_EVP_MD *messageDigest = NULL; + jbyteArray resultDerivedKey = NULL; + unsigned char *resultDerivedKeyNative = NULL; + jboolean isCopy = 0; + int saltLength = 0; + int passwordLength = 0; + int rc = 0; + + if (debug) { + gslogFunctionEntry(functionName); + } + + // Validation is assumed by the caller for the key length input, + // context, algorithm, and password. + + // Get the hash algorithm name. + hashAlgorithmChars = (*env)->GetStringUTFChars(env, hashAlgorithm, NULL); + if (NULL == hashAlgorithmChars) { +#ifdef DEBUG_PBKDF_DETAIL + if (debug) { + gslogMessage( + "DETAIL_PBKDF FAILURE: Failed to get hash algorithm name"); + } +#endif + throwOCKException(env, 0, "Failed to get hash algorithm name"); + goto cleanup; + } + + // Get the salt. + saltNative = (*env)->GetPrimitiveArrayCritical(env, salt, &isCopy); + if (NULL == saltNative) { +#ifdef DEBUG_PBKDF_DETAIL + if (debug) { + gslogMessage("DETAIL_PBKDF FAILURE: Failed to get salt"); + } +#endif + throwOCKException(env, 0, "Failed to get salt"); + goto cleanup; + } + saltLength = (*env)->GetArrayLength(env, salt); + + // Get the password. + passwordNative = (*env)->GetPrimitiveArrayCritical(env, password, &isCopy); + if (NULL == passwordNative) { +#ifdef DEBUG_PBKDF_DETAIL + if (debug) { + gslogMessage("DETAIL_PBKDF FAILURE: Failed to get password data"); + } +#endif + throwOCKException(env, 0, "Failed to get password data"); + goto cleanup; + } + passwordLength = (*env)->GetArrayLength(env, password); + + // Get the message digest specified by hashAlgorithmChars. + messageDigest = ICC_EVP_get_digestbyname(ockCtx, hashAlgorithmChars); + if (NULL == messageDigest) { +#ifdef DEBUG_PBKDF_DETAIL + if (debug) { + gslogMessage( + "DETAIL_PBKDF FAILURE: Failed to initialize hash function"); + } +#endif + throwOCKException(env, 0, "Failed to initialize hash function"); + goto cleanup; + } + + // Allocate the result. + resultDerivedKey = (*env)->NewByteArray(env, keyLength); + if (NULL == resultDerivedKey) { +#ifdef DEBUG_PBKDF_DETAIL + if (debug) { + gslogMessage("DETAIL_PBKDF FAILURE: Failed to create result array"); + } +#endif + throwOCKException(env, 0, "Failed to create result array"); + goto cleanup; + } + + // Get pointer to result we just allocated. + resultDerivedKeyNative = + (unsigned char *)((*env)->GetPrimitiveArrayCritical( + env, resultDerivedKey, &isCopy)); + if (NULL == resultDerivedKeyNative) { +#ifdef DEBUG_PBKDF_DETAIL + if (debug) { + gslogMessage( + "DETAIL_PBKDF FAILURE: Failed to get native derived key"); + } +#endif + throwOCKException(env, 0, "Failed to get native derived key"); + goto cleanup; + } + + // Execute PBKDF2 key derivation + rc = ICC_PKCS5_PBKDF2_HMAC( + ockCtx, passwordNative, passwordLength, saltNative, saltLength, + iterations, messageDigest, keyLength, resultDerivedKeyNative); + if (ICC_OSSL_SUCCESS != rc) { +#ifdef DEBUG_PBKDF_DETAIL + if (debug) { + gslogMessage("DETAIL_PBKDF FAILURE: Key derivation failed"); + } +#endif + throwOCKException(env, 0, "Key derivation failed"); + goto cleanup; + } + +// Release all necessary resources. +cleanup: + if (NULL != resultDerivedKeyNative) { + (*env)->ReleasePrimitiveArrayCritical(env, resultDerivedKey, + resultDerivedKeyNative, 0); + resultDerivedKeyNative = NULL; + } + if (NULL != passwordNative) { + (*env)->ReleasePrimitiveArrayCritical(env, password, + (void *)passwordNative, 0); + passwordNative = NULL; + } + if (NULL != saltNative) { + (*env)->ReleasePrimitiveArrayCritical(env, salt, saltNative, 0); + saltNative = NULL; + } + if (NULL != hashAlgorithmChars) { + (*env)->ReleaseStringUTFChars(env, hashAlgorithm, hashAlgorithmChars); + hashAlgorithmChars = NULL; + } + + if (debug) { + gslogFunctionExit(functionName); + } + + return resultDerivedKey; +} diff --git a/src/main/native/jgskit.mac.mak b/src/main/native/jgskit.mac.mak index 5ec191a8..f9336169 100644 --- a/src/main/native/jgskit.mac.mak +++ b/src/main/native/jgskit.mac.mak @@ -1,6 +1,6 @@ ############################################################################### # -# Copyright IBM Corp. 2023, 2024 +# Copyright IBM Corp. 2023, 2025 # # Licensed under the Apache License 2.0 (the "License"). You may not use # this file except in compliance with the License. You can obtain a copy @@ -22,7 +22,7 @@ endif #Setting this flag will result non key material such as handle to OCK Objects etc being logged to the trace file. #This flag must be disabled before building production version #DEBUG_FLAGS += -DDEBUG -#DEBUG_DETAIL = -DDEBUG_RANDOM_DETAIL -DDEBUG_RAND_DETAIL -DDEBUG_DH_DETAIL -DDEBUG_DSA_DETAIL -DDEBUG_DIGEST_DETAIL -DDEBUG_EC_DETAIL -DDEBUG_EXTENDED_RANDOM_DETAIL -DDEBUG_GCM_DETAIL -DDEBUG_CCM_DETAIL -DDEBUG_HMAC_DETAIL -DDEBUG_PKEY_DETAIL -DDEBUG_CIPHER_DETAIL -DDEBUG_RSA_DETAIL -DDEBUG_SIGNATURE_DETAIL -DDEBUG_SIGNATURE_DSANONE_DETAIL -DDEBUG_SIGNATURE_RSASSL_DETAIL -DDEBUG_HKDF_DETAIL -DDEBUG_RSAPSS_DETAIL -DDEBUG_SIGNATURE_EDDSA_DETAIL +#DEBUG_DETAIL = -DDEBUG_RANDOM_DETAIL -DDEBUG_RAND_DETAIL -DDEBUG_DH_DETAIL -DDEBUG_DSA_DETAIL -DDEBUG_DIGEST_DETAIL -DDEBUG_EC_DETAIL -DDEBUG_EXTENDED_RANDOM_DETAIL -DDEBUG_GCM_DETAIL -DDEBUG_CCM_DETAIL -DDEBUG_HMAC_DETAIL -DDEBUG_PKEY_DETAIL -DDEBUG_CIPHER_DETAIL -DDEBUG_RSA_DETAIL -DDEBUG_SIGNATURE_DETAIL -DDEBUG_SIGNATURE_DSANONE_DETAIL -DDEBUG_SIGNATURE_RSASSL_DETAIL -DDEBUG_HKDF_DETAIL -DDEBUG_RSAPSS_DETAIL -DDEBUG_SIGNATURE_EDDSA_DETAIL -DDEBUG_PBKDF_DETAIL #Setting this flag will result sensitive key material such as private/public key bytes/parameter bytes being logged to the trace file. #Please warn the customer know that it not suitable to deploy jgskit library on production system, enabling this flag. @@ -47,6 +47,7 @@ OBJS = \ ${HOSTOUT}/GCM.o \ ${HOSTOUT}/HKDF.o \ ${HOSTOUT}/HMAC.o \ + ${HOSTOUT}/PBKDF.o \ ${HOSTOUT}/PKey.o \ ${HOSTOUT}/Poly1305Cipher.o \ ${HOSTOUT}/RSA.o \ diff --git a/src/main/native/jgskit.mak b/src/main/native/jgskit.mak index 0d3f9adb..523276dd 100644 --- a/src/main/native/jgskit.mak +++ b/src/main/native/jgskit.mak @@ -1,6 +1,6 @@ ############################################################################### # -# Copyright IBM Corp. 2023, 2024 +# Copyright IBM Corp. 2023, 2025 # # Licensed under the Apache License 2.0 (the "License"). You may not use # this file except in compliance with the License. You can obtain a copy @@ -56,7 +56,7 @@ endif #Setting this flag will result non key material such as handle to OCK Objects etc being logged to the trace file. #This flag must be disabled before building production version #DEBUG_FLAGS += -DDEBUG -#DEBUG_DETAIL = -DDEBUG_RANDOM_DETAIL -DDEBUG_RAND_DETAIL -DDEBUG_DH_DETAIL -DDEBUG_DSA_DETAIL -DDEBUG_DIGEST_DETAIL -DDEBUG_EC_DETAIL -DDEBUG_EXTENDED_RANDOM_DETAIL -DDEBUG_GCM_DETAIL -DDEBUG_CCM_DETAIL -DDEBUG_HMAC_DETAIL -DDEBUG_PKEY_DETAIL -DDEBUG_CIPHER_DETAIL -DDEBUG_RSA_DETAIL -DDEBUG_SIGNATURE_DETAIL -DDEBUG_SIGNATURE_DSANONE_DETAIL -DDEBUG_SIGNATURE_RSASSL_DETAIL -DDEBUG_HKDF_DETAIL -DDEBUG_RSAPSS_DETAIL -DDEBUG_SIGNATURE_EDDSA_DETAIL +#DEBUG_DETAIL = -DDEBUG_RANDOM_DETAIL -DDEBUG_RAND_DETAIL -DDEBUG_DH_DETAIL -DDEBUG_DSA_DETAIL -DDEBUG_DIGEST_DETAIL -DDEBUG_EC_DETAIL -DDEBUG_EXTENDED_RANDOM_DETAIL -DDEBUG_GCM_DETAIL -DDEBUG_CCM_DETAIL -DDEBUG_HMAC_DETAIL -DDEBUG_PKEY_DETAIL -DDEBUG_CIPHER_DETAIL -DDEBUG_RSA_DETAIL -DDEBUG_SIGNATURE_DETAIL -DDEBUG_SIGNATURE_DSANONE_DETAIL -DDEBUG_SIGNATURE_RSASSL_DETAIL -DDEBUG_HKDF_DETAIL -DDEBUG_RSAPSS_DETAIL -DDEBUG_SIGNATURE_EDDSA_DETAIL -DDEBUG_PBKDF_DETAIL #Setting this flag will result sensitive key material such as private/public key bytes/parameter bytes being logged to the trace file. #Please warn the customer know that it not suitable to deploy jgskit library on production system, enabling this flag. @@ -82,6 +82,7 @@ OBJS = \ ${HOSTOUT}/GCM.o \ ${HOSTOUT}/HKDF.o \ ${HOSTOUT}/HMAC.o \ + ${HOSTOUT}/PBKDF.o \ ${HOSTOUT}/PKey.o \ ${HOSTOUT}/Poly1305Cipher.o \ ${HOSTOUT}/RSA.o \ diff --git a/src/main/native/jgskit.win64.cygwin.mak b/src/main/native/jgskit.win64.cygwin.mak index dbc3e93b..3249f449 100644 --- a/src/main/native/jgskit.win64.cygwin.mak +++ b/src/main/native/jgskit.win64.cygwin.mak @@ -1,6 +1,6 @@ ############################################################################### # -# Copyright IBM Corp. 2023, 2024 +# Copyright IBM Corp. 2023, 2025 # # Licensed under the Apache License 2.0 (the "License"). You may not use # this file except in compliance with the License. You can obtain a copy @@ -13,7 +13,7 @@ TOPDIR = $(MAKEDIR)\..\..\.. PLAT = win CFLAGS= -nologo -DWINDOWS -#DEBUG_DETAIL = -DDEBUG_RANDOM_DETAIL -DDEBUG_RAND_DETAIL -DDEBUG_DH_DETAIL -DDEBUG_DSA_DETAIL -DDEBUG_DIGEST_DETAIL -DDEBUG_EC_DETAIL -DDEBUG_EXTENDED_RANDOM_DETAIL -DDEBUG_GCM_DETAIL -DDEBUG_CCM_DETAIL -DDEBUG_HMAC_DETAIL -DDEBUG_PKEY_DETAIL -DDEBUG_CIPHER_DETAIL -DDEBUG_RSA_DETAIL -DDEBUG_SIGNATURE_DETAIL -DDEBUG_SIGNATURE_DSANONE_DETAIL -DDEBUG_SIGNATURE_RSASSL_DETAIL -DDEBUG_HKDF_DETAIL -DDEBUG_RSAPSS_DETAIL -DDEBUG_SIGNATURE_EDDSA_DETAIL +#DEBUG_DETAIL = -DDEBUG_RANDOM_DETAIL -DDEBUG_RAND_DETAIL -DDEBUG_DH_DETAIL -DDEBUG_DSA_DETAIL -DDEBUG_DIGEST_DETAIL -DDEBUG_EC_DETAIL -DDEBUG_EXTENDED_RANDOM_DETAIL -DDEBUG_GCM_DETAIL -DDEBUG_CCM_DETAIL -DDEBUG_HMAC_DETAIL -DDEBUG_PKEY_DETAIL -DDEBUG_CIPHER_DETAIL -DDEBUG_RSA_DETAIL -DDEBUG_SIGNATURE_DETAIL -DDEBUG_SIGNATURE_DSANONE_DETAIL -DDEBUG_SIGNATURE_RSASSL_DETAIL -DDEBUG_HKDF_DETAIL -DDEBUG_RSAPSS_DETAIL -DDEBUG_SIGNATURE_EDDSA_DETAIL -DDEBUG_PBKDF_DETAIL #Setting this flag will result sensitive key material such as private/public key bytes/parameter bytes being logged to the trace file. #Please warn the customer know that it not suitable to deploy jgskit library on production system, enabling this flag. @@ -37,6 +37,7 @@ OBJS= \ GCM.obj \ HKDF.obj \ HMAC.obj \ + PBKDF.obj \ PKey.obj \ Poly1305Cipher.obj \ RSA.obj \ diff --git a/src/main/native/jgskit.win64.mak b/src/main/native/jgskit.win64.mak index fcfd955a..2b462448 100644 --- a/src/main/native/jgskit.win64.mak +++ b/src/main/native/jgskit.win64.mak @@ -1,6 +1,6 @@ ############################################################################### # -# Copyright IBM Corp. 2023, 2024 +# Copyright IBM Corp. 2023, 2025 # # Licensed under the Apache License 2.0 (the "License"). You may not use # this file except in compliance with the License. You can obtain a copy @@ -13,7 +13,7 @@ TOPDIR = $(MAKEDIR)../../.. PLAT = win CFLAGS= -nologo -DWINDOWS -#DEBUG_DETAIL = -DDEBUG_RANDOM_DETAIL -DDEBUG_RAND_DETAIL -DDEBUG_DH_DETAIL -DDEBUG_DSA_DETAIL -DDEBUG_DIGEST_DETAIL -DDEBUG_EC_DETAIL -DDEBUG_EXTENDED_RANDOM_DETAIL -DDEBUG_GCM_DETAIL -DDEBUG_CCM_DETAIL -DDEBUG_HMAC_DETAIL -DDEBUG_PKEY_DETAIL -DDEBUG_CIPHER_DETAIL -DDEBUG_RSA_DETAIL -DDEBUG_SIGNATURE_DETAIL -DDEBUG_SIGNATURE_DSANONE_DETAIL -DDEBUG_SIGNATURE_RSASSL_DETAIL -DDEBUG_HKDF_DETAIL -DDEBUG_RSAPSS_DETAIL -DDEBUG_SIGNATURE_EDDSA_DETAIL +#DEBUG_DETAIL = -DDEBUG_RANDOM_DETAIL -DDEBUG_RAND_DETAIL -DDEBUG_DH_DETAIL -DDEBUG_DSA_DETAIL -DDEBUG_DIGEST_DETAIL -DDEBUG_EC_DETAIL -DDEBUG_EXTENDED_RANDOM_DETAIL -DDEBUG_GCM_DETAIL -DDEBUG_CCM_DETAIL -DDEBUG_HMAC_DETAIL -DDEBUG_PKEY_DETAIL -DDEBUG_CIPHER_DETAIL -DDEBUG_RSA_DETAIL -DDEBUG_SIGNATURE_DETAIL -DDEBUG_SIGNATURE_DSANONE_DETAIL -DDEBUG_SIGNATURE_RSASSL_DETAIL -DDEBUG_HKDF_DETAIL -DDEBUG_RSAPSS_DETAIL -DDEBUG_SIGNATURE_EDDSA_DETAIL -DDEBUG_PBKDF_DETAIL #Setting this flag will result sensitive key material such as private/public key bytes/parameter bytes being logged to the trace file. #Please warn the customer know that it not suitable to deploy jgskit library on production system, enabling this flag. @@ -38,6 +38,7 @@ OBJS= \ $(HOSTOUT)/GCM.obj \ $(HOSTOUT)/HKDF.obj \ $(HOSTOUT)/HMAC.obj \ + $(HOSTOUT)/PBKDF.obj \ $(HOSTOUT)/PKey.obj \ $(HOSTOUT)/Poly1305Cipher.obj \ $(HOSTOUT)/RSA.obj \ diff --git a/src/test/java/ibm/jceplus/junit/base/BaseTestPBKDF2.java b/src/test/java/ibm/jceplus/junit/base/BaseTestPBKDF2.java new file mode 100644 index 00000000..76c2ddce --- /dev/null +++ b/src/test/java/ibm/jceplus/junit/base/BaseTestPBKDF2.java @@ -0,0 +1,190 @@ +/* + * Copyright IBM Corp. 2025 + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution. + */ +package ibm.jceplus.junit.base; + +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.ArrayList; +import java.util.List; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests associated with PBKDF2 algorithms. + * + */ +public class BaseTestPBKDF2 extends BaseTestJunit5Interop { + static List allowableFIPSAlgorithms = new ArrayList(){{ + add("PBKDF2WithHmacSHA224"); + add("PBKDF2WithHmacSHA256"); + add("PBKDF2WithHmacSHA384"); + add("PBKDF2WithHmacSHA512"); + }}; + + /** + * Official test vector from RFC 7914. + * + * 11. Test Vectors for PBKDF2 with HMAC-SHA-256 + * + * Below is a sequence of octets that illustrate input and output values + * for PBKDF2-HMAC-SHA-256. The octets are hex encoded and whitespace + * is inserted for readability. The test vectors below can be used to + * verify the PBKDF2-HMAC-SHA-256 [RFC2898] function. The password and + * salt strings are passed as sequences of ASCII [RFC20] octets. + * + * PBKDF2-HMAC-SHA-256 (P="passwd", S="salt", + * c=1, dkLen=64) = + * 55 ac 04 6e 56 e3 08 9f ec 16 91 c2 25 44 b6 05 + * f9 41 85 21 6d de 04 65 e6 8b 9d 57 c2 0d ac bc + * 49 ca 9c cc f1 79 b6 45 99 16 64 b3 9d 77 ef 31 + * 7c 71 b8 45 b1 e3 0b d5 09 11 20 41 d3 a1 97 83 + * + * PBKDF2-HMAC-SHA-256 (P="Password", S="NaCl", + * c=80000, dkLen=64) = + * 4d dc d8 f6 0b 98 be 21 83 0c ee 5e f2 27 01 f9 + * 64 1a 44 18 d0 4c 04 14 ae ff 08 87 6b 34 ab 56 + * a1 d4 25 a1 22 58 33 54 9a db 84 1b 51 c9 b3 17 + * 6a 27 2b de bb a1 d0 78 47 8f 62 b3 97 f3 3c 8d + * + * @param algorithm + * @throws Exception + */ + @Test + public void testPBKDF2KAT() throws Exception { + + SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256", + this.getProviderName()); + PBEKeySpec pbeks = new PBEKeySpec("passwd".toCharArray(), "salt".getBytes("ASCII"), + 1, 512); + SecretKey sk = skf.generateSecret(pbeks); + String hexKeyValue = BaseUtils.bytesToHex(sk.getEncoded()); + assertEquals("55ac046e56e3089fec1691c22544b605" + + "f94185216dde0465e68b9d57c20dacbc" + + "49ca9cccf179b645991664b39d77ef31" + + "7c71b845b1e30bd509112041d3a19783", hexKeyValue, + "RFC Known answer test failed for PBKDF2WithHmacSHA256."); + pbeks = new PBEKeySpec("Password".toCharArray(), "NaCl".getBytes("ASCII"), 80000, + 512); + sk = skf.generateSecret(pbeks); + hexKeyValue = BaseUtils.bytesToHex(sk.getEncoded()); + assertEquals("4ddcd8f60b98be21830cee5ef22701f9" + + "641a4418d04c0414aeff08876b34ab56" + + "a1d425a1225833549adb841b51c9b317" + + "6a272bdebba1d078478f62b397f33c8d", hexKeyValue, + "RFC Known answer test failed for PBKDF2WithHmacSHA256."); + } + + @ParameterizedTest + @CsvSource({"PBKDF2WithHmacSHA1", "PBKDF2WithHmacSHA224", "PBKDF2WithHmacSHA256", + "PBKDF2WithHmacSHA384", "PBKDF2WithHmacSHA512"}) + public void testAlgorithmExistence(String algorithm) throws Exception { + try { + SecretKeyFactory.getInstance(algorithm, this.getProviderName()); + } catch (NoSuchAlgorithmException e) { + // The FIPS provider does not allow for PBKDF2WithHmacSHA1 and PBKDF2WithHmacSHA224. + if ((!isSupportedByOpenJCEPlusFIPS(algorithm)) && this.getProviderName().equalsIgnoreCase("OpenJCEPlusFIPS")) { + return; + } else { + throw e; + } + } + } + + @ParameterizedTest + @CsvSource({"PBKDF2WithHmacSHA224", "PBKDF2WithHmacSHA256", + "PBKDF2WithHmacSHA384", "PBKDF2WithHmacSHA512"}) + public void testSmallSalt(String algorithm) throws Exception { + + PBEKeySpec pbeks = new PBEKeySpec("ABCDEFGHIJ".toCharArray(), "SmallSalt".getBytes(), 10000, 512); + if (this.getProviderName().equalsIgnoreCase("OpenJCEPlusFIPS")) { + try { + SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm, this.getProviderName()); + skf.generateSecret(pbeks); + } catch(InvalidKeySpecException e) { + assertEquals("Salt must be 128 bits or higher when using the OpenJCEPlusFIPS provider.", e.getMessage()); + } + } else { + SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm, this.getProviderName()); + skf.generateSecret(pbeks); + } + } + + @ParameterizedTest + @CsvSource({"PBKDF2WithHmacSHA224", "PBKDF2WithHmacSHA256", + "PBKDF2WithHmacSHA384", "PBKDF2WithHmacSHA512"}) + public void testSmallIterationCount(String algorithm) throws Exception { + PBEKeySpec pbeks = new PBEKeySpec("ABCDEFGHIJ".toCharArray(), new byte[32], 999, 512); + if (this.getProviderName().equalsIgnoreCase("OpenJCEPlusFIPS")) { + try { + SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm, this.getProviderName()); + skf.generateSecret(pbeks); + } catch(InvalidKeySpecException e) { + assertEquals("Iteration count must be 1000 or higher when using the OpenJCEPlusFIPS provider.", e.getMessage()); + } + } else { + SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm, this.getProviderName()); + skf.generateSecret(pbeks); + } + } + + @ParameterizedTest + @CsvSource({"PBKDF2WithHmacSHA224", "PBKDF2WithHmacSHA256", + "PBKDF2WithHmacSHA384", "PBKDF2WithHmacSHA512"}) + public void testSmallKeyLength(String algorithm) throws Exception { + PBEKeySpec pbeks = new PBEKeySpec("ABCDEFGHIJ".toCharArray(), new byte[32], 8000, 111); + if (this.getProviderName().equalsIgnoreCase("OpenJCEPlusFIPS")) { + try { + SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm, this.getProviderName()); + skf.generateSecret(pbeks); + } catch(InvalidKeySpecException e) { + assertEquals("Key length must be 112 bits or higher when using the OpenJCEPlusFIPS provider.", e.getMessage()); + } + } else { + SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm, this.getProviderName()); + skf.generateSecret(pbeks); + } + } + + @ParameterizedTest + @CsvSource({"PBKDF2WithHmacSHA224", "PBKDF2WithHmacSHA256", + "PBKDF2WithHmacSHA384", "PBKDF2WithHmacSHA512"}) + public void testShortPassword(String algorithm) throws Exception { + PBEKeySpec pbeks = new PBEKeySpec("ABCDEFGHI".toCharArray(), new byte[32], 1000, 112); + if (this.getProviderName().equalsIgnoreCase("OpenJCEPlusFIPS")) { + try { + SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm, this.getProviderName()); + skf.generateSecret(pbeks); + } catch(InvalidKeySpecException e) { + assertEquals("Password must be 10 characters or higher when using the OpenJCEPlusFIPS provider.", e.getMessage()); + } + } else { + SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm, this.getProviderName()); + skf.generateSecret(pbeks); + } + } + + /** + * Method to help determine if the OpenJCEPlusFIPS provider supports an algorithm. + * + * @param algorithm + * @return + */ + private boolean isSupportedByOpenJCEPlusFIPS(String algorithm) { + for (String allowed : allowableFIPSAlgorithms) { + if (allowed.equalsIgnoreCase(algorithm)) { + return true; + } + } + return false; + } +} diff --git a/src/test/java/ibm/jceplus/junit/base/BaseTestPBKDF2Interop.java b/src/test/java/ibm/jceplus/junit/base/BaseTestPBKDF2Interop.java new file mode 100644 index 00000000..a41526e2 --- /dev/null +++ b/src/test/java/ibm/jceplus/junit/base/BaseTestPBKDF2Interop.java @@ -0,0 +1,244 @@ +/* + * Copyright IBM Corp. 2025 + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution. + */ +package ibm.jceplus.junit.base; + +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.List; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests to perform interoperability tests between a provider under test, + * typically OpenJCEPlus or OpenJCEPlusFIPS and another provider for + * PBKDF2 supported algorithms. + */ +public class BaseTestPBKDF2Interop extends BaseTestJunit5Interop { + + static final String PASSWORD = "Thequickbrownfoxjumpsoverthelazydog"; + static byte[] randomSalt = new byte[32]; + static SecureRandom random = new SecureRandom(); + static PBEKeySpec pbeks = null; + static List allowableFIPSAlgorithms = new ArrayList(){{ + add("PBKDF2WithHmacSHA224"); + add("PBKDF2WithHmacSHA256"); + add("PBKDF2WithHmacSHA384"); + add("PBKDF2WithHmacSHA512"); + }}; + + @BeforeAll + public void setUp() { + random.nextBytes(randomSalt); + this.pbeks = new PBEKeySpec(PASSWORD.toCharArray(), randomSalt, 5000, 112); + } + /** + * Test used to perform interoperability tests using a KeyFactory for + * the method `getAlgorithm()`. + */ + @ParameterizedTest + @CsvSource({"PBKDF2WithHmacSHA1", "PBKDF2WithHmacSHA224", "PBKDF2WithHmacSHA256", + "PBKDF2WithHmacSHA384", "PBKDF2WithHmacSHA512"}) + public void testGetAlgorithm(String algorithm) throws Exception { + + if ((!isSupportedByOpenJCEPlusFIPS(algorithm)) + && this.getProviderName().equalsIgnoreCase("OpenJCEPlusFIPS")) { + return; + } + + SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm, this.getProviderName()); + SecretKeyFactory skfInterop = SecretKeyFactory.getInstance(algorithm, + this.getInteropProviderName()); + + // Validate that the algorithm name from provider under test matches the interop provider. + System.out.println(" Checking getAlgorithm()"); + assertEquals(skf.getAlgorithm(), skfInterop.getAlgorithm(), + "Algorithm name is not as expected."); + } + + /** + * Test used to perform interoperability tests using a KeyFactory for + * the method `getEncoded()`. + */ + @ParameterizedTest + @CsvSource({"PBKDF2WithHmacSHA1", "PBKDF2WithHmacSHA224", "PBKDF2WithHmacSHA256", + "PBKDF2WithHmacSHA384", "PBKDF2WithHmacSHA512"}) + public void testGetEncoding(String algorithm) throws Exception { + + if ((!isSupportedByOpenJCEPlusFIPS(algorithm)) + && this.getProviderName().equalsIgnoreCase("OpenJCEPlusFIPS")) { + return; + } + + SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm, this.getProviderName()); + SecretKeyFactory skfInterop = SecretKeyFactory.getInstance(algorithm, + this.getInteropProviderName()); + + // Validate key encodings generated from provider under test matches the interop provider. + SecretKey sk1 = skf.generateSecret(pbeks); + SecretKey skInterop = skfInterop.generateSecret(pbeks); + assertArrayEquals(sk1.getEncoded(), skInterop.getEncoded(), "Key encodings do not match."); + } + + /** + * Test used to perform interoperability tests using a KeyFactory for + * the method `translateKey()`. + */ + @ParameterizedTest + @CsvSource({"PBKDF2WithHmacSHA1", "PBKDF2WithHmacSHA224", "PBKDF2WithHmacSHA256", + "PBKDF2WithHmacSHA384", "PBKDF2WithHmacSHA512"}) + public void testTranslate(String algorithm) throws Exception { + + if ((!isSupportedByOpenJCEPlusFIPS(algorithm)) + && this.getProviderName().equalsIgnoreCase("OpenJCEPlusFIPS")) { + return; + } + + SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm, this.getProviderName()); + SecretKeyFactory skfInterop = SecretKeyFactory.getInstance(algorithm, + this.getInteropProviderName()); + + // Validate key translations of the same key generated from provider under test matches the interop provider. + System.out.println(" Checking translateKey()"); + SecretKey sk1 = skf.generateSecret(pbeks); + SecretKey skInterop = skfInterop.generateSecret(pbeks); + SecretKey sk1T = skf.translateKey(sk1); + SecretKey skInteropT = skf.translateKey(skInterop); + assertArrayEquals(sk1T.getEncoded(), skInteropT.getEncoded(), + "Translated keys do not match."); + SecretKey sk1TI = skfInterop.translateKey(sk1); + SecretKey skInteropTI = skfInterop.translateKey(skInterop); + assertArrayEquals(sk1TI.getEncoded(), skInteropTI.getEncoded(), + "Translated keys do not match."); + } + + /** + * Test used to perform interoperability tests using a KeyFactory for + * the method `getKeySpec()`. + */ + @ParameterizedTest + @CsvSource({"PBKDF2WithHmacSHA1", "PBKDF2WithHmacSHA224", "PBKDF2WithHmacSHA256", + "PBKDF2WithHmacSHA384", "PBKDF2WithHmacSHA512"}) + public void testKeySpec(String algorithm) throws Exception { + + if ((!isSupportedByOpenJCEPlusFIPS(algorithm)) + && this.getProviderName().equalsIgnoreCase("OpenJCEPlusFIPS")) { + return; + } + + SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm, this.getProviderName()); + SecretKeyFactory skfInterop = SecretKeyFactory.getInstance(algorithm, + this.getInteropProviderName()); + + // Validate that the key spec produced by the provider under test matches the interop provider. + System.out.println(" Checking getKeySpec()"); + SecretKey sk1 = skf.generateSecret(pbeks); + SecretKey skInterop = skfInterop.generateSecret(pbeks); + PBEKeySpec ks1 = (PBEKeySpec) skf.getKeySpec(sk1, PBEKeySpec.class); + PBEKeySpec ksInterop = (PBEKeySpec) skfInterop.getKeySpec(skInterop, PBEKeySpec.class); + assertEquals(ks1.getIterationCount(), ksInterop.getIterationCount(), + "Iteration count does not match."); + assertEquals(ks1.getKeyLength(), ksInterop.getKeyLength(), "Key length does not match."); + assertArrayEquals(ks1.getPassword(), ksInterop.getPassword(), "Password does not match."); + assertArrayEquals(ks1.getSalt(), ksInterop.getSalt(), "Salt does not match."); + } + + /** + * Test used to perform interoperability tests using a KeyFactory for + * the method `hashCode()`. + */ + @ParameterizedTest + @CsvSource({"PBKDF2WithHmacSHA1", "PBKDF2WithHmacSHA224", "PBKDF2WithHmacSHA256", + "PBKDF2WithHmacSHA384", "PBKDF2WithHmacSHA512"}) + public void testHashCode(String algorithm) throws Exception { + + if ((!isSupportedByOpenJCEPlusFIPS(algorithm)) + && this.getProviderName().equalsIgnoreCase("OpenJCEPlusFIPS")) { + return; + } + + SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm, this.getProviderName()); + SecretKeyFactory skfInterop = SecretKeyFactory.getInstance(algorithm, + this.getInteropProviderName()); + SecretKey sk1 = skf.generateSecret(pbeks); + SecretKey skInterop = skfInterop.generateSecret(pbeks); + assertEquals(sk1.hashCode(), skInterop.hashCode(), "Hash codes do not match."); + } + + /** + * Test used to perform interoperability tests using a KeyFactory for + * the method `equals()`. + */ + @ParameterizedTest + @CsvSource({"PBKDF2WithHmacSHA1", "PBKDF2WithHmacSHA224", "PBKDF2WithHmacSHA256", + "PBKDF2WithHmacSHA384", "PBKDF2WithHmacSHA512"}) + public void testEquality(String algorithm) throws Exception { + + if ((!isSupportedByOpenJCEPlusFIPS(algorithm)) + && this.getProviderName().equalsIgnoreCase("OpenJCEPlusFIPS")) { + return; + } + + SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm, this.getProviderName()); + SecretKeyFactory skfInterop = SecretKeyFactory.getInstance(algorithm, + this.getInteropProviderName()); + SecretKey sk1 = skf.generateSecret(pbeks); + SecretKey skInterop = skfInterop.generateSecret(pbeks); + assertTrue(sk1.equals(skInterop), "Keys are not equal between different providers."); + assertTrue(sk1.equals(sk1), "Keys are not equal when key is exactly the same."); + PBEKeySpec pbeksDifferent = new PBEKeySpec("DifferentPW".toCharArray(), randomSalt, + 5000, 112); + SecretKey skDifferent = skf.generateSecret(pbeksDifferent); + assertFalse(sk1.equals(skDifferent), "Keys are not expected to be equal."); + } + + /** + * Test used to perform interoperability tests using a SecretKey generated + * by a providers KeyFactory for the method `getFormat()`. + */ + @ParameterizedTest + @CsvSource({"PBKDF2WithHmacSHA1", "PBKDF2WithHmacSHA224", "PBKDF2WithHmacSHA256", + "PBKDF2WithHmacSHA384", "PBKDF2WithHmacSHA512"}) + public void testGetFormat(String algorithm) throws Exception { + + if ((!isSupportedByOpenJCEPlusFIPS(algorithm)) + && this.getProviderName().equalsIgnoreCase("OpenJCEPlusFIPS")) { + return; + } + + SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm, this.getProviderName()); + SecretKeyFactory skfInterop = SecretKeyFactory.getInstance(algorithm, + this.getInteropProviderName()); + SecretKey sk1 = skf.generateSecret(pbeks); + SecretKey skInterop = skfInterop.generateSecret(pbeks); + assertEquals(sk1.getFormat(), skInterop.getFormat(), "Format does not match."); + + } + + /** + * Method to help determine if the OpenJCEPlusFIPS provider supports an algorithm. + * + * @param algorithm + * @return + */ + private boolean isSupportedByOpenJCEPlusFIPS(String algorithm) { + for (String allowed : allowableFIPSAlgorithms) { + if (allowed.equalsIgnoreCase(algorithm)) { + return true; + } + } + return false; + } +} diff --git a/src/test/java/ibm/jceplus/junit/openjceplus/TestAll.java b/src/test/java/ibm/jceplus/junit/openjceplus/TestAll.java index 888aa3fd..10b96007 100644 --- a/src/test/java/ibm/jceplus/junit/openjceplus/TestAll.java +++ b/src/test/java/ibm/jceplus/junit/openjceplus/TestAll.java @@ -1,5 +1,5 @@ /* - * Copyright IBM Corp. 2023, 2024 + * Copyright IBM Corp. 2023, 2025 * * Licensed under the Apache License 2.0 (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy @@ -93,6 +93,8 @@ TestIsAssignableFromOrder.class, TestMD5.class, TestMiniRSAPSS2.class, + TestPBKDF2.class, + TestPBKDF2Interop.class, TestPublicMethodsToMakeNonPublic.class, TestResetByteBuffer.class, TestRSA_1024.class, diff --git a/src/test/java/ibm/jceplus/junit/openjceplus/TestPBKDF2.java b/src/test/java/ibm/jceplus/junit/openjceplus/TestPBKDF2.java new file mode 100644 index 00000000..f74bc314 --- /dev/null +++ b/src/test/java/ibm/jceplus/junit/openjceplus/TestPBKDF2.java @@ -0,0 +1,24 @@ +/* + * Copyright IBM Corp. 2023, 2025 + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution. + */ + +package ibm.jceplus.junit.openjceplus; + +import ibm.jceplus.junit.base.BaseTestPBKDF2; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; + +@TestInstance(Lifecycle.PER_CLASS) +public class TestPBKDF2 extends BaseTestPBKDF2 { + + @BeforeAll + public void beforeAll() { + Utils.loadProviderTestSuite(); + setProviderName(Utils.TEST_SUITE_PROVIDER_NAME); + } +} diff --git a/src/test/java/ibm/jceplus/junit/openjceplus/TestPBKDF2Interop.java b/src/test/java/ibm/jceplus/junit/openjceplus/TestPBKDF2Interop.java new file mode 100644 index 00000000..0b8b03ec --- /dev/null +++ b/src/test/java/ibm/jceplus/junit/openjceplus/TestPBKDF2Interop.java @@ -0,0 +1,25 @@ +/* + * Copyright IBM Corp. 2023, 2025 + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution. + */ + +package ibm.jceplus.junit.openjceplus; + +import ibm.jceplus.junit.base.BaseTestPBKDF2Interop; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; + +@TestInstance(Lifecycle.PER_CLASS) +public class TestPBKDF2Interop extends BaseTestPBKDF2Interop { + + @BeforeAll + public void beforeAll() { + Utils.loadProviderTestSuite(); + setProviderName(Utils.TEST_SUITE_PROVIDER_NAME); + setInteropProviderName(Utils.PROVIDER_SunJCE); + } +} diff --git a/src/test/java/ibm/jceplus/junit/openjceplusfips/TestAll.java b/src/test/java/ibm/jceplus/junit/openjceplusfips/TestAll.java index 3593157b..2c3827ad 100644 --- a/src/test/java/ibm/jceplus/junit/openjceplusfips/TestAll.java +++ b/src/test/java/ibm/jceplus/junit/openjceplusfips/TestAll.java @@ -1,5 +1,5 @@ /* - * Copyright IBM Corp. 2023, 2024 + * Copyright IBM Corp. 2023, 2025 * * Licensed under the Apache License 2.0 (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy @@ -70,6 +70,8 @@ TestImplementationClassesFinal.class, TestInvalidArrayIndex.class, TestMiniRSAPSS2.class, + TestPBKDF2.class, + TestPBKDF2Interop.class, TestPublicMethodsToMakeNonPublic.class, TestResetByteBuffer.class, TestRSA_2048.class, diff --git a/src/test/java/ibm/jceplus/junit/openjceplusfips/TestPBKDF2.java b/src/test/java/ibm/jceplus/junit/openjceplusfips/TestPBKDF2.java new file mode 100644 index 00000000..64e7f253 --- /dev/null +++ b/src/test/java/ibm/jceplus/junit/openjceplusfips/TestPBKDF2.java @@ -0,0 +1,30 @@ +/* + * Copyright IBM Corp. 2023, 2025 + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution. + */ + +package ibm.jceplus.junit.openjceplusfips; + +import ibm.jceplus.junit.base.BaseTestPBKDF2; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; + +@TestInstance(Lifecycle.PER_CLASS) +public class TestPBKDF2 extends BaseTestPBKDF2 { + + @BeforeAll + public void beforeAll() { + Utils.loadProviderTestSuite(); + setProviderName(Utils.TEST_SUITE_PROVIDER_NAME); + } + + @Override + @Disabled("The KAT published salt value is not acceptable for FIPS, disable this test.") + public void testPBKDF2KAT() throws Exception { + } +} diff --git a/src/test/java/ibm/jceplus/junit/openjceplusfips/TestPBKDF2Interop.java b/src/test/java/ibm/jceplus/junit/openjceplusfips/TestPBKDF2Interop.java new file mode 100644 index 00000000..ef2cd8c9 --- /dev/null +++ b/src/test/java/ibm/jceplus/junit/openjceplusfips/TestPBKDF2Interop.java @@ -0,0 +1,25 @@ +/* + * Copyright IBM Corp. 2023, 2025 + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution. + */ + +package ibm.jceplus.junit.openjceplusfips; + +import ibm.jceplus.junit.base.BaseTestPBKDF2Interop; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; + +@TestInstance(Lifecycle.PER_CLASS) +public class TestPBKDF2Interop extends BaseTestPBKDF2Interop { + + @BeforeAll + public void beforeAll() { + Utils.loadProviderTestSuite(); + setProviderName(Utils.TEST_SUITE_PROVIDER_NAME); + setInteropProviderName(Utils.PROVIDER_SunJCE); + } +} From 8ac665a21cb1a7a4f5e5e6726a6fd462651b7b62 Mon Sep 17 00:00:00 2001 From: Jason Katonica Date: Tue, 28 Jan 2025 16:22:12 -0500 Subject: [PATCH 2/2] Hash code logic specific to Java 17 --- .../com/ibm/crypto/plus/provider/PBKDF2KeyImpl.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/ibm/crypto/plus/provider/PBKDF2KeyImpl.java b/src/main/java/com/ibm/crypto/plus/provider/PBKDF2KeyImpl.java index af74bf7b..f3741dca 100644 --- a/src/main/java/com/ibm/crypto/plus/provider/PBKDF2KeyImpl.java +++ b/src/main/java/com/ibm/crypto/plus/provider/PBKDF2KeyImpl.java @@ -170,15 +170,12 @@ public String getFormat() { * Calculates a hash code value for the object. * Objects that are equal will also have the same hashcode. */ - @Override public int hashCode() { - try { - return Arrays.hashCode(this.key) - ^ getAlgorithm().toLowerCase(Locale.ENGLISH).hashCode(); - } finally { - // prevent this from being cleaned for the above block - Reference.reachabilityFence(this); + int retval = 0; + for (int i = 1; i < this.key.length; i++) { + retval += this.key[i] * i; } + return(retval ^= getAlgorithm().toLowerCase(Locale.ENGLISH).hashCode()); } public boolean equals(Object obj) {