8301553: Support Password-Based Cryptography in SunPKCS11

Co-authored-by: Francisco Ferrari Bihurriet <fferrari@redhat.com>
Co-authored-by: Martin Balao <mbalao@openjdk.org>
Reviewed-by: valeriep
This commit is contained in:
Martin Balao 2023-06-06 19:39:34 +00:00
parent 0a4f9ad637
commit 4a75fd462c
30 changed files with 3013 additions and 452 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2023, 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
@ -25,13 +25,14 @@
package com.sun.crypto.provider;
import java.util.Arrays;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import java.security.*;
import java.security.spec.*;
import java.util.Arrays;
import jdk.internal.access.SharedSecrets;
import sun.security.util.PBEUtil;
/**
* This is an implementation of the HMAC algorithms as defined
@ -108,81 +109,30 @@ abstract class HmacPKCS12PBECore extends HmacCore {
*/
protected void engineInit(Key key, AlgorithmParameterSpec params)
throws InvalidKeyException, InvalidAlgorithmParameterException {
char[] passwdChars;
byte[] salt = null;
int iCount = 0;
if (key instanceof javax.crypto.interfaces.PBEKey) {
javax.crypto.interfaces.PBEKey pbeKey =
(javax.crypto.interfaces.PBEKey) key;
passwdChars = pbeKey.getPassword();
salt = pbeKey.getSalt(); // maybe null if unspecified
iCount = pbeKey.getIterationCount(); // maybe 0 if unspecified
} else if (key instanceof SecretKey) {
byte[] passwdBytes;
if (!(key.getAlgorithm().regionMatches(true, 0, "PBE", 0, 3)) ||
(passwdBytes = key.getEncoded()) == null) {
throw new InvalidKeyException("Missing password");
}
passwdChars = new char[passwdBytes.length];
for (int i=0; i<passwdChars.length; i++) {
passwdChars[i] = (char) (passwdBytes[i] & 0x7f);
}
Arrays.fill(passwdBytes, (byte)0x00);
} else {
throw new InvalidKeyException("SecretKey of PBE type required");
}
byte[] derivedKey;
char[] password = null;
byte[] derivedKey = null;
SecretKeySpec cipherKey = null;
PBEKeySpec keySpec = PBEUtil.getPBAKeySpec(key, params);
try {
if (params == null) {
// should not auto-generate default values since current
// javax.crypto.Mac api does not have any method for caller to
// retrieve the generated defaults.
if ((salt == null) || (iCount == 0)) {
throw new InvalidAlgorithmParameterException
("PBEParameterSpec required for salt and iteration count");
}
} else if (!(params instanceof PBEParameterSpec)) {
throw new InvalidAlgorithmParameterException
("PBEParameterSpec type required");
} else {
PBEParameterSpec pbeParams = (PBEParameterSpec) params;
// make sure the parameter values are consistent
if (salt != null) {
if (!Arrays.equals(salt, pbeParams.getSalt())) {
throw new InvalidAlgorithmParameterException
("Inconsistent value of salt between key and params");
}
} else {
salt = pbeParams.getSalt();
}
if (iCount != 0) {
if (iCount != pbeParams.getIterationCount()) {
throw new InvalidAlgorithmParameterException
("Different iteration count between key and params");
}
} else {
iCount = pbeParams.getIterationCount();
}
}
// For security purpose, we need to enforce a minimum length
// for salt; just require the minimum salt length to be 8-byte
// which is what PKCS#5 recommends and openssl does.
if (salt.length < 8) {
throw new InvalidAlgorithmParameterException
("Salt must be at least 8 bytes long");
}
if (iCount <= 0) {
throw new InvalidAlgorithmParameterException
("IterationCount must be a positive number");
}
derivedKey = PKCS12PBECipherCore.derive(passwdChars, salt,
iCount, engineGetMacLength(), PKCS12PBECipherCore.MAC_KEY,
algorithm, bl);
password = keySpec.getPassword();
derivedKey = PKCS12PBECipherCore.derive(
password, keySpec.getSalt(),
keySpec.getIterationCount(), engineGetMacLength(),
PKCS12PBECipherCore.MAC_KEY, algorithm, bl);
cipherKey = new SecretKeySpec(derivedKey, "HmacSHA1");
super.engineInit(cipherKey, null);
} finally {
Arrays.fill(passwdChars, '\0');
if (cipherKey != null) {
SharedSecrets.getJavaxCryptoSpecAccess()
.clearSecretKeySpec(cipherKey);
}
if (derivedKey != null) {
Arrays.fill(derivedKey, (byte) 0);
}
if (password != null) {
Arrays.fill(password, '\0');
}
keySpec.clearPassword();
}
SecretKey cipherKey = new SecretKeySpec(derivedKey, "HmacSHA1");
super.engineInit(cipherKey, null);
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2023, 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
@ -31,6 +31,9 @@ import java.util.Arrays;
import javax.crypto.*;
import javax.crypto.spec.*;
import jdk.internal.access.SharedSecrets;
import sun.security.util.PBEUtil;
/**
* This class represents password-based encryption as defined by the PKCS #5
* standard.
@ -43,10 +46,6 @@ import javax.crypto.spec.*;
* @see javax.crypto.Cipher
*/
abstract class PBES2Core extends CipherSpi {
private static final int DEFAULT_SALT_LENGTH = 20;
private static final int DEFAULT_COUNT = 4096;
// the encapsulated cipher
private final CipherCore cipher;
private final int keyLength; // in bits
@ -54,9 +53,7 @@ abstract class PBES2Core extends CipherSpi {
private final PBKDF2Core kdf;
private final String pbeAlgo;
private final String cipherAlgo;
private int iCount = DEFAULT_COUNT;
private byte[] salt = null;
private IvParameterSpec ivSpec = null;
private final PBEUtil.PBES2Params pbes2Params = new PBEUtil.PBES2Params();
/**
* Creates an instance of PBE Scheme 2 according to the selected
@ -135,32 +132,8 @@ abstract class PBES2Core extends CipherSpi {
}
protected AlgorithmParameters engineGetParameters() {
AlgorithmParameters params = null;
if (salt == null) {
// generate random salt and use default iteration count
salt = new byte[DEFAULT_SALT_LENGTH];
SunJCE.getRandom().nextBytes(salt);
iCount = DEFAULT_COUNT;
}
if (ivSpec == null) {
// generate random IV
byte[] ivBytes = new byte[blkSize];
SunJCE.getRandom().nextBytes(ivBytes);
ivSpec = new IvParameterSpec(ivBytes);
}
PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, iCount, ivSpec);
try {
params = AlgorithmParameters.getInstance(pbeAlgo,
SunJCE.getInstance());
params.init(pbeSpec);
} catch (NoSuchAlgorithmException nsae) {
// should never happen
throw new RuntimeException("SunJCE called, but not configured");
} catch (InvalidParameterSpecException ipse) {
// should never happen
throw new RuntimeException("PBEParameterSpec not supported");
}
return params;
return pbes2Params.getAlgorithmParameters(
blkSize, pbeAlgo, SunJCE.getInstance(), SunJCE.getRandom());
}
protected void engineInit(int opmode, Key key, SecureRandom random)
@ -172,132 +145,46 @@ abstract class PBES2Core extends CipherSpi {
}
}
private static byte[] check(byte[] salt)
throws InvalidAlgorithmParameterException {
if (salt != null && salt.length < 8) {
throw new InvalidAlgorithmParameterException(
"Salt must be at least 8 bytes long");
}
return salt;
}
private static int check(int iCount)
throws InvalidAlgorithmParameterException {
if (iCount < 0) {
throw new InvalidAlgorithmParameterException(
"Iteration count must be a positive number");
}
return iCount == 0 ? DEFAULT_COUNT : iCount;
}
protected void engineInit(int opmode, Key key,
AlgorithmParameterSpec params,
SecureRandom random)
throws InvalidKeyException, InvalidAlgorithmParameterException {
if (key == null) {
throw new InvalidKeyException("Null key");
}
byte[] passwdBytes = key.getEncoded();
char[] passwdChars = null;
salt = null;
iCount = 0;
ivSpec = null;
PBEKeySpec pbeSpec;
try {
if ((passwdBytes == null) ||
!(key.getAlgorithm().regionMatches(true, 0, "PBE", 0, 3))) {
throw new InvalidKeyException("Missing password");
}
boolean doEncrypt = ((opmode == Cipher.ENCRYPT_MODE) ||
(opmode == Cipher.WRAP_MODE));
// Extract from the supplied PBE params, if present
if (params instanceof PBEParameterSpec pbeParams) {
// salt should be non-null per PBEParameterSpec
salt = check(pbeParams.getSalt());
iCount = check(pbeParams.getIterationCount());
AlgorithmParameterSpec ivParams = pbeParams.getParameterSpec();
if (ivParams instanceof IvParameterSpec iv) {
ivSpec = iv;
} else if (ivParams == null && doEncrypt) {
// generate random IV
byte[] ivBytes = new byte[blkSize];
random.nextBytes(ivBytes);
ivSpec = new IvParameterSpec(ivBytes);
} else {
throw new InvalidAlgorithmParameterException(
"Wrong parameter type: IV expected");
}
} else if (params == null && doEncrypt) {
// Try extracting from the key if present. If unspecified,
// PBEKey returns null and 0 respectively.
if (key instanceof javax.crypto.interfaces.PBEKey pbeKey) {
salt = check(pbeKey.getSalt());
iCount = check(pbeKey.getIterationCount());
}
if (salt == null) {
// generate random salt
salt = new byte[DEFAULT_SALT_LENGTH];
random.nextBytes(salt);
}
if (iCount == 0) {
// use default iteration count
iCount = DEFAULT_COUNT;
}
// generate random IV
byte[] ivBytes = new byte[blkSize];
random.nextBytes(ivBytes);
ivSpec = new IvParameterSpec(ivBytes);
} else {
throw new InvalidAlgorithmParameterException
("Wrong parameter type: PBE expected");
}
passwdChars = new char[passwdBytes.length];
for (int i = 0; i < passwdChars.length; i++)
passwdChars[i] = (char) (passwdBytes[i] & 0x7f);
pbeSpec = new PBEKeySpec(passwdChars, salt, iCount, keyLength);
} finally {
// password char[] was cloned in PBEKeySpec constructor,
// so we can zero it out here
if (passwdChars != null) Arrays.fill(passwdChars, '\0');
if (passwdBytes != null) Arrays.fill(passwdBytes, (byte)0x00);
}
PBKDF2KeyImpl s;
PBEKeySpec pbeSpec = pbes2Params.getPBEKeySpec(blkSize, keyLength,
opmode, key, params, random);
PBKDF2KeyImpl s = null;
byte[] derivedKey;
try {
s = (PBKDF2KeyImpl)kdf.engineGenerateSecret(pbeSpec);
derivedKey = s.getEncoded();
} catch (InvalidKeySpecException ikse) {
throw new InvalidKeyException("Cannot construct PBE key", ikse);
} finally {
if (s != null) {
s.clear();
}
pbeSpec.clearPassword();
}
byte[] derivedKey = s.getEncoded();
s.clearPassword();
SecretKeySpec cipherKey = new SecretKeySpec(derivedKey, cipherAlgo);
// initialize the underlying cipher
cipher.init(opmode, cipherKey, ivSpec, random);
SecretKeySpec cipherKey = null;
try {
cipherKey = new SecretKeySpec(derivedKey, cipherAlgo);
// initialize the underlying cipher
cipher.init(opmode, cipherKey, pbes2Params.getIvSpec(), random);
} finally {
if (cipherKey != null) {
SharedSecrets.getJavaxCryptoSpecAccess()
.clearSecretKeySpec(cipherKey);
}
Arrays.fill(derivedKey, (byte) 0);
}
}
protected void engineInit(int opmode, Key key, AlgorithmParameters params,
SecureRandom random)
throws InvalidKeyException, InvalidAlgorithmParameterException {
AlgorithmParameterSpec pbeSpec = null;
if (params != null) {
try {
pbeSpec = params.getParameterSpec(PBEParameterSpec.class);
} catch (InvalidParameterSpecException ipse) {
throw new InvalidAlgorithmParameterException(
"Wrong parameter type: PBE expected");
}
}
engineInit(opmode, key, pbeSpec, random);
engineInit(opmode, key, PBEUtil.PBES2Params.getParameterSpec(params),
random);
}
protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2023, 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
@ -26,7 +26,7 @@
package com.sun.crypto.provider;
import java.io.ObjectStreamException;
import java.lang.ref.Reference;
import java.lang.ref.Cleaner;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.Arrays;
@ -64,9 +64,9 @@ final class PBKDF2KeyImpl implements javax.crypto.interfaces.PBEKey {
private int iterCount;
private byte[] key;
@SuppressWarnings("serial") // Type of field is not Serializable;
// see writeReplace method
private Mac prf;
// The following fields are not Serializable. See writeReplace method.
private transient Mac prf;
private transient Cleaner.Cleanable cleaner;
private static byte[] getPasswordBytes(char[] passwd) {
CharBuffer cb = CharBuffer.wrap(passwd);
@ -88,17 +88,9 @@ final class PBKDF2KeyImpl implements javax.crypto.interfaces.PBEKey {
*/
PBKDF2KeyImpl(PBEKeySpec keySpec, String prfAlgo)
throws InvalidKeySpecException {
char[] passwd = keySpec.getPassword();
if (passwd == null) {
// Should allow an empty password.
this.passwd = new char[0];
} else {
this.passwd = passwd.clone();
}
this.passwd = keySpec.getPassword();
// Convert the password from char[] to byte[]
byte[] passwdBytes = getPasswordBytes(this.passwd);
// remove local copy
if (passwd != null) Arrays.fill(passwd, '\0');
try {
this.salt = keySpec.getSalt();
@ -124,16 +116,18 @@ final class PBKDF2KeyImpl implements javax.crypto.interfaces.PBEKey {
throw new InvalidKeySpecException(nsae);
} finally {
Arrays.fill(passwdBytes, (byte) 0x00);
// Use the cleaner to zero the key when no longer referenced
final byte[] k = this.key;
final char[] p = this.passwd;
CleanerFactory.cleaner().register(this,
() -> {
Arrays.fill(k, (byte) 0x00);
Arrays.fill(p, '\0');
});
if (key == null) {
Arrays.fill(passwd, '\0');
}
}
// Use the cleaner to zero the key when no longer referenced
final byte[] k = this.key;
final char[] p = this.passwd;
cleaner = CleanerFactory.cleaner().register(this,
() -> {
Arrays.fill(k, (byte) 0x00);
Arrays.fill(p, '\0');
});
}
private static byte[] deriveKey(final Mac prf, final byte[] password,
@ -211,11 +205,7 @@ final class PBKDF2KeyImpl implements javax.crypto.interfaces.PBEKey {
}
public byte[] getEncoded() {
// The key is zeroized by finalize()
// The reachability fence ensures finalize() isn't called early
byte[] result = key.clone();
Reference.reachabilityFence(this);
return result;
return key.clone();
}
public String getAlgorithm() {
@ -226,16 +216,12 @@ final class PBKDF2KeyImpl implements javax.crypto.interfaces.PBEKey {
return iterCount;
}
public void clearPassword() {
Arrays.fill(passwd, (char)0);
public void clear() {
cleaner.clean();
}
public char[] getPassword() {
// The password is zeroized by finalize()
// The reachability fence ensures finalize() isn't called early
char[] result = passwd.clone();
Reference.reachabilityFence(this);
return result;
return passwd.clone();
}
public byte[] getSalt() {

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2023, 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
@ -25,6 +25,8 @@
package com.sun.crypto.provider;
import jdk.internal.access.SharedSecrets;
import java.util.Arrays;
import javax.crypto.SecretKey;
@ -179,23 +181,29 @@ abstract class PBMAC1Core extends HmacCore {
}
PBKDF2KeyImpl s = null;
PBKDF2Core kdf = getKDFImpl(kdfAlgo);
byte[] derivedKey;
byte[] derivedKey = null;
SecretKeySpec cipherKey = null;
try {
PBKDF2Core kdf = getKDFImpl(kdfAlgo);
s = (PBKDF2KeyImpl)kdf.engineGenerateSecret(pbeSpec);
derivedKey = s.getEncoded();
cipherKey = new SecretKeySpec(derivedKey, kdfAlgo);
super.engineInit(cipherKey, null);
} catch (InvalidKeySpecException ikse) {
throw new InvalidKeyException("Cannot construct PBE key", ikse);
} finally {
pbeSpec.clearPassword();
if (s != null) {
s.clearPassword();
if (cipherKey != null) {
SharedSecrets.getJavaxCryptoSpecAccess()
.clearSecretKeySpec(cipherKey);
}
if (derivedKey != null) {
Arrays.fill(derivedKey, (byte) 0);
}
if (s != null) {
s.clear();
}
pbeSpec.clearPassword();
}
SecretKey cipherKey = new SecretKeySpec(derivedKey, kdfAlgo);
Arrays.fill(derivedKey, (byte)0);
super.engineInit(cipherKey, null);
}
public static final class HmacSHA1 extends PBMAC1Core {

View file

@ -0,0 +1,355 @@
/*
* Copyright (c) 2023, Red Hat, Inc.
* 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 sun.security.util;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
public final class PBEUtil {
/*
* PBES2Params is an auxiliary class that represents the state needed for
* PBES2 operations (iterations count, salt and IV) and its (re)
* initialization logic. Users of this class are CipherSpi implementors that
* support PBES2 cryptography (RFC #8018), such as PBES2Core (SunJCE) and
* P11PBECipher (SunPKCS11).
*
* CipherSpi implementors must call ::getPBEKeySpec in every engine
* initialization (CipherSpi::engineInit override) to reset the state and
* get new values in a PBEKeySpec instance. These new values are taken
* from parameters, defaults or generated randomly.
*
* After engine initialization, values in effect can be extracted with
* ::getAlgorithmParameters (as AlgorithmParameters) or ::getIvSpec (as
* IvParameterSpec).
*/
public static final class PBES2Params {
private static final int DEFAULT_SALT_LENGTH = 20;
private static final int DEFAULT_ITERATIONS = 4096;
private int iCount;
private byte[] salt;
private IvParameterSpec ivSpec;
/*
* Initialize a PBES2Params instance. May generate random salt and
* IV if not passed and the operation is encryption. If initialization
* fails, values are reset. Used by PBES2Params and P11PBECipher
* (SunPKCS11).
*/
public void initialize(int blkSize, int opmode, int iCount, byte[] salt,
AlgorithmParameterSpec ivSpec, SecureRandom random)
throws InvalidAlgorithmParameterException {
try {
boolean doEncrypt = opmode == Cipher.ENCRYPT_MODE ||
opmode == Cipher.WRAP_MODE;
if (ivSpec instanceof IvParameterSpec iv) {
this.ivSpec = iv;
} else if (ivSpec == null && doEncrypt) {
byte[] ivBytes = new byte[blkSize];
random.nextBytes(ivBytes);
this.ivSpec = new IvParameterSpec(ivBytes);
} else {
throw new InvalidAlgorithmParameterException("Wrong " +
"parameter type: IvParameterSpec " +
(doEncrypt ? "or null " : "") + "expected");
}
this.iCount = iCount == 0 ? DEFAULT_ITERATIONS : iCount;
if (salt == null) {
if (doEncrypt) {
salt = new byte[DEFAULT_SALT_LENGTH];
random.nextBytes(salt);
} else {
throw new InvalidAlgorithmParameterException("Salt " +
"needed for decryption");
}
}
this.salt = salt;
} catch (InvalidAlgorithmParameterException e) {
this.ivSpec = null;
this.iCount = 0;
this.salt = null;
throw e;
}
}
/*
* Obtain an IvParameterSpec for Cipher services. This method returns
* null when the state is not initialized. Used by PBES2Core (SunJCE)
* and P11PBECipher (SunPKCS11).
*/
public IvParameterSpec getIvSpec() {
return ivSpec;
}
/*
* Obtain AlgorithmParameters for Cipher services. This method will
* initialize PBES2Params if needed, generating new values randomly or
* assigning from defaults. If PBES2Params is initialized, existing
* values will be returned. Used by PBES2Core (SunJCE) and
* P11PBECipher (SunPKCS11).
*/
public AlgorithmParameters getAlgorithmParameters(int blkSize,
String pbeAlgo, Provider algParamsProv, SecureRandom random) {
AlgorithmParameters params;
try {
if (iCount == 0 && salt == null && ivSpec == null) {
initialize(blkSize, Cipher.ENCRYPT_MODE, 0, null, null,
random);
}
params = AlgorithmParameters.getInstance(pbeAlgo,
algParamsProv);
params.init(new PBEParameterSpec(salt, iCount, ivSpec));
} catch (NoSuchAlgorithmException nsae) {
// should never happen
throw new RuntimeException("AlgorithmParameters for "
+ pbeAlgo + " not configured");
} catch (InvalidParameterSpecException ipse) {
// should never happen
throw new RuntimeException("PBEParameterSpec not supported");
} catch (InvalidAlgorithmParameterException iape) {
// should never happen
throw new RuntimeException("Error initializing PBES2Params");
}
return params;
}
/*
* Initialize PBES2Params and obtain a PBEKeySpec for Cipher services.
* Data from the key, parameters, defaults or random may be used for
* initialization. Used by PBES2Core (SunJCE) and P11PBECipher
* (SunPKCS11).
*/
public PBEKeySpec getPBEKeySpec(int blkSize, int keyLength, int opmode,
Key key, AlgorithmParameterSpec params, SecureRandom random)
throws InvalidKeyException, InvalidAlgorithmParameterException {
if (key == null) {
throw new InvalidKeyException("Null key");
}
byte[] passwdBytes;
char[] passwdChars = null;
if (!(key.getAlgorithm().regionMatches(true, 0, "PBE", 0, 3)) ||
(passwdBytes = key.getEncoded()) == null) {
throw new InvalidKeyException("Missing password");
}
try {
int iCountInit;
byte[] saltInit;
AlgorithmParameterSpec ivSpecInit;
// Extract from the supplied PBE params, if present
if (params instanceof PBEParameterSpec pbeParams) {
// salt should be non-null per PBEParameterSpec
iCountInit = check(pbeParams.getIterationCount());
saltInit = check(pbeParams.getSalt());
ivSpecInit = pbeParams.getParameterSpec();
} else if (params == null) {
// Try extracting from the key if present. If unspecified,
// PBEKey returns 0 and null respectively.
if (key instanceof javax.crypto.interfaces.PBEKey pbeKey) {
iCountInit = check(pbeKey.getIterationCount());
saltInit = check(pbeKey.getSalt());
} else {
iCountInit = 0;
saltInit = null;
}
ivSpecInit = null;
} else {
throw new InvalidAlgorithmParameterException(
"Wrong parameter type: PBE expected");
}
initialize(blkSize, opmode, iCountInit, saltInit, ivSpecInit,
random);
passwdChars = new char[passwdBytes.length];
for (int i = 0; i < passwdChars.length; i++) {
passwdChars[i] = (char) (passwdBytes[i] & 0x7f);
}
return new PBEKeySpec(passwdChars, salt, iCount, keyLength);
} finally {
// password char[] was cloned in PBEKeySpec constructor,
// so we can zero it out here
if (passwdChars != null) Arrays.fill(passwdChars, '\0');
if (passwdBytes != null) Arrays.fill(passwdBytes, (byte)0x00);
}
}
/*
* Obtain an AlgorithmParameterSpec from an AlgorithmParameters
* instance, for Cipher services. Used by PBES2Core (SunJCE) and
* P11PBECipher (SunPKCS11).
*/
public static AlgorithmParameterSpec getParameterSpec(
AlgorithmParameters params)
throws InvalidAlgorithmParameterException {
AlgorithmParameterSpec pbeSpec = null;
if (params != null) {
try {
pbeSpec = params.getParameterSpec(PBEParameterSpec.class);
} catch (InvalidParameterSpecException ipse) {
throw new InvalidAlgorithmParameterException(
"Wrong parameter type: PBE expected");
}
}
return pbeSpec;
}
private static byte[] check(byte[] salt)
throws InvalidAlgorithmParameterException {
if (salt != null && salt.length < 8) {
throw new InvalidAlgorithmParameterException(
"Salt must be at least 8 bytes long");
}
return salt;
}
private static int check(int iCount)
throws InvalidAlgorithmParameterException {
if (iCount < 0) {
throw new InvalidAlgorithmParameterException(
"Iteration count must be a positive number");
}
return iCount;
}
}
/*
* Obtain a PBEKeySpec for Mac services, after key and parameters
* validation. Used by HmacPKCS12PBECore (SunJCE) and P11Mac (SunPKCS11).
*/
public static PBEKeySpec getPBAKeySpec(Key key,
AlgorithmParameterSpec params)
throws InvalidKeyException, InvalidAlgorithmParameterException {
char[] passwdChars;
byte[] salt = null;
int iCount = 0;
if (key instanceof javax.crypto.interfaces.PBEKey pbeKey) {
passwdChars = pbeKey.getPassword();
salt = pbeKey.getSalt(); // maybe null if unspecified
iCount = pbeKey.getIterationCount(); // maybe 0 if unspecified
} else if (key instanceof SecretKey) {
byte[] passwdBytes;
if (!(key.getAlgorithm().regionMatches(true, 0, "PBE", 0, 3)) ||
(passwdBytes = key.getEncoded()) == null) {
throw new InvalidKeyException("Missing password");
}
passwdChars = new char[passwdBytes.length];
for (int i = 0; i < passwdChars.length; i++) {
passwdChars[i] = (char) (passwdBytes[i] & 0x7f);
}
Arrays.fill(passwdBytes, (byte)0x00);
} else {
throw new InvalidKeyException("SecretKey of PBE type required");
}
try {
if (params == null) {
// should not auto-generate default values since current
// javax.crypto.Mac api does not have any method for caller to
// retrieve the generated defaults.
if ((salt == null) || (iCount == 0)) {
throw new InvalidAlgorithmParameterException(
"PBEParameterSpec required for salt " +
"and iteration count");
}
} else if (!(params instanceof PBEParameterSpec)) {
throw new InvalidAlgorithmParameterException(
"PBEParameterSpec type required");
} else {
PBEParameterSpec pbeParams = (PBEParameterSpec) params;
// make sure the parameter values are consistent
if (salt != null) {
if (!Arrays.equals(salt, pbeParams.getSalt())) {
throw new InvalidAlgorithmParameterException(
"Inconsistent value of salt " +
"between key and params");
}
} else {
salt = pbeParams.getSalt();
}
if (iCount != 0) {
if (iCount != pbeParams.getIterationCount()) {
throw new InvalidAlgorithmParameterException(
"Different iteration count " +
"between key and params");
}
} else {
iCount = pbeParams.getIterationCount();
}
}
// For security purpose, we need to enforce a minimum length
// for salt; just require the minimum salt length to be 8-byte
// which is what PKCS#5 recommends and openssl does.
if (salt.length < 8) {
throw new InvalidAlgorithmParameterException(
"Salt must be at least 8 bytes long");
}
if (iCount <= 0) {
throw new InvalidAlgorithmParameterException(
"IterationCount must be a positive number");
}
return new PBEKeySpec(passwdChars, salt, iCount);
} finally {
Arrays.fill(passwdChars, '\0');
}
}
/*
* Check that the key implements the PBEKey interface. If params is an
* instance of PBEParameterSpec, validate consistency with the key's
* derivation data. Used by P11Mac and P11PBECipher (SunPKCS11).
*/
public static void checkKeyAndParams(Key key,
AlgorithmParameterSpec params, String algorithm)
throws InvalidKeyException, InvalidAlgorithmParameterException {
if (key instanceof javax.crypto.interfaces.PBEKey pbeKey) {
if (params instanceof PBEParameterSpec pbeParams) {
if (pbeParams.getIterationCount() !=
pbeKey.getIterationCount() ||
!Arrays.equals(pbeParams.getSalt(), pbeKey.getSalt())) {
throw new InvalidAlgorithmParameterException(
"Salt or iteration count parameters are " +
"not consistent with PBE key");
}
}
} else {
throw new InvalidKeyException(
"Cannot use a " + algorithm + " service with a key that " +
"does not implement javax.crypto.interfaces.PBEKey");
}
}
}