GordianSP800HashDRBG.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.GordianLength;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigest;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianByteArrayInteger;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianDataConverter;
import org.bouncycastle.crypto.prng.EntropySource;
import org.bouncycastle.util.Arrays;
/**
* Implementation of HashSP800DRBG based on the BouncyCastle Code.
* <p>
* This implementation is modified so that it accepts any GordianDigest.
*/
public final class GordianSP800HashDRBG
implements GordianDRBGenerator {
/**
* The Initial Seed Id.
*/
private static final byte[] INIT_ID = {0};
/**
* The ReSeed Id.
*/
private static final byte[] RESEED_ID = {1};
/**
* The Extra bytes Id.
*/
private static final byte[] XTRA_ID = {2};
/**
* The ReHash Id.
*/
private static final byte[] REHASH_ID = {3};
/**
* The Seed length for large digests.
*/
public static final int LONG_SEED_LENGTH = 888;
/**
* The Seed length for large digests.
*/
public static final int SHORT_SEED_LENGTH = 440;
/**
* The Message Digest.
*/
private final GordianDigest theDigest;
/**
* The SeedLength.
*/
private final int theSeedLength;
/**
* The Entropy Source.
*/
private final EntropySource theEntropy;
/**
* The ReSeed Counter.
*/
private final GordianByteArrayInteger theReseedCounter;
/**
* The Variable Hash.
*/
private GordianByteArrayInteger theV;
/**
* The Constant Hash.
*/
private GordianByteArrayInteger theC;
/**
* Construct a SP800-90A Hash DRBG.
*
* @param pDigest source digest to use for DRB stream.
* @param pEntropy source of entropy to use for seeding/reSeeding.
* @param pSecurityBytes personalisation string to distinguish this DRBG (may be null).
* @param pInitVector nonce to further distinguish this DRBG (may be null).
*/
public GordianSP800HashDRBG(final GordianDigest pDigest,
final EntropySource pEntropy,
final byte[] pSecurityBytes,
final byte[] pInitVector) {
/* Store digest and entropy source */
theDigest = pDigest;
theEntropy = pEntropy;
/* Calculate the seedLength */
theSeedLength = pDigest.getDigestSize() > GordianLength.LEN_256.getByteLength()
? LONG_SEED_LENGTH
: SHORT_SEED_LENGTH;
/* Create variable Hash */
final byte[] myEntropy = theEntropy.getEntropy();
final byte[] mySeed = Arrays.concatenate(myEntropy, pInitVector, pSecurityBytes);
theV = hashDerive(mySeed, theSeedLength);
/* Create constant hash */
final byte[] myTempH = Arrays.concatenate(INIT_ID, theV.getBuffer());
theC = hashDerive(myTempH, theSeedLength);
/* Initialise reSeed counter */
theReseedCounter = new GordianByteArrayInteger(Long.BYTES);
theReseedCounter.iterate();
}
@Override
public void reseed(final byte[] pXtraBytes) {
/* Create variable Hash */
final byte[] myEntropy = theEntropy.getEntropy();
final byte[] mySeed = Arrays.concatenate(RESEED_ID, theV.getBuffer(), myEntropy, pXtraBytes);
theV = hashDerive(mySeed, theSeedLength);
/* Create constant hash */
final byte[] myTempH = Arrays.concatenate(INIT_ID, theV.getBuffer());
theC = hashDerive(myTempH, theSeedLength);
/* re-initialise reSeed counter */
theReseedCounter.reset();
theReseedCounter.iterate();
}
@Override
public int generate(final byte[] pOutput,
final byte[] pXtraBytes,
final boolean isPredictionResistant) {
/* Check valid # of bits */
final int myNumBits = pOutput.length << GordianCoreRandomFactory.BIT_SHIFT;
if (myNumBits > GordianCoreRandomFactory.MAX_BITS_REQUEST) {
throw new IllegalArgumentException("Number of bits per request limited to "
+ GordianCoreRandomFactory.MAX_BITS_REQUEST);
}
/* Check for reSeed required */
if (theReseedCounter.compareLimit(GordianCoreRandomFactory.RESEED_MAX)) {
return -1;
}
/* If we are prediction resistant */
if (isPredictionResistant) {
/* ReSeed */
reseed(pXtraBytes);
/* else if we have extra bytes */
} else if (pXtraBytes != null) {
/* Hash the new input and add to variable hash */
final byte[] newInput = Arrays.concatenate(XTRA_ID, theV.getBuffer(), pXtraBytes);
theV.addTo(theDigest.finish(newInput));
}
/* Generate the requested bits */
final byte[] myResult = hashgen(theV.getBuffer(), myNumBits);
/* Adjust the variable hash */
final byte[] myTempH = Arrays.concatenate(REHASH_ID, theV.getBuffer());
/* Add the hash and constant */
theV.addTo(theDigest.finish(myTempH));
theV.addTo(theC.getBuffer());
/* Add the reSeed counter */
theV.addTo(theReseedCounter.getBuffer());
/* Iterate the reSeed counter */
theReseedCounter.iterate();
/* Return the bytes */
System.arraycopy(myResult, 0, pOutput, 0, pOutput.length);
/* Return the number of bits generated */
return myNumBits;
}
/**
* Stretch a hash output to required # of bits.
*
* @param pInputBytes the input bytes to hash
* @param pNumBits the number of output bits
* @return the stretched hash
*/
private byte[] hashgen(final byte[] pInputBytes,
final int pNumBits) {
/* Determine # of iterations */
final int mySize = theDigest.getDigestSize();
final int myLen = pNumBits >> GordianCoreRandomFactory.BIT_SHIFT;
/* Allocate counters */
final GordianByteArrayInteger myData = new GordianByteArrayInteger(pInputBytes);
final byte[] myOutput = new byte[myLen];
/* while we need to generate more bytes */
int myBuilt = 0;
while (myBuilt < myLen) {
/* Calculate the digest */
final byte[] myDigest = theDigest.finish(myData.getBuffer());
/* Determine how many bytes of this hash should be used */
int myNeeded = myLen
- myBuilt;
if (myNeeded > mySize) {
myNeeded = mySize;
}
/* Copy bytes across */
System.arraycopy(myDigest, 0, myOutput, myBuilt, myNeeded);
myBuilt += myNeeded;
/* Iterate the data */
myData.iterate();
}
/* Return the result */
return myOutput;
}
/**
* Hash derivation function (hash_df).
*
* @param pSeedMaterial the seed material
* @param pSeedLength the length of seed required
* @return the new hash as a counter
*/
private GordianByteArrayInteger hashDerive(final byte[] pSeedMaterial,
final int pSeedLength) {
/* Determine sizes */
final int mySize = theDigest.getDigestSize();
final int myLen = pSeedLength >> GordianCoreRandomFactory.BIT_SHIFT;
byte myCount = 1;
/* Create output buffer */
final byte[] myOutput = new byte[myLen];
/* Create seed array */
final byte[] mySeed = new byte[Integer.BYTES];
int mySeedLength = pSeedLength;
for (int i = mySeed.length - 1; i >= 0; i--) {
mySeed[i] = (byte) mySeedLength;
mySeedLength >>= GordianDataConverter.BYTE_SHIFT;
}
/* while we need to generate more bytes */
int myBuilt = 0;
while (myBuilt < myLen) {
/* Update with the count */
theDigest.update(myCount);
/* Update with the seed length */
theDigest.update(mySeed);
/* Create digest with the seed material */
final byte[] myDigest = theDigest.finish(pSeedMaterial);
/* Determine how many bytes of this hash should be used */
int myNeeded = myLen
- myBuilt;
if (myNeeded > mySize) {
myNeeded = mySize;
}
/* Copy bytes across */
System.arraycopy(myDigest, 0, myOutput, myBuilt, myNeeded);
myBuilt += myNeeded;
/* Iterate the counter */
myCount++;
}
/* Return byte array counter */
return new GordianByteArrayInteger(myOutput);
}
@Override
public int getBlockSize() {
return theDigest.getDigestSize();
}
@Override
public String getAlgorithm() {
return GordianCoreRandomFactory.SP800_PREFIX + theDigest.getDigestSpec().toString();
}
}