Skip to content

Instantly share code, notes, and snippets.

@lordmulder
Last active January 26, 2019 19:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lordmulder/0f19621c6b1bc4640f1c4e393e1ed539 to your computer and use it in GitHub Desktop.
Save lordmulder/0f19621c6b1bc4640f1c4e393e1ed539 to your computer and use it in GitHub Desktop.
SHA-512-based CSRNG (cryptographically secure pseudorandom number generator) for Java
/*
* SHA-512 CSRNG
* Created by LoRd_MuldeR <mulder2@gmx.de>.
*
* This work is licensed under the CC0 1.0 Universal License.
* To view a copy of the license, visit:
* https://creativecommons.org/publicdomain/zero/1.0/legalcode
*/
package sha512csrng;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
import java.util.Random;
public class SHA512CSRNG {
public static final int BLOCK_SIZE = 64;
private static final double EPSILON = StrictMath.ulp(1.0);
// ======================================================================
// SHA-512 ENGINE
// ======================================================================
private static class Engine {
private final MessageDigest SHA512;
private final byte[] state = Base64.getDecoder().decode(
"JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxObIlFKCHmONATd75UZs806QxswKwpt8l8UN0/hNW1tUcJFw==");
private int carryBit = 0;
public Engine(final byte[] seed) {
try {
SHA512 = MessageDigest.getInstance("SHA-512");
} catch(NoSuchAlgorithmException e) {
throw new Error("Runtime does not support SHA-512 algorithm!", e);
}
if((seed == null) || (seed.length < 1)) {
throw new IllegalArgumentException("Seed must not be null or empty!");
}
final int[] indices = createIndices(seed.length);
final int maxRounds = Math.max(997, seed.length);
for(int r = 0; r < maxRounds; ++r) {
final int i = indices[r % BLOCK_SIZE];
state[i] = (byte) (state[i] ^ seed[r % seed.length]);
addToState(SHA512.digest(state));
}
}
public byte[] getNextBlock() {
final byte[] output;
addToState(output = SHA512.digest(state));
return output;
}
private void addToState(final byte[] other) {
assert other.length == state.length : "Length mistmatch detected!";
for(int i = state.length - 1; i >= 0; --i) {
final int temp = Byte.toUnsignedInt(state[i]) + Byte.toUnsignedInt(other[i]) + carryBit;
state[i] = (byte) temp;
carryBit = (temp >> 8);
}
}
private static int[] createIndices(final long seed) {
final int[] indices = new int[BLOCK_SIZE];
final Random prng = new Random(seed);
for(int rnd, i = 0; i < BLOCK_SIZE; ++i) {
rnd = prng.nextInt(i + 1);
if(rnd != i) {
indices[i] = indices[rnd];
}
indices[rnd] = i;
}
return indices;
}
}
// ======================================================================
// CONSTRUCTOR
// ======================================================================
/* RNG engine */
private final Engine engine;
/* Random bytes buffer */
private byte[] buffer = null;
private int offset = BLOCK_SIZE;
/* Box-Muller state */
private double nextGaussian;
private boolean haveNextGaussian = false;
/**
* Create a new SHA512CSRNG instance, seeded from the system's native "randomness" source;
* this constructor uses the "blocking" randomness source for seeding.
*/
public SHA512CSRNG() {
this(true);
}
/**
* Create a new SHA512CSRNG instance, seeded from the system's native "randomness" source.
*
* @param blocking
* use "blocking" randomness source for seeding, otherwise "non-blocking" one is used
*/
public SHA512CSRNG(final boolean blocking) {
final SecureRandom provider;
try {
provider = SecureRandom.getInstance(isWin32() ? "Windows-PRNG" : (blocking ? "NativePRNGBlocking" : "NativePRNGNonBlocking"));
} catch (NoSuchAlgorithmException e) {
throw new Error("Failed to create native RNG instance!", e);
}
final byte[] seed = Arrays.copyOf(provider.generateSeed(BLOCK_SIZE), 2 * BLOCK_SIZE);
for(int i = 0; i < BLOCK_SIZE; ++i) {
final long timestamp = nextTimestamp();
seed[BLOCK_SIZE + i] = (byte) ((timestamp ^ (timestamp >>> 8) ^ (timestamp >>> 16)) & 0xFFL);
}
engine = new Engine(seed);
}
/**
* Create a new SHA512CSRNG instance, seeded from the given byte array.
*
* @param seed
* the array of bytes to seed the CSRNG from, <b>must not</b> be <code>null</code> or empty
*/
public SHA512CSRNG(final byte[] seed) {
engine = new Engine(seed);
}
// ======================================================================
// PUBLIC METHODS
// ======================================================================
/**
* Fill the given byte-array completely with random bytes.
*
* @param result
* the byte-array to be filled with random bytes;
* all previous content of the array is overwritten
*/
public void nextBytes(final byte[] result) {
if((result == null) || (result.length < 1)) {
throw new IllegalArgumentException("Array must not be null or empty!");
}
int count, pos = 0;
while(pos < result.length) {
if(offset >= BLOCK_SIZE) {
buffer = engine.getNextBlock();
offset = 0;
}
count = Math.min(result.length - pos, BLOCK_SIZE - offset);
System.arraycopy(buffer, offset, result, pos, count);
pos += count;
offset += count;
}
}
/**
* Create an array of N random bytes.
*
* @param count
* the number of random bytes to be generated
*
* @return
* a new byte-array of length <code>count</code> containing the random bytes
*/
public byte[] nextBytes(final int count) {
final byte[] result;
nextBytes(result = new byte[count]);
return result;
}
/**
* Create a random, uniformly distributed <code>int</code> value between 0 (inclusive) and <code>Integer.MAX_VALUE</code> (inclusive).
*
* @return
* the next random <code>int</code> value
*/
public int nextInt() {
return nextInt(false);
}
/**
* Create a random, uniformly distributed <code>int</code> value.
* If <i>signed</i>, the value will be in the <code>Integer.MIN_VALUE</code> (inclusive) to <code>Integer.MAX_VALUE (inclusive)</code> range;
* if <i>unsigned</i>, the value will be in the <code>0</code> (inclusive) to <code>Integer.MAX_VALUE</code> (inclusive) range.
*
* @param signed
* specified whether the result should be <i>signed</i>; will be <i>unsigned</i> otherwise
*
* @return
* the next random <code>int</code> value
*/
public int nextInt(final boolean signed) {
if(BLOCK_SIZE - offset < 4) {
buffer = engine.getNextBlock();
offset = 0;
}
final int rand = ByteBuffer.wrap(buffer, offset, 4).getInt();
offset += 4;
return signed ? rand : (rand >>> 1);
}
/**
* Create a random, uniformly distributed <code>int</code> value between <code>0</code> (inclusive) and <code>max</code> (exclusive).
*
* @param max
* the upper bound; result is guaranteed to be <i>less</i> than this value
*
* @return
* the next random <code>int</code> value in the specified range
*/
public int nextInt(final int max) {
if(max < 1) {
throw new IllegalArgumentException("Max value must be positive!");
}
return nextInt(false) / ((Integer.MAX_VALUE / max) + 1);
}
/**
* Create a random, uniformly distributed <code>int</code> value between <code>min</code> (inclusive) and <code>max</code> (exclusive).
*
* @param min
* the lower bound; result is guaranteed to be <i>greater than or equal</i> to this value
*
* @param max
* the upper bound; result is guaranteed to be <i>less</i> than this value; must be <i>greater</i> than <code>min</code>
*
* @return
* the next random <code>int</code> value in the specified range
*/
public int nextInt(final int min, final int max) {
if(max <= min) {
throw new IllegalArgumentException("Max value must be greater than min value!");
}
final int range;
try {
range = StrictMath.subtractExact(max, min);
} catch(ArithmeticException e) {
throw new IllegalArgumentException("Numeric range is too large!", e);
}
return min + nextInt(range);
}
/**
* Create a random, uniformly distributed <code>long</code> value between 0 (inclusive) and <code>Long.MAX_VALUE</code> (inclusive).
*
* @return
* the next random <code>long</code> value
*/
public long nextLong() {
return nextLong(false);
}
/**
* Create a random, uniformly distributed <code>long</code> value.
* If <i>signed</i>, the value will be in the <code>Long.MIN_VALUE</code> (inclusive) to <code>Long.MAX_VALUE (inclusive)</code> range;
* if <i>unsigned</i>, the value will be in the <code>0</code> (inclusive) to <code>Long.MAX_VALUE</code> (inclusive) range.
*
* @param signed
* specified whether the result should be <i>signed</i>; will be <i>unsigned</i> otherwise
*
* @return
* the next random <code>long</code> value
*/
public long nextLong(final boolean signed) {
if(BLOCK_SIZE - offset < 8) {
buffer = engine.getNextBlock();
offset = 0;
}
final long rand = ByteBuffer.wrap(buffer, offset, 8).getLong();
offset += 8;
return signed ? rand : (rand >>> 1);
}
/**
* Create a random, uniformly distributed <code>long</code> value between <code>0</code> (inclusive) and <code>max</code> (exclusive).
*
* @param max
* the upper bound; result is guaranteed to be <i>less</i> than this value
*
* @return
* the next random <code>long</code> value in the specified range
*/
public long nextLong(final long max) {
if(max < 1) {
throw new IllegalArgumentException("Max value must be positive!");
}
return nextLong(false) / ((Long.MAX_VALUE / max) + 1L);
}
/**
* Create a random, uniformly distributed <code>long</code> value between <code>min</code> (inclusive) and <code>max</code> (exclusive).
*
* @param min
* the lower bound; result is guaranteed to be <i>greater than or equal</i> to this value
*
* @param max
* the upper bound; result is guaranteed to be <i>less</i> than this value; must be <i>greater</i> than <code>min</code>
*
* @return
* the next random <code>long</code> value in the specified range
*/
public long nextLong(final long min, final long max) {
if(max <= min) {
throw new IllegalArgumentException("Max value must be greater than min value!");
}
final long range;
try {
range = StrictMath.subtractExact(max, min);
} catch(ArithmeticException e) {
throw new IllegalArgumentException("Numeric range is too large!", e);
}
return min + nextLong(range);
}
/**
* Create a random, uniformly distributed <code>float</code> value between <code>0.0f</code> and <code>1.0f</code>.
*
* @return
* the next random <code>float</code> value
*/
public float nextFloat() {
return ((float) nextInt(false)) / Integer.MAX_VALUE;
}
/**
* Create a random, uniformly distributed <code>double</code> value between <code>0.0</code> and <code>1.0</code>.
*
* @return
* the next random <code>double</code> value
*/
public double nextDouble() {
return ((double) nextInt(false)) / Integer.MAX_VALUE;
}
/**
* Create a random, Gaussian ("normally") distributed <code>double</code> value with mean <code>0.0</code> and standard deviation <code>1.0</code>.
*
* @return
* the next random <code>double</code> value
*/
public double nextGaussian() {
if(haveNextGaussian) {
haveNextGaussian = false;
return nextGaussian;
}
double u1, u2;
do {
u1 = SHA512CSRNG.this.nextDouble();
u2 = SHA512CSRNG.this.nextDouble();
} while(u1 < EPSILON);
final double v1 = StrictMath.sqrt((-2.0) * StrictMath.log(u1));
final double v2 = 2.0 * StrictMath.PI * u2;
haveNextGaussian = true;
nextGaussian = v1 * StrictMath.sin(v2);
return v1 * StrictMath.cos(v2);
}
// ======================================================================
// INTERNAL METHODS
// ======================================================================
/**
* Detect whether we are running on Microsoft Windows
*/
private static boolean isWin32() {
final String osName = System.getProperty("os.name", "unknown");
return osName.matches("(?i:.*\\bWindows\\b.*)");
}
/**
* Generate the next time-stamp, for seeding purpose
*/
private static long nextTimestamp() {
final long reference = System.nanoTime();
long time;
while((time = System.nanoTime()) == reference);
return time;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment