GordianSP800HMacDRBG.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.mac.GordianMac;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianByteArrayInteger;
import org.bouncycastle.crypto.prng.EntropySource;
import org.bouncycastle.util.Arrays;
/**
* Implementation of HMacSP800DRBG based on the BouncyCastle Code.
* <p>
* This implementation is modified so that it accepts any GordianMac.
*/
public final class GordianSP800HMacDRBG
implements GordianDRBGenerator {
/**
* The Update Id.
*/
private static final byte[] UPDATE_ID = {0};
/**
* The ReSeed Id.
*/
private static final byte[] SEED_ID = {1};
/**
* The HMac.
*/
private final GordianMac theHMac;
/**
* The Entropy Source.
*/
private final EntropySource theEntropy;
/**
* The ReSeed Counter.
*/
private final GordianByteArrayInteger theReseedCounter;
/**
* The Key.
*/
private final byte[] theKey;
/**
* The Hash.
*/
private final byte[] theHash;
/**
* Construct a SP800-90A Hash DRBG.
*
* @param pHMac Hash MAC to base the DRBG on.
* @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 GordianSP800HMacDRBG(final GordianMac pHMac,
final EntropySource pEntropy,
final byte[] pSecurityBytes,
final byte[] pInitVector) {
/* Store hMac and entropy source */
theHMac = pHMac;
theEntropy = pEntropy;
/* Create Seed Material */
final byte[] myEntropy = theEntropy.getEntropy();
final byte[] mySeed = Arrays.concatenate(myEntropy, pInitVector, pSecurityBytes);
/* Initialise buffers */
final int myLen = theHMac.getMacSize();
theKey = new byte[myLen];
theHash = new byte[myLen];
Arrays.fill(theHash, (byte) 1);
/* Update the state */
updateState(mySeed);
/* Initialise reSeed counter */
theReseedCounter = new GordianByteArrayInteger(Long.BYTES);
theReseedCounter.iterate();
}
/**
* Update the state (HMAC_DRBG_Update).
*
* @param pSeed the extra seed material
*/
private void updateState(final byte[] pSeed) {
try {
updateState(pSeed, UPDATE_ID);
if (pSeed != null) {
updateState(pSeed, SEED_ID);
}
} catch (GordianException e) {
throw new IllegalStateException(e);
}
}
/**
* Update the state.
*
* @param pSeed optional seed material
* @param pCycle the cycle id
* @throws GordianException on error
*/
private void updateState(final byte[] pSeed,
final byte[] pCycle) throws GordianException {
/* Initialise the hMac */
theHMac.initKeyBytes(theKey);
/* Update with hash and cycle id */
theHMac.update(theHash);
theHMac.update(pCycle);
/* Add any seed material */
if (pSeed != null) {
theHMac.update(pSeed);
}
/* Generate new key */
theHMac.finish(theKey, 0);
/* Calculate new hash */
theHMac.initKeyBytes(theKey);
theHMac.update(theHash);
theHMac.finish(theHash, 0);
}
@Override
public void reseed(final byte[] pXtraBytes) {
/* Create seed material */
final byte[] myEntropy = theEntropy.getEntropy();
final byte[] mySeed = Arrays.concatenate(myEntropy, pXtraBytes);
/* Update the state */
updateState(mySeed);
/* 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 myLen = pOutput.length;
final int myNumBits = myLen << 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;
}
/* Access XtraBytes */
byte[] myXtraBytes = pXtraBytes;
/* If we are prediction resistant */
if (isPredictionResistant) {
/* ReSeed and discard xtraBytes */
reseed(myXtraBytes);
myXtraBytes = null;
/* else if we have extra bytes */
} else if (myXtraBytes != null) {
/* Update the state */
updateState(myXtraBytes);
}
/* Allocate output buffer */
final byte[] myResult = new byte[myLen];
/* Protect against exceptions */
try {
/* Initialise the hMac */
theHMac.initKeyBytes(theKey);
final int mySize = theHMac.getMacSize();
/* while we need to generate more bytes */
int myBuilt = 0;
while (myBuilt < myLen) {
/* Update the mac */
theHMac.update(theHash);
theHMac.finish(theHash, 0);
/* Determine how many bytes of this hash should be used */
int myNeeded = myLen
- myBuilt;
if (myNeeded > mySize) {
myNeeded = mySize;
}
/* Copy bytes across */
System.arraycopy(theHash, 0, myResult, myBuilt, myNeeded);
myBuilt += myNeeded;
}
} catch (GordianException e) {
throw new IllegalStateException(e);
}
/* Update the state */
updateState(myXtraBytes);
/* 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;
}
@Override
public int getBlockSize() {
return theHMac.getMacSize();
}
@Override
public String getAlgorithm() {
return GordianCoreRandomFactory.SP800_PREFIX + theHMac.getMacSpec().toString();
}
}