GordianX931CipherDRBG.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.cipher.GordianSymCipher;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymCipherSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianByteArrayInteger;
import org.bouncycastle.crypto.prng.EntropySource;
/**
* Implementation of X931DRBG based on the BouncyCastle Code.
* <p>
* This implementation is modified so that it accepts any GordianCipher.
*/
public class GordianX931CipherDRBG
implements GordianDRBGenerator {
/**
* The X931 prefix.
*/
private static final String X931_PREFIX = "X931-";
/**
* The bit shift.
*/
private static final int BIT_SHIFT = 3;
/**
* The power of 2 for RESEED calculation.
*/
private static final int RESEED_POWER = 24;
/**
* Max # of bits before reSeed.
*/
private static final long BLOCK128_RESEED_MAX = 1L << (RESEED_POWER - 1);
/**
* The power of 2 for BITS calculation.
*/
private static final int BITS_POWER = 19;
/**
* Max # of bits per request.
*/
private static final int BLOCK128_MAX_BITS_REQUEST = 1 << (BITS_POWER - 1);
/**
* The Cipher.
*/
private final GordianSymCipher theCipher;
/**
* The Entropy Source.
*/
private final EntropySource theEntropy;
/**
* The DateTime vector.
*/
private final GordianByteArrayInteger theDT;
/**
* The ReSeed Counter.
*/
private final GordianByteArrayInteger theReseedCounter;
/**
* The intermediate buffer.
*/
private final byte[] theI;
/**
* The result buffer.
*/
private final byte[] theR;
/**
* The entropy bytes.
*/
private byte[] theV;
/**
* Constructor.
*
* @param pCipher source cipher to use for DRB stream.
* @param pEntropy source of entropy to use for seeding/reSeeding.
* @param pInitVector nonce to further distinguish this DRBG.
*/
public GordianX931CipherDRBG(final GordianSymCipher pCipher,
final EntropySource pEntropy,
final byte[] pInitVector) {
/* Store parameters */
theCipher = pCipher;
theEntropy = pEntropy;
/* Determine the bufferSize */
final int mySize = getBlockSize();
final int myLen = mySize >> BIT_SHIFT;
/* Create DT Buffer */
theDT = new GordianByteArrayInteger(myLen);
final int myCopyLen = Math.min(myLen, pInitVector.length);
System.arraycopy(pInitVector, 0, theDT.getBuffer(), 0, myCopyLen);
/* Create intermediate buffers */
theI = new byte[myLen];
theR = new byte[myLen];
/* Initialise reSeed counter */
theReseedCounter = new GordianByteArrayInteger(Long.BYTES);
theReseedCounter.iterate();
}
@Override
public int generate(final byte[] pOutput,
final byte[] pXtraBytes,
final boolean isPredictionResistant) {
/* Check valid # of bits */
final int myNumBits = pOutput.length << BIT_SHIFT;
if (myNumBits > BLOCK128_MAX_BITS_REQUEST) {
throw new IllegalArgumentException("Number of bits per request limited to "
+ BLOCK128_MAX_BITS_REQUEST);
}
/* Check for reSeed required */
if (theReseedCounter.compareLimit(BLOCK128_RESEED_MAX)) {
return -1;
}
/* If we are prediction resistant or have not allocated V */
if (isPredictionResistant
|| theV == null) {
/* Initialise V from entropy */
initFromEntropy();
}
/* Protect against exceptions */
try {
/* Generate the bits */
final byte[] myResult = cipherGen(myNumBits);
/* Iterate the reSeed counter */
theReseedCounter.iterate();
/* Return the bytes */
System.arraycopy(myResult, 0, pOutput, 0, pOutput.length);
} catch (GordianException e) {
throw new IllegalStateException(e);
}
/* Return the number of bits generated */
return myNumBits;
}
/**
* Stretch a cipher output to required # of bits.
*
* @param pNumBits the number of output bits
* @return the stretched cipher output
* @throws GordianException on error
*/
private byte[] cipherGen(final int pNumBits) throws GordianException {
/* Determine # of iterations */
final int mySize = getBlockSize() >> BIT_SHIFT;
final int myLen = pNumBits >> BIT_SHIFT;
/* Allocate counters */
final byte[] myOutput = new byte[myLen];
/* while we need to generate more bytes */
int myBuilt = 0;
while (myBuilt < myLen) {
/* Generate a new block */
theCipher.finish(theDT.getBuffer(), 0, mySize, theI);
processBytes(theR, theI, theV);
processBytes(theV, theR, theI);
/* Determine how many bytes of this hash should be used */
int myNeeded = myLen
- myBuilt;
if (myNeeded > mySize) {
myNeeded = mySize;
}
/* Copy bytes across */
System.arraycopy(theR, 0, myOutput, myBuilt, myNeeded);
myBuilt += myNeeded;
/* Iterate the dateTime */
theDT.iterate();
}
/* Return the result */
return myOutput;
}
@Override
public void reseed(final byte[] pSeed) {
reseed();
}
/**
* ReSeed the RNG.
*/
private void reseed() {
/* Initialise V from entropy */
initFromEntropy();
/* re-initialise reSeed counter */
theReseedCounter.reset();
theReseedCounter.iterate();
}
/**
* Initialise from entropy.
*/
private void initFromEntropy() {
/* Initialise V from entropy */
theV = theEntropy.getEntropy();
if (theV.length != theI.length) {
throw new IllegalStateException("Insufficient entropy returned");
}
}
/**
* Process bytes.
*
* @param pResult the result
* @param pFirst the first array
* @param pSecond the second array
* @throws GordianException on error
*/
private void processBytes(final byte[] pResult,
final byte[] pFirst,
final byte[] pSecond) throws GordianException {
/* Combine the two inputs */
for (int i = 0; i != pResult.length; i++) {
pResult[i] = (byte) (pFirst[i] ^ pSecond[i]);
}
/* Process the block via the cipher */
theCipher.finish(pResult, 0, pResult.length, pResult);
}
@Override
public int getBlockSize() {
final GordianSymCipherSpec mySpec = (GordianSymCipherSpec) theCipher.getCipherSpec();
return mySpec.getBlockLength().getLength();
}
@Override
public String getAlgorithm() {
return X931_PREFIX + theCipher.getCipherSpec().toString();
}
}