GordianCombinedRandom.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.impl.core.base.GordianSeededRandom;
import java.security.SecureRandom;
/**
* Combined Random.
*/
public class GordianCombinedRandom
extends SecureRandom
implements GordianSeededRandom {
/**
* SerialId.
*/
private static final long serialVersionUID = -7229182850521659615L;
/**
* The ctr random.
*/
private final GordianSecureRandom theCtrRandom;
/**
* The hash Random.
*/
private final GordianSecureRandom theHashRandom;
/**
* The pre-calculated buffer.
*/
private final byte[] theBuffer;
/**
* Bytes available in buffer.
*/
private int bytesAvailable;
/**
* Constructor.
*
* @param pCtr the ctrRandom
* @param pHash the hashRandom
*/
GordianCombinedRandom(final GordianSecureRandom pCtr,
final GordianSecureRandom pHash) {
theHashRandom = pHash;
theCtrRandom = pCtr;
theBuffer = new byte[GordianLength.LEN_512.getByteLength()];
}
@Override
public SecureRandom getRandom() {
return this;
}
@Override
public void setSeed(final byte[] seed) {
synchronized (this) {
/* Pass the call on to the ctrRandom */
theCtrRandom.setSeed(seed);
bytesAvailable = 0;
}
}
@Override
public void setSeed(final long seed) {
synchronized (this) {
if (theCtrRandom != null) {
/* Pass the call on to the ctrRandom */
theCtrRandom.setSeed(seed);
bytesAvailable = 0;
}
}
}
@Override
public void nextBytes(final byte[] bytes) {
synchronized (this) {
/* Obtain the required random bytes from the hashRandom */
theHashRandom.nextBytes(bytes);
/* Determine how many bytes are needed */
int bytesNeeded = bytes.length;
int bytesBuilt = 0;
/* If we have bytes available */
if (bytesAvailable > 0) {
/* Fulfil the request from the buffer as much as possible */
final int bytesToTransfer = Math.min(bytesNeeded, bytesAvailable);
processBuffer(theBuffer.length - bytesAvailable, bytes, 0, bytesToTransfer);
bytesAvailable -= bytesToTransfer;
bytesNeeded -= bytesToTransfer;
bytesBuilt += bytesToTransfer;
}
/* Loop to fulfil remaining bytes */
while (bytesNeeded > 0) {
/* Fill the buffer again */
theHashRandom.nextBytes(theBuffer);
bytesAvailable = theBuffer.length;
/* Fulfil the request from the buffer as much as possible */
final int bytesToTransfer = Math.min(bytesNeeded, bytesAvailable);
processBuffer(0, bytes, bytesBuilt, bytesToTransfer);
bytesAvailable -= bytesToTransfer;
bytesNeeded -= bytesToTransfer;
bytesBuilt += bytesToTransfer;
}
}
}
/**
* Xor bytes from buffer into output bytes.
*
* @param pBufPos the starting position in the buffer
* @param pOutput the output buffer,
* @param pOutPos the starting position in the output buffer
* @param pNumBytes the number of bytes to process
*/
private void processBuffer(final int pBufPos,
final byte[] pOutput,
final int pOutPos,
final int pNumBytes) {
/* Loop through the bytes */
for (int i = 0; i < pNumBytes; i++) {
pOutput[i + pOutPos] ^= theBuffer[i + pBufPos];
}
}
@Override
public byte[] generateSeed(final int numBytes) {
return theCtrRandom.generateSeed(numBytes);
}
@Override
public String getAlgorithm() {
return theCtrRandom.getAlgorithm()
+ "-" + theHashRandom.getAlgorithm();
}
@Override
public String toString() {
return getAlgorithm();
}
@Override
public void reseed(final byte[] pXtraInput) {
synchronized (this) {
theCtrRandom.reseed(pXtraInput);
theHashRandom.reseed(pXtraInput);
bytesAvailable = 0;
}
}
}