| package org.bouncycastle.crypto.prng; |
| |
| import org.bouncycastle.crypto.BlockCipher; |
| |
| public class X931RNG |
| { |
| private static final long BLOCK64_RESEED_MAX = 1L << (16 - 1); |
| private static final long BLOCK128_RESEED_MAX = 1L << (24 - 1); |
| private static final int BLOCK64_MAX_BITS_REQUEST = 1 << (13 - 1); |
| private static final int BLOCK128_MAX_BITS_REQUEST = 1 << (19 - 1); |
| |
| private final BlockCipher engine; |
| private final EntropySource entropySource; |
| |
| private final byte[] DT; |
| private final byte[] I; |
| private final byte[] R;; |
| |
| private byte[] V; |
| |
| private long reseedCounter = 1; |
| |
| /** |
| * |
| * @param engine |
| * @param entropySource |
| */ |
| public X931RNG(BlockCipher engine, byte[] dateTimeVector, EntropySource entropySource) |
| { |
| this.engine = engine; |
| this.entropySource = entropySource; |
| |
| this.DT = new byte[engine.getBlockSize()]; |
| |
| System.arraycopy(dateTimeVector, 0, DT, 0, DT.length); |
| |
| this.I = new byte[engine.getBlockSize()]; |
| this.R = new byte[engine.getBlockSize()]; |
| } |
| |
| /** |
| * Populate a passed in array with random data. |
| * |
| * @param output output array for generated bits. |
| * @param predictionResistant true if a reseed should be forced, false otherwise. |
| * |
| * @return number of bits generated, -1 if a reseed required. |
| */ |
| int generate(byte[] output, boolean predictionResistant) |
| { |
| if (R.length == 8) // 64 bit block size |
| { |
| if (reseedCounter > BLOCK64_RESEED_MAX) |
| { |
| return -1; |
| } |
| |
| if (isTooLarge(output, BLOCK64_MAX_BITS_REQUEST / 8)) |
| { |
| throw new IllegalArgumentException("Number of bits per request limited to " + BLOCK64_MAX_BITS_REQUEST); |
| } |
| } |
| else |
| { |
| if (reseedCounter > BLOCK128_RESEED_MAX) |
| { |
| return -1; |
| } |
| |
| if (isTooLarge(output, BLOCK128_MAX_BITS_REQUEST / 8)) |
| { |
| throw new IllegalArgumentException("Number of bits per request limited to " + BLOCK128_MAX_BITS_REQUEST); |
| } |
| } |
| |
| if (predictionResistant || V == null) |
| { |
| V = entropySource.getEntropy(); |
| if (V.length != engine.getBlockSize()) |
| { |
| throw new IllegalStateException("Insufficient entropy returned"); |
| } |
| } |
| |
| int m = output.length / R.length; |
| |
| for (int i = 0; i < m; i++) |
| { |
| engine.processBlock(DT, 0, I, 0); |
| process(R, I, V); |
| process(V, R, I); |
| |
| System.arraycopy(R, 0, output, i * R.length, R.length); |
| |
| increment(DT); |
| } |
| |
| int bytesToCopy = (output.length - m * R.length); |
| |
| if (bytesToCopy > 0) |
| { |
| engine.processBlock(DT, 0, I, 0); |
| process(R, I, V); |
| process(V, R, I); |
| |
| System.arraycopy(R, 0, output, m * R.length, bytesToCopy); |
| |
| increment(DT); |
| } |
| |
| reseedCounter++; |
| |
| return output.length; |
| } |
| |
| /** |
| * Reseed the RNG. |
| */ |
| void reseed() |
| { |
| V = entropySource.getEntropy(); |
| if (V.length != engine.getBlockSize()) |
| { |
| throw new IllegalStateException("Insufficient entropy returned"); |
| } |
| reseedCounter = 1; |
| } |
| |
| EntropySource getEntropySource() |
| { |
| return entropySource; |
| } |
| |
| private void process(byte[] res, byte[] a, byte[] b) |
| { |
| for (int i = 0; i != res.length; i++) |
| { |
| res[i] = (byte)(a[i] ^ b[i]); |
| } |
| |
| engine.processBlock(res, 0, res, 0); |
| } |
| |
| private void increment(byte[] val) |
| { |
| for (int i = val.length - 1; i >= 0; i--) |
| { |
| if (++val[i] != 0) |
| { |
| break; |
| } |
| } |
| } |
| |
| private static boolean isTooLarge(byte[] bytes, int maxBytes) |
| { |
| return bytes != null && bytes.length > maxBytes; |
| } |
| } |