GordianCoreCipherParameters.java
/*
* GordianKnot: Security Suite
* Copyright 2012-2026. Tony Washer
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.github.tonywasher.joceanus.gordianknot.impl.core.cipher;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianCipherFactory;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianCipherParameters;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianCipherParameters.GordianAEADCipherParameters;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianCipherParameters.GordianKeyCipherParameters;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianCipherParameters.GordianNonceParameters;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianCipherParameters.GordianPBECipherParameters;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianCipherSpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianPBESpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianPBESpec.GordianPBEArgon2Spec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianPBESpec.GordianPBEDigestAndCountSpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianPBESpec.GordianPBESCryptSpec;
import io.github.tonywasher.joceanus.gordianknot.api.key.GordianKey;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseData;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianRandomSource;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianDataException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.key.GordianCoreKeyGenerator;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.PBEParametersGenerator;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
import org.bouncycastle.crypto.generators.PKCS12ParametersGenerator;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.generators.SCrypt;
import org.bouncycastle.crypto.params.Argon2Parameters;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.util.Arrays;
/**
* Core Cipher parameters implementation.
*
* @param <T> the key type
*/
public class GordianCoreCipherParameters<T extends GordianKeySpec> {
/**
* The PBESaltLength.
*/
private static final int PBESALTLEN = GordianLength.LEN_256.getByteLength();
/**
* The factory.
*/
private final GordianBaseFactory theFactory;
/**
* The cipherSpec.
*/
private final GordianCipherSpec<T> theSpec;
/**
* The secureRandom.
*/
private final GordianRandomSource theRandom;
/**
* The KeyGenerator.
*/
private GordianCoreKeyGenerator<T> theGenerator;
/**
* Key.
*/
private GordianKey<T> theKey;
/**
* InitialisationVector.
*/
private byte[] theInitVector;
/**
* InitialAEAD.
*/
private byte[] theInitialAEAD;
/**
* pbeSalt.
*/
private byte[] thePBESalt;
/**
* PBESpec.
*/
private GordianPBESpec thePBESpec;
/**
* Constructor.
*
* @param pFactory the factory
* @param pCipherSpec the CipherSpec
*/
GordianCoreCipherParameters(final GordianBaseFactory pFactory,
final GordianCipherSpec<T> pCipherSpec) {
theFactory = pFactory;
theSpec = pCipherSpec;
theRandom = theFactory.getRandomSource();
}
/**
* Obtain the key.
*
* @return the key
*/
public GordianKey<T> getKey() {
return theKey;
}
/**
* Obtain the initVector.
*
* @return the initVector
*/
byte[] getInitVector() {
return theInitVector;
}
/**
* Obtain the initialAEAD.
*
* @return the initialAEAD
*/
byte[] getInitialAEAD() {
return theInitialAEAD;
}
/**
* Obtain the pbeSalt.
*
* @return the pbeSalt
*/
byte[] getPBESalt() {
return thePBESalt;
}
/**
* Obtain the pbeSpec.
*
* @return the pbeSpec
*/
GordianPBESpec getPBESpec() {
return thePBESpec;
}
/**
* Process cipherParameters.
*
* @param pParams the cipher parameters
* @throws GordianException on error
*/
void processParameters(final GordianCipherParameters pParams) throws GordianException {
/* If the cipher parameters are PBE */
if (pParams instanceof GordianPBECipherParameters myPBEParams) {
/* Process separately */
processPBEParameters(myPBEParams);
/* else standard parameters */
} else {
/* Show that we are not PBE */
thePBESpec = null;
thePBESalt = null;
/* Access the key details */
theKey = obtainKeyFromParameters(pParams);
theInitVector = obtainNonceFromParameters(pParams, false);
theInitialAEAD = obtainInitialAEADFromParameters(pParams);
}
}
/**
* Process cipherParameters.
*
* @param pParams the cipher parameters
* @throws GordianException on error
*/
private void processPBEParameters(final GordianPBECipherParameters pParams) throws GordianException {
/* Check that the PBE parameters are supported */
final GordianPBESpec myPBESpec = pParams.getPBESpec();
final GordianPBECipherSpec<T> myPBECipherSpec = new GordianPBECipherSpec<>(myPBESpec, theSpec);
final GordianBaseCipherFactory myCipherFactory = (GordianBaseCipherFactory) theFactory.getCipherFactory();
if (!myCipherFactory.supportedPBECipherSpecs().test(myPBECipherSpec)) {
throw new GordianDataException(GordianBaseData.getInvalidText(myPBECipherSpec));
}
/* Access PBE details */
thePBESpec = myPBESpec;
thePBESalt = obtainNonceFromParameters(pParams, true);
/* Switch on the PBE type */
CipherParameters myParams;
switch (thePBESpec.getPBEType()) {
case PBKDF2:
myParams = derivePBKDF2Parameters(pParams.getPassword());
break;
case PKCS12:
myParams = derivePKCS12Parameters(pParams.getPassword());
break;
case SCRYPT:
myParams = deriveSCRYPTParameters(pParams.getPassword());
break;
case ARGON2:
default:
myParams = deriveArgon2Parameters(pParams.getPassword());
break;
}
/* Store details */
theInitialAEAD = null;
theInitVector = null;
if (myParams instanceof ParametersWithIV myIVParams) {
theInitVector = myIVParams.getIV();
myParams = myIVParams.getParameters();
}
theKey = buildKeyFromBytes(((KeyParameter) myParams).getKey());
}
/**
* Build a key from bytes.
*
* @param pKeyBytes the bytes to use
* @return the key
* @throws GordianException on error
*/
GordianKey<T> buildKeyFromBytes(final byte[] pKeyBytes) throws GordianException {
/* Create generator if needed */
if (theGenerator == null) {
final GordianCipherFactory myFactory = theFactory.getCipherFactory();
theGenerator = (GordianCoreKeyGenerator<T>) myFactory.getKeyGenerator(theSpec.getKeyType());
}
/* Create the key */
return theGenerator.buildKeyFromBytes(pKeyBytes);
}
/**
* Obtain Key from CipherParameters.
*
* @param pParams parameters
* @return the key
*/
@SuppressWarnings("unchecked")
private GordianKey<T> obtainKeyFromParameters(final GordianCipherParameters pParams) {
/* If we have specified IV */
if (pParams instanceof GordianKeyCipherParameters<?> myParams) {
return (GordianKey<T>) myParams.getKey();
}
/* No key */
return null;
}
/**
* Obtain Nonce from CipherParameters.
*
* @param pParams parameters
* @param pPBESalt is this a PBESalt
* @return the nonce
*/
private byte[] obtainNonceFromParameters(final GordianCipherParameters pParams,
final boolean pPBESalt) {
/* Default IV is null */
byte[] myIV = null;
/* If we have specified IV */
if (pParams instanceof GordianNonceParameters myParams) {
/* If we have an explicit Nonce */
if (!myParams.randomNonce()) {
/* access the nonce */
myIV = Arrays.clone(myParams.getNonce());
/* Else if we actually need a nonce */
} else if (pPBESalt || theSpec.needsIV()) {
/* Create a random IV */
final int myLen = pPBESalt ? PBESALTLEN : theSpec.getIVLength();
myIV = new byte[myLen];
theRandom.getRandom().nextBytes(myIV);
}
}
/* return the IV */
return myIV;
}
/**
* Obtain initialAEAD from CipherParameters.
*
* @param pParams parameters
* @return the initialAEAD
*/
private static byte[] obtainInitialAEADFromParameters(final GordianCipherParameters pParams) {
/* Default initialAEAD is null */
byte[] myInitial = null;
/* If we have specified IV */
if (pParams instanceof GordianAEADCipherParameters<?> myParams) {
myInitial = Arrays.clone(myParams.getInitialAEAD());
}
/* return initialAEAD */
return myInitial;
}
/**
* derive PBKDF2 key and IV.
*
* @param pPassword the password
* @return the parameters
*/
private CipherParameters derivePBKDF2Parameters(final char[] pPassword) {
/* Protect password bytes */
byte[] myPassword = null;
try {
/* Create the digest */
final GordianPBEDigestAndCountSpec mySpec = (GordianPBEDigestAndCountSpec) thePBESpec;
final Digest myDigest = new SHA512Digest();
/* Create the generator and initialise it */
final PKCS5S2ParametersGenerator myGenerator = new PKCS5S2ParametersGenerator(myDigest);
myPassword = PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(pPassword);
myGenerator.init(myPassword, thePBESalt, mySpec.getIterationCount());
/* Generate the parameters */
final int myKeyLen = theSpec.getKeyType().getKeyLength().getLength();
final int myIVLen = theSpec.needsIV() ? Byte.SIZE * theSpec.getIVLength() : 0;
return myIVLen == 0
? myGenerator.generateDerivedParameters(myKeyLen)
: myGenerator.generateDerivedParameters(myKeyLen, myIVLen);
} finally {
if (myPassword != null) {
Arrays.fill(myPassword, (byte) 0);
}
}
}
/**
* derive PKCS12 key and IV.
*
* @param pPassword the password
* @return the parameters
*/
private CipherParameters derivePKCS12Parameters(final char[] pPassword) {
/* Protect password bytes */
byte[] myPassword = null;
try {
/* Create the digest */
final GordianPBEDigestAndCountSpec mySpec = (GordianPBEDigestAndCountSpec) thePBESpec;
final Digest myDigest = new SHA512Digest();
/* Create the generator and initialise it */
final PKCS12ParametersGenerator myGenerator = new PKCS12ParametersGenerator(myDigest);
myPassword = PBEParametersGenerator.PKCS12PasswordToBytes(pPassword);
myGenerator.init(myPassword, thePBESalt, mySpec.getIterationCount());
/* Generate the parameters */
final int myKeyLen = theSpec.getKeyType().getKeyLength().getLength();
final int myIVLen = theSpec.needsIV() ? Byte.SIZE * theSpec.getIVLength() : 0;
return myIVLen == 0
? myGenerator.generateDerivedParameters(myKeyLen)
: myGenerator.generateDerivedParameters(myKeyLen, myIVLen);
} finally {
if (myPassword != null) {
Arrays.fill(myPassword, (byte) 0);
}
}
}
/**
* derive SCRYPT key and IV.
*
* @param pPassword the password
* @return the parameters
*/
private CipherParameters deriveSCRYPTParameters(final char[] pPassword) {
/* Protect password bytes */
byte[] myPassword = null;
try {
/* Access the password as bytes */
final GordianPBESCryptSpec mySpec = (GordianPBESCryptSpec) thePBESpec;
myPassword = PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(pPassword);
/* Generate the bytes */
final int myKeyLen = theSpec.getKeyType().getKeyLength().getByteLength();
final int myIVLen = theSpec.needsIV() ? theSpec.getIVLength() : 0;
final int myBufLen = myIVLen + myKeyLen;
final byte[] myBuffer = SCrypt.generate(myPassword, thePBESalt, mySpec.getCost(),
mySpec.getBlockSize(), mySpec.getParallel(), myBufLen);
/* Convert to parameters */
final KeyParameter myKeyParm = new KeyParameter(myBuffer, 0, myKeyLen);
return myIVLen == 0
? myKeyParm
: new ParametersWithIV(myKeyParm, myBuffer, myKeyLen, myIVLen);
} finally {
if (myPassword != null) {
Arrays.fill(myPassword, (byte) 0);
}
}
}
/**
* derive Argon2 key and IV.
*
* @param pPassword the password
* @return the parameters
*/
private CipherParameters deriveArgon2Parameters(final char[] pPassword) {
/* Create the parameters */
final GordianPBEArgon2Spec mySpec = (GordianPBEArgon2Spec) thePBESpec;
final Argon2Parameters.Builder myBuilder = new Argon2Parameters.Builder();
myBuilder.withSalt(thePBESalt);
myBuilder.withIterations(mySpec.getIterationCount());
myBuilder.withParallelism(mySpec.getLanes());
myBuilder.withMemoryAsKB(mySpec.getMemory());
final Argon2Parameters myParams = myBuilder.build();
/* Generate the bytes */
final Argon2BytesGenerator myGenerator = new Argon2BytesGenerator();
myGenerator.init(myParams);
final int myKeyLen = theSpec.getKeyType().getKeyLength().getByteLength();
final int myIVLen = theSpec.needsIV() ? theSpec.getIVLength() : 0;
final int myBufLen = myIVLen + myKeyLen;
final byte[] myBuffer = new byte[myBufLen];
myGenerator.generateBytes(pPassword, myBuffer);
/* Convert to parameters */
final KeyParameter myKeyParm = new KeyParameter(myBuffer, 0, myKeyLen);
return myIVLen == 0
? myKeyParm
: new ParametersWithIV(myKeyParm, myBuffer, myKeyLen, myIVLen);
}
}