| package org.bouncycastle.jcajce.provider.drbg; |
| |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| 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; |
| import org.bouncycastle.util.Arrays; |
| import org.bouncycastle.util.Pack; |
| import org.bouncycastle.util.Strings; |
| |
| public class DRBG |
| { |
| private static final String PREFIX = DRBG.class.getName(); |
| |
| // {"Provider class name","SecureRandomSpi class name"} |
| private static final String[][] initialEntropySourceNames = new String[][] |
| { |
| // Normal JVM |
| {"sun.security.provider.Sun", "sun.security.provider.SecureRandom"}, |
| // Apache harmony |
| {"org.apache.harmony.security.provider.crypto.CryptoProvider", "org.apache.harmony.security.provider.crypto.SHA1PRNG_SecureRandomImpl"}, |
| // Android. |
| {"com.android.org.conscrypt.OpenSSLProvider", "com.android.org.conscrypt.OpenSSLRandom"}, |
| {"org.conscrypt.OpenSSLProvider", "org.conscrypt.OpenSSLRandom"}, |
| }; |
| |
| private static final Object[] initialEntropySourceAndSpi = findSource(); |
| |
| // Cascade through providers looking for match. |
| private final static Object[] findSource() |
| { |
| for (int t = 0; t < initialEntropySourceNames.length; t++) |
| { |
| String[] pair = initialEntropySourceNames[t]; |
| try |
| { |
| Object[] r = new Object[]{Class.forName(pair[0]).newInstance(), Class.forName(pair[1]).newInstance()}; |
| |
| return r; |
| } |
| catch (Throwable ex) |
| { |
| continue; |
| } |
| } |
| |
| return null; |
| } |
| |
| private static class CoreSecureRandom |
| extends SecureRandom |
| { |
| CoreSecureRandom() |
| { |
| super((SecureRandomSpi)initialEntropySourceAndSpi[1], (Provider)initialEntropySourceAndSpi[0]); |
| } |
| } |
| |
| // unfortunately new SecureRandom() can cause a regress and it's the only reliable way of getting access |
| // to the JVM's seed generator. |
| private static SecureRandom createInitialEntropySource() |
| { |
| if (initialEntropySourceAndSpi != null) |
| { |
| return new CoreSecureRandom(); |
| } |
| else |
| { |
| return new SecureRandom(); // we're desperate, it's worth a try. |
| } |
| } |
| |
| private static EntropySourceProvider createEntropySource() |
| { |
| final String sourceClass = System.getProperty("org.bouncycastle.drbg.entropysource"); |
| |
| return AccessController.doPrivileged(new PrivilegedAction<EntropySourceProvider>() |
| { |
| public EntropySourceProvider run() |
| { |
| try |
| { |
| Class clazz = DRBG.class.getClassLoader().loadClass(sourceClass); |
| |
| return (EntropySourceProvider)clazz.newInstance(); |
| } |
| catch (Exception e) |
| { |
| throw new IllegalStateException("entropy source " + sourceClass + " not created: " + e.getMessage(), e); |
| } |
| } |
| }); |
| } |
| |
| private static SecureRandom createBaseRandom(boolean isPredictionResistant) |
| { |
| if (System.getProperty("org.bouncycastle.drbg.entropysource") != null) |
| { |
| EntropySourceProvider entropyProvider = createEntropySource(); |
| |
| EntropySource initSource = entropyProvider.get(16 * 8); |
| |
| byte[] personalisationString = isPredictionResistant ? generateDefaultPersonalizationString(initSource.getEntropy()) |
| : generateNonceIVPersonalizationString(initSource.getEntropy()); |
| |
| return new SP800SecureRandomBuilder(entropyProvider) |
| .setPersonalizationString(personalisationString) |
| .buildHash(new SHA512Digest(), Arrays.concatenate(initSource.getEntropy(), initSource.getEntropy()), isPredictionResistant); |
| } |
| else |
| { |
| 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(personalisationString) |
| .buildHash(new SHA512Digest(), randomSource.generateSeed(32), isPredictionResistant); |
| } |
| } |
| |
| public static class Default |
| extends SecureRandomSpi |
| { |
| private static final SecureRandom random = createBaseRandom(true); |
| |
| public Default() |
| { |
| } |
| |
| protected void engineSetSeed(byte[] bytes) |
| { |
| random.setSeed(bytes); |
| } |
| |
| protected void engineNextBytes(byte[] bytes) |
| { |
| random.nextBytes(bytes); |
| } |
| |
| protected byte[] engineGenerateSeed(int numBytes) |
| { |
| return random.generateSeed(numBytes); |
| } |
| } |
| |
| public static class NonceAndIV |
| extends SecureRandomSpi |
| { |
| private static final SecureRandom random = createBaseRandom(false); |
| |
| public NonceAndIV() |
| { |
| } |
| |
| protected void engineSetSeed(byte[] bytes) |
| { |
| random.setSeed(bytes); |
| } |
| |
| protected void engineNextBytes(byte[] bytes) |
| { |
| random.nextBytes(bytes); |
| } |
| |
| protected byte[] engineGenerateSeed(int numBytes) |
| { |
| return random.generateSeed(numBytes); |
| } |
| } |
| |
| public static class Mappings |
| extends AsymmetricAlgorithmProvider |
| { |
| public Mappings() |
| { |
| } |
| |
| public void configure(ConfigurableProvider provider) |
| { |
| provider.addAlgorithm("SecureRandom.DEFAULT", PREFIX + "$Default"); |
| provider.addAlgorithm("SecureRandom.NONCEANDIV", PREFIX + "$NonceAndIV"); |
| } |
| } |
| |
| private static byte[] generateDefaultPersonalizationString(byte[] seed) |
| { |
| return Arrays.concatenate(Strings.toByteArray("Default"), seed, |
| Pack.longToBigEndian(Thread.currentThread().getId()), Pack.longToBigEndian(System.currentTimeMillis())); |
| } |
| |
| 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); |
| } |
| } |
| } |
| } |
| } |