GordianCoreRandomFactory.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.random;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
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.GordianPadding;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymCipher;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymCipherSpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymCipherSpecBuilder;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymKeyType;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigest;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestFactory;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestSpec;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestType;
import io.github.tonywasher.joceanus.gordianknot.api.key.GordianKey;
import io.github.tonywasher.joceanus.gordianknot.api.key.GordianKeyGenerator;
import io.github.tonywasher.joceanus.gordianknot.api.key.GordianKeyLengths;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMac;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacFactory;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacParameters;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacSpec;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacSpecBuilder;
import io.github.tonywasher.joceanus.gordianknot.api.random.GordianRandomFactory;
import io.github.tonywasher.joceanus.gordianknot.api.random.GordianRandomSpec;
import io.github.tonywasher.joceanus.gordianknot.api.random.GordianRandomSpecBuilder;
import io.github.tonywasher.joceanus.gordianknot.api.random.GordianRandomType;
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.GordianIdManager;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianRandomSource;
import io.github.tonywasher.joceanus.gordianknot.impl.core.cipher.GordianCoreCipher;
import io.github.tonywasher.joceanus.gordianknot.impl.core.cipher.GordianCoreCipherFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.digest.GordianCoreDigestFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianDataException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.mac.GordianCoreMacFactory;
import org.bouncycastle.crypto.prng.BasicEntropySourceProvider;
import org.bouncycastle.crypto.prng.EntropySource;
import org.bouncycastle.crypto.prng.EntropySourceProvider;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* GordianKnot Core RandomFactory.
*/
public class GordianCoreRandomFactory
implements GordianRandomFactory {
/**
* The SP800 prefix.
*/
static final String SP800_PREFIX = "SP800-";
/**
* The bit shift.
*/
static final int BIT_SHIFT = 3;
/**
* The number of entropy bits required.
*/
private static final int NUM_ENTROPY_BITS_REQUIRED = GordianSP800HashDRBG.LONG_SEED_LENGTH;
/**
* The number of entropy bits required.
*/
private static final int NUM_ENTROPY_BYTES_REQUIRED = NUM_ENTROPY_BITS_REQUIRED / Byte.SIZE;
/**
* The power of 2 for RESEED calculation.
*/
private static final int RESEED_POWER = 48;
/**
* The length of time before a reSeed is required.
*/
static final long RESEED_MAX = 1L << (RESEED_POWER - 1);
/**
* The power of 2 for BITS calculation.
*/
private static final int BITS_POWER = 19;
/**
* The maximum # of bits that can be requested.
*/
static final int MAX_BITS_REQUEST = 1 << (BITS_POWER - 1);
/**
* The factory.
*/
private final GordianBaseFactory theFactory;
/**
* The Secure Random source.
*/
private final GordianRandomSource theRandomSource;
/**
* The Basic Secure Random instance.
*/
private final SecureRandom theRandom;
/**
* The Entropy Source Provider.
*/
private final EntropySourceProvider theEntropyProvider;
/**
* Construct a builder with an EntropySourceProvider based on the initial random.
*
* @param pFactory the factory
* @throws GordianException on error
*/
public GordianCoreRandomFactory(final GordianBaseFactory pFactory) throws GordianException {
/* Access the initial stringRandom */
theFactory = pFactory;
theRandomSource = theFactory.getRandomSource();
theRandom = theRandomSource.getRandom();
/* Store parameters and create an entropy provider */
theEntropyProvider = new BasicEntropySourceProvider(theRandom, true);
/* Create a random combinedRandom and register it */
theRandomSource.setRandom(generateRandomCombined());
}
@Override
public GordianSecureRandom createRandom(final GordianRandomSpec pRandomSpec) throws GordianException {
/* Check validity of RandomSpec */
if (!supportedRandomSpecs().test(pRandomSpec)) {
throw new GordianDataException(GordianBaseData.getInvalidText(pRandomSpec));
}
/* Access factories */
final GordianCoreDigestFactory myDigests = (GordianCoreDigestFactory) theFactory.getDigestFactory();
final GordianCoreCipherFactory myCiphers = (GordianCoreCipherFactory) theFactory.getCipherFactory();
final GordianCoreMacFactory myMacs = (GordianCoreMacFactory) theFactory.getMacFactory();
/* Access the digestSpec */
final GordianDigestSpec myDigest = pRandomSpec.getDigestSpec();
final boolean isResistent = pRandomSpec.isPredictionResistant();
switch (pRandomSpec.getRandomType()) {
case HASH:
return buildHash(myDigests.createDigest(myDigest), isResistent);
case HMAC:
final GordianMacSpec myMacSpec = GordianMacSpecBuilder.hMac(myDigest);
return buildHMAC(myMacs.createMac(myMacSpec), isResistent);
case CTR:
GordianSymCipherSpec myCipherSpec = GordianSymCipherSpecBuilder.ecb(pRandomSpec.getSymKeySpec(), GordianPadding.NONE);
return buildCTR(myCiphers.createSymKeyCipher(myCipherSpec), isResistent);
case X931:
myCipherSpec = GordianSymCipherSpecBuilder.ecb(pRandomSpec.getSymKeySpec(), GordianPadding.NONE);
return buildX931(myCiphers.createSymKeyCipher(myCipherSpec), isResistent);
default:
throw new GordianDataException(GordianBaseData.getInvalidText(pRandomSpec));
}
}
@Override
public GordianCombinedRandom createRandom(final GordianRandomSpec pCtrSpec,
final GordianRandomSpec pHashSpec) throws GordianException {
/* Check validity of ctrSpecs */
if (!validCombinedSpec(pCtrSpec, pHashSpec)) {
throw new GordianDataException(GordianBaseData.getInvalidText(pCtrSpec)
+ "-" + pHashSpec);
}
/* Build pair of randoms */
final GordianSecureRandom myCtr = createRandom(pCtrSpec);
final GordianSecureRandom myHash = createRandom(pHashSpec);
/* return the combined random */
return new GordianCombinedRandom(myCtr, myHash);
}
@Override
public Predicate<GordianRandomSpec> supportedRandomSpecs() {
return this::validRandomSpec;
}
@Override
public BiPredicate<GordianRandomSpec, GordianRandomSpec> supportedCombinedSpecs() {
return this::validCombinedSpec;
}
/**
* Check RandomSpec.
*
* @param pRandomSpec the randomSpec
* @return true/false
*/
private boolean validRandomSpec(final GordianRandomSpec pRandomSpec) {
/* Reject invalid randomSpec */
if (pRandomSpec == null || !pRandomSpec.isValid()) {
return false;
}
/* Access details */
final GordianRandomType myType = pRandomSpec.getRandomType();
final GordianDigestSpec myDigest = pRandomSpec.getDigestSpec();
final GordianSymKeySpec mySymKey = pRandomSpec.getSymKeySpec();
/* Check that the randomType is supported */
switch (myType) {
case HASH:
final GordianCoreDigestFactory myDigests = (GordianCoreDigestFactory) theFactory.getDigestFactory();
return myDigest != null && myDigests.validDigestSpec(myDigest);
case HMAC:
final GordianCoreMacFactory myMacs = (GordianCoreMacFactory) theFactory.getMacFactory();
return myDigest != null && myMacs.validHMacSpec(myDigest);
case CTR:
case X931:
final GordianCoreCipherFactory myCiphers = (GordianCoreCipherFactory) theFactory.getCipherFactory();
return mySymKey != null && myCiphers.validSymKeySpec(mySymKey);
default:
return false;
}
}
/**
* Check combined RandomSpec.
*
* @param pCtrSpec the Counter Spec.
* @param pHashSpec the Hash Spec
* @return true/false
*/
private boolean validCombinedSpec(final GordianRandomSpec pCtrSpec,
final GordianRandomSpec pHashSpec) {
/* Check validity of ctrSpecs */
if (!supportedRandomSpecs().test(pCtrSpec)
|| pCtrSpec.getRandomType() != GordianRandomType.CTR
|| pCtrSpec.getSymKeySpec().getKeyLength() != GordianLength.LEN_128
|| pCtrSpec.getSymKeySpec().getBlockLength() != GordianLength.LEN_128) {
return false;
}
/* Validate the hashSpec */
return supportedRandomSpecs().test(pHashSpec)
&& pHashSpec.getRandomType() == GordianRandomType.HASH
&& pHashSpec.getDigestSpec().getDigestLength() == GordianLength.LEN_512;
}
/**
* Create a random combinedRandom.
*
* @return the combinedRandom
* @throws GordianException on error
*/
GordianCombinedRandom generateRandomCombined() throws GordianException {
/* Create a random ctrSpec */
final GordianSymKeySpec mySymKeySpec = generateRandomSymKeySpec();
final GordianRandomSpec myCtrSpec = GordianRandomSpecBuilder.ctr(mySymKeySpec);
/* Create a random hashSpec */
final GordianDigestSpec myDigestSpec = generateRandomDigestSpec();
final GordianRandomSpec myHashSpec = GordianRandomSpecBuilder.hash(myDigestSpec);
/* Build the combinedRandom */
return createRandom(myCtrSpec, myHashSpec);
}
/**
* Obtain random SymKeySpec with blockLength/keySize of 128 bits.
*
* @return the random symKeySpec
*/
private GordianSymKeySpec generateRandomSymKeySpec() {
/* Access the list of symKeySpecs and unique symKeyTypes */
final GordianCoreCipherFactory myCiphers = (GordianCoreCipherFactory) theFactory.getCipherFactory();
final List<GordianSymKeySpec> mySpecs = myCiphers.listAllSupportedSymKeySpecs(GordianLength.LEN_128);
/* Remove the specs that are wrong block size and obtain keyTypes */
mySpecs.removeIf(s -> s.getBlockLength() != GordianLength.LEN_128);
final List<GordianSymKeyType> myTypes
= mySpecs.stream().map(GordianSymKeySpec::getSymKeyType).collect(Collectors.toCollection(ArrayList::new));
/* Determine a random index into the list and obtain the symKeyType */
int myIndex = theRandom.nextInt(myTypes.size());
final GordianSymKeyType myKeyType = myTypes.get(myIndex);
/* Select from among possible keySpecs of this type */
mySpecs.removeIf(s -> s.getSymKeyType() != myKeyType);
myIndex = theRandom.nextInt(mySpecs.size());
return mySpecs.get(myIndex);
}
/**
* Obtain random DigestSpec for a large data output length of 512-bits.
*
* @return the random digestSpec
*/
private GordianDigestSpec generateRandomDigestSpec() {
/* Access the list to select from */
final GordianCoreDigestFactory myDigests = (GordianCoreDigestFactory) theFactory.getDigestFactory();
final List<GordianDigestSpec> mySpecs = myDigests.listAllSupportedSpecs();
mySpecs.removeIf(s -> !s.getDigestType().supportsLargeData()
|| s.getDigestLength() != GordianLength.LEN_512);
final List<GordianDigestType> myTypes
= mySpecs.stream().map(GordianDigestSpec::getDigestType).collect(Collectors.toCollection(ArrayList::new));
/* Determine a random index into the list and obtain the digestType */
final SecureRandom myRandom = theFactory.getRandomSource().getRandom();
int myIndex = myRandom.nextInt(myTypes.size());
final GordianDigestType myDigestType = myTypes.get(myIndex);
/* Select from among possible digestSpecs of this type */
mySpecs.removeIf(s -> s.getDigestType() != myDigestType);
myIndex = myRandom.nextInt(mySpecs.size());
return mySpecs.get(myIndex);
}
@Override
public GordianDigest generateRandomDigest(final boolean pLargeData) throws GordianException {
/* Access Digest Factory and IdManager */
final GordianDigestFactory myDigests = theFactory.getDigestFactory();
final GordianIdManager myIds = theFactory.getIdManager();
/* Determine a random specification and create it */
final GordianDigestSpec mySpec = myIds.generateRandomDigestSpec(pLargeData);
return myDigests.createDigest(mySpec);
}
@Override
public GordianMac generateRandomMac(final GordianLength pKeyLen,
final boolean pLargeData) throws GordianException {
/* Access Mac Factory and IdManager */
final GordianMacFactory myMacs = theFactory.getMacFactory();
final GordianIdManager myIds = theFactory.getIdManager();
/* Determine a random specification */
final GordianMacSpec mySpec = myIds.generateRandomMacSpec(pKeyLen, pLargeData);
/* Determine a random key */
final GordianKeyGenerator<GordianMacSpec> myGenerator = myMacs.getKeyGenerator(mySpec);
final GordianKey<GordianMacSpec> myKey = myGenerator.generateKey();
/* Create and initialise the MAC */
final GordianMac myMac = myMacs.createMac(mySpec);
myMac.init(GordianMacParameters.keyWithRandomNonce(myKey));
/* Return it */
return myMac;
}
@Override
public GordianKey<GordianSymKeySpec> generateRandomSymKey(final GordianLength pKeyLen) throws GordianException {
/* Access Cipher Factory and IdManager */
final GordianCipherFactory myCiphers = theFactory.getCipherFactory();
final GordianIdManager myIds = theFactory.getIdManager();
/* Determine a random keySpec */
final GordianSymKeySpec mySpec = myIds.generateRandomSymKeySpec(pKeyLen);
/* Generate a random key */
final GordianKeyGenerator<GordianSymKeySpec> myGenerator = myCiphers.getKeyGenerator(mySpec);
return myGenerator.generateKey();
}
@Override
public GordianKey<GordianStreamKeySpec> generateRandomStreamKey(final GordianLength pKeyLen,
final boolean pLargeData) throws GordianException {
/* Access Cipher Factory and IdManager */
final GordianCipherFactory myCiphers = theFactory.getCipherFactory();
final GordianIdManager myIds = theFactory.getIdManager();
/* Generate a random keySpec */
final GordianStreamKeySpec mySpec = myIds.generateRandomStreamKeySpec(pKeyLen, pLargeData);
/* Generate a random key */
final GordianKeyGenerator<GordianStreamKeySpec> myGenerator = myCiphers.getKeyGenerator(mySpec);
return myGenerator.generateKey();
}
/**
* Build a SecureRandom based on a SP 800-90A Hash DRBG.
*
* @param pDigest digest to use in the DRBG underneath the SecureRandom.
* @param isPredictionResistant specify whether the underlying DRBG in the resulting
* SecureRandom should re-seed on each request for bytes.
* @return a SecureRandom supported by a Hash DRBG.
*/
private GordianSecureRandom buildHash(final GordianDigest pDigest,
final boolean isPredictionResistant) {
/* Create initVector */
final byte[] myInit = theRandom.generateSeed(NUM_ENTROPY_BYTES_REQUIRED);
/* Build DRBG */
final EntropySource myEntropy = theEntropyProvider.get(NUM_ENTROPY_BITS_REQUIRED);
final GordianSP800HashDRBG myProvider = new GordianSP800HashDRBG(pDigest, myEntropy, theRandomSource.defaultPersonalisation(), myInit);
return new GordianSecureRandom(myProvider, theRandom, myEntropy, isPredictionResistant);
}
/**
* Build a SecureRandom based on a SP 800-90A HMAC DRBG.
*
* @param hMac hMac to use in the DRBG underneath the SecureRandom.
* @param isPredictionResistant specify whether the underlying DRBG in the resulting
* SecureRandom should re-seed on each request for bytes.
* @return a SecureRandom supported by a HMAC DRBG.
*/
private GordianSecureRandom buildHMAC(final GordianMac hMac,
final boolean isPredictionResistant) {
/* Create initVector */
final byte[] myInit = theRandom.generateSeed(NUM_ENTROPY_BYTES_REQUIRED);
/* Build DRBG */
final EntropySource myEntropy = theEntropyProvider.get(NUM_ENTROPY_BITS_REQUIRED);
final GordianSP800HMacDRBG myProvider = new GordianSP800HMacDRBG(hMac, myEntropy, theRandomSource.defaultPersonalisation(), myInit);
return new GordianSecureRandom(myProvider, theRandom, myEntropy, isPredictionResistant);
}
/**
* Build a SecureRandom based on a SP 800-90A CTR DRBG.
*
* @param pCipher cipher to use in the DRBG underneath the SecureRandom.
* @param isPredictionResistant specify whether the underlying DRBG in the resulting
* SecureRandom should re-seed on each request for bytes.
* @return a SecureRandom supported by a CTR DRBG.
* @throws GordianException on error
*/
@SuppressWarnings("unchecked")
private GordianSecureRandom buildCTR(final GordianSymCipher pCipher,
final boolean isPredictionResistant) throws GordianException {
/* Create initVector */
final byte[] myInit = theRandom.generateSeed(NUM_ENTROPY_BYTES_REQUIRED);
/* Build DRBG */
final GordianCoreCipher<GordianSymKeySpec> myCipher = (GordianCoreCipher<GordianSymKeySpec>) pCipher;
final EntropySource myEntropy = theEntropyProvider.get(NUM_ENTROPY_BITS_REQUIRED);
final GordianSP800CTRDRBG myProvider = new GordianSP800CTRDRBG(myCipher,
myEntropy, theRandomSource.defaultPersonalisation(), myInit);
return new GordianSecureRandom(myProvider, theRandom, myEntropy, isPredictionResistant);
}
/**
* Build a SecureRandom based on a X931 Cipher DRBG.
*
* @param pCipher ctr cipher to use in the DRBG underneath the SecureRandom.
* @param isPredictionResistant specify whether the underlying DRBG in the resulting
* SecureRandom should re-seed on each request for bytes.
* @return a SecureRandom supported by a HMAC DRBG.
* @throws GordianException on error
*/
private GordianSecureRandom buildX931(final GordianSymCipher pCipher,
final boolean isPredictionResistant) throws GordianException {
/* Initialise the cipher with a random key */
final GordianCipherFactory myCiphers = theFactory.getCipherFactory();
final GordianKeyGenerator<GordianSymKeySpec> myGenerator = myCiphers.getKeyGenerator(pCipher.getKeyType());
final GordianKey<GordianSymKeySpec> myKey = myGenerator.generateKey();
pCipher.initForEncrypt(GordianCipherParameters.key(myKey));
/* Build DRBG */
final EntropySource myEntropy = theEntropyProvider.get(pCipher.getKeyType().getBlockLength().getLength());
final GordianX931CipherDRBG myProvider = new GordianX931CipherDRBG(pCipher, myEntropy, theRandomSource.defaultPersonalisation());
return new GordianSecureRandom(myProvider, theRandom, myEntropy, isPredictionResistant);
}
@Override
public List<GordianRandomSpec> listAllSupportedRandomSpecs() {
return listAllPossibleSpecs()
.stream()
.filter(supportedRandomSpecs())
.toList();
}
@Override
public List<GordianRandomSpec> listAllSupportedRandomSpecs(final GordianRandomType pType) {
return listAllPossibleSpecs()
.stream()
.filter(s -> s.getRandomType().equals(pType))
.filter(supportedRandomSpecs())
.toList();
}
@Override
public List<GordianRandomSpec> listAllSupportedRandomSpecs(final GordianRandomType pType,
final GordianLength pKeyLen) {
return listAllPossibleSpecs()
.stream()
.filter(s -> s.getRandomType().equals(pType))
.filter(s -> s.getRandomType().hasSymKeySpec())
.filter(s -> s.getSymKeySpec().getKeyLength() == pKeyLen)
.filter(supportedRandomSpecs())
.toList();
}
/**
* List all possible randomSpecs.
*
* @return the list
*/
private List<GordianRandomSpec> listAllPossibleSpecs() {
/* Create the array list */
final List<GordianRandomSpec> myList = new ArrayList<>();
/* For each digestSpec */
for (final GordianDigestSpec mySpec : theFactory.getDigestFactory().listAllPossibleSpecs()) {
/* Add a hash random */
myList.add(GordianRandomSpecBuilder.hash(mySpec));
myList.add(GordianRandomSpecBuilder.hashResist(mySpec));
/* Add an hMac random */
myList.add(GordianRandomSpecBuilder.hMac(mySpec));
myList.add(GordianRandomSpecBuilder.hMacResist(mySpec));
}
/* For each KeyLength */
final Iterator<GordianLength> myIterator = GordianKeyLengths.iterator();
while (myIterator.hasNext()) {
final GordianLength myKeyLen = myIterator.next();
/* For each symKeySpec */
for (final GordianSymKeySpec mySpec : theFactory.getCipherFactory().listAllSymKeySpecs(myKeyLen)) {
/* Add a CTR random */
myList.add(GordianRandomSpecBuilder.ctr(mySpec));
myList.add(GordianRandomSpecBuilder.ctrResist(mySpec));
/* Add an X931 random */
myList.add(GordianRandomSpecBuilder.x931(mySpec));
myList.add(GordianRandomSpecBuilder.x931Resist(mySpec));
}
}
/* Return the list */
return myList;
}
}