diff --git a/core/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandom.java b/core/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandom.java index 4d861c6239..7ebeecec21 100644 --- a/core/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandom.java +++ b/core/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandom.java @@ -67,4 +67,14 @@ public byte[] generateSeed(int numBytes) { return EntropyUtil.generateSeed(entropySource, numBytes); } + + /** + * Force a reseed of the DRBG + * + * @param additionalInput optional additional input + */ + public void reseed(byte[] additionalInput) + { + drbg.reseed(additionalInput); + } } diff --git a/prov/src/main/java/org/bouncycastle/jcajce/provider/drbg/DRBG.java b/prov/src/main/java/org/bouncycastle/jcajce/provider/drbg/DRBG.java index b33c305432..d48c40180b 100644 --- a/prov/src/main/java/org/bouncycastle/jcajce/provider/drbg/DRBG.java +++ b/prov/src/main/java/org/bouncycastle/jcajce/provider/drbg/DRBG.java @@ -5,10 +5,15 @@ import java.security.Provider; import java.security.SecureRandom; import java.security.SecureRandomSpi; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import org.bouncycastle.crypto.digests.SHA512Digest; +import org.bouncycastle.crypto.macs.HMac; import org.bouncycastle.crypto.prng.EntropySource; import org.bouncycastle.crypto.prng.EntropySourceProvider; +import org.bouncycastle.crypto.prng.SP800SecureRandom; import org.bouncycastle.crypto.prng.SP800SecureRandomBuilder; import org.bouncycastle.jcajce.provider.config.ConfigurableProvider; import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider; @@ -108,15 +113,22 @@ private static SecureRandom createBaseRandom(boolean isPredictionResistant) EntropySource initSource = entropyProvider.get(16 * 8); + byte[] personalisationString = isPredictionResistant ? generateDefaultPersonalizationString(initSource.getEntropy()) + : generateNonceIVPersonalizationString(initSource.getEntropy()); + return new SP800SecureRandomBuilder(entropyProvider) - .setPersonalizationString(generateDefaultPersonalizationString(initSource.getEntropy())) + .setPersonalizationString(personalisationString) .buildHash(new SHA512Digest(), Arrays.concatenate(initSource.getEntropy(), initSource.getEntropy()), isPredictionResistant); } else { - SecureRandom randomSource = createInitialEntropySource(); // needs to be done late, can't use static + SecureRandom randomSource = new HybridSecureRandom(); // needs to be done late, can't use static + + byte[] personalisationString = isPredictionResistant ? generateDefaultPersonalizationString(randomSource.generateSeed(16)) + : generateNonceIVPersonalizationString(randomSource.generateSeed(16)); + return new SP800SecureRandomBuilder(randomSource, true) - .setPersonalizationString(generateDefaultPersonalizationString(randomSource.generateSeed(16))) + .setPersonalizationString(personalisationString) .buildHash(new SHA512Digest(), randomSource.generateSeed(32), isPredictionResistant); } } @@ -196,4 +208,106 @@ private static byte[] generateNonceIVPersonalizationString(byte[] seed) return Arrays.concatenate(Strings.toByteArray("Nonce"), seed, Pack.longToLittleEndian(Thread.currentThread().getId()), Pack.longToLittleEndian(System.currentTimeMillis())); } + + private static class HybridSecureRandom + extends SecureRandom + { + private final AtomicBoolean seedAvailable = new AtomicBoolean(false); + private final AtomicInteger samples = new AtomicInteger(0); + private final SecureRandom baseRandom = createInitialEntropySource(); + private final SP800SecureRandom drbg; + + HybridSecureRandom() + { + drbg = new SP800SecureRandomBuilder(new EntropySourceProvider() + { + public EntropySource get(final int bitsRequired) + { + return new SignallingEntropySource(bitsRequired); + } + }) + .setPersonalizationString(Strings.toByteArray("Bouncy Castle Hybrid Entropy Source")) + .buildHMAC(new HMac(new SHA512Digest()), baseRandom.generateSeed(32), false); // 32 byte nonce + } + + public byte[] generateSeed(int numBytes) + { + byte[] data = new byte[numBytes]; + + // after 20 samples we'll start to check if there is new seed material. + if (samples.getAndIncrement() > 20) + { + if (seedAvailable.getAndSet(false)) + { + samples.set(0); + drbg.reseed(null); + } + } + + drbg.nextBytes(data); + + return data; + } + + private class SignallingEntropySource + implements EntropySource + { + private final int byteLength; + private final AtomicReference entropy = new AtomicReference(); + private final AtomicBoolean scheduled = new AtomicBoolean(false); + + SignallingEntropySource(int bitsRequired) + { + this.byteLength = (bitsRequired + 7) / 8; + } + + public boolean isPredictionResistant() + { + return true; + } + + public byte[] getEntropy() + { + byte[] seed = (byte[])entropy.getAndSet(null); + + if (seed == null || seed.length != byteLength) + { + seed = baseRandom.generateSeed(byteLength); + } + else + { + scheduled.set(false); + } + + if (!scheduled.getAndSet(true)) + { + new Thread(new EntropyGatherer(byteLength)).start(); + } + + return seed; + } + + public int entropySize() + { + return byteLength * 8; + } + + private class EntropyGatherer + implements Runnable + { + private final int numBytes; + + EntropyGatherer(int numBytes) + { + this.numBytes = numBytes; + } + + public void run() + { + entropy.set(baseRandom.generateSeed(numBytes)); + seedAvailable.set(true); + } + } + } + } }