Last active
January 26, 2019 19:58
-
-
Save lordmulder/0f19621c6b1bc4640f1c4e393e1ed539 to your computer and use it in GitHub Desktop.
SHA-512-based CSRNG (cryptographically secure pseudorandom number generator) for Java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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