All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bouncycastle.jcajce.provider.drbg.DRBG Maven / Gradle / Ivy

There is a newer version: 1.2.2.1-jre17
Show newest version
package org.bouncycastle.jcajce.provider.drbg;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
import java.security.Security;
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.symmetric.util.ClassUtil;
import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Pack;
import org.bouncycastle.util.Properties;
import org.bouncycastle.util.Strings;

/**
 * DRBG Configuration
*

org.bouncycastle.drbg.gather_pause_secs - is to stop the entropy collection thread from grabbing all * available entropy on the system. The original motivation for the hybrid infrastructure was virtual machines * sometimes produce very few bits of entropy a second, the original approach (which "worked" at least for BC) was * to just read on the second thread and allow things to progress around it, but it did tend to hog the system * if other processes were using /dev/random. By default the thread will pause for 5 seconds between 64 bit reads, * increasing this time will reduce the demands on the system entropy pool. Ideally the pause will be set to large * enough to allow everyone to work together, but small enough to ensure the provider's DRBG is being regularly * reseeded. *

*

org.bouncycastle.drbg.entropysource - is the class name for an implementation of EntropySourceProvider. * For example, one could be provided which just reads directly from /dev/random and the extra infrastructure used here * could be avoided.

*/ 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"}, }; // 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(Object[] initialEntropySourceAndSpi) { 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() { boolean hasGetInstanceStrong = AccessController.doPrivileged(new PrivilegedAction() { public Boolean run() { try { Class def = SecureRandom.class; return def.getMethod("getInstanceStrong") != null; } catch (Exception e) { return false; } } }); if (hasGetInstanceStrong) { return AccessController.doPrivileged(new PrivilegedAction() { public SecureRandom run() { try { return (SecureRandom)SecureRandom.class.getMethod("getInstanceStrong").invoke(null); } catch (Exception e) { return createCoreSecureRandom(); } } }); } else { return createCoreSecureRandom(); } } private static SecureRandom createCoreSecureRandom() { if (Security.getProperty("securerandom.source") == null) { return new CoreSecureRandom(findSource()); } else { try { String source = Security.getProperty("securerandom.source"); return new URLSeededSecureRandom(new URL(source)); } catch (Exception e) { return new CoreSecureRandom(findSource()); } } } private static EntropySourceProvider createEntropySource() { final String sourceClass = Properties.getPropertyValue("org.bouncycastle.drbg.entropysource"); return AccessController.doPrivileged(new PrivilegedAction() { public EntropySourceProvider run() { try { Class clazz = ClassUtil.loadClass(DRBG.class, 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 (Properties.getPropertyValue("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 HybridRandomProvider extends Provider { protected HybridRandomProvider() { super("BCHEP", 1.0, "Bouncy Castle Hybrid Entropy Provider"); } } private static class URLSeededSecureRandom extends SecureRandom { private final InputStream seedStream; URLSeededSecureRandom(final URL url) { super(null, new HybridRandomProvider()); this.seedStream = AccessController.doPrivileged(new PrivilegedAction() { public InputStream run() { try { return url.openStream(); } catch (IOException e) { throw new IllegalStateException("unable to open random source"); } } }); } public void setSeed(byte[] seed) { // ignore } public void setSeed(long seed) { // ignore } public byte[] generateSeed(int numBytes) { synchronized (this) { byte[] data = new byte[numBytes]; int off = 0; int len; while (off != data.length && (len = privilegedRead(data, off, data.length - off)) > -1) { off += len; } if (off != data.length) { throw new InternalError("unable to fully read random source"); } return data; } } private int privilegedRead(final byte[] data, final int off, final int len) { return AccessController.doPrivileged(new PrivilegedAction() { public Integer run() { try { return seedStream.read(data, off, len); } catch (IOException e) { throw new InternalError("unable to read random source"); } } }); } } 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() { super(null, new HybridRandomProvider()); 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 void setSeed(byte[] seed) { if (drbg != null) { drbg.setSeed(seed); } } public void setSeed(long seed) { if (drbg != null) { drbg.setSeed(seed); } } 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((byte[])null); // need for Java 1.9 } } 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)) { // don't try to be clever here - things change in Java 11! Thread gatherer = new Thread(new EntropyGatherer(byteLength)); gatherer.setDaemon(true); gatherer.start(); } return seed; } public int entropySize() { return byteLength * 8; } private class EntropyGatherer implements Runnable { private final int numBytes; EntropyGatherer(int numBytes) { this.numBytes = numBytes; } private void sleep(long ms) { try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } public void run() { long ms; String pause = Properties.getPropertyValue("org.bouncycastle.drbg.gather_pause_secs"); if (pause != null) { try { ms = Long.parseLong(pause) * 1000; } catch (Exception e) { ms = 5000; } } else { ms = 5000; } byte[] seed = new byte[numBytes]; for (int i = 0; i < byteLength / 8; i++) { // we need to be mindful that we may not be the only thread/process looking for entropy sleep(ms); byte[] rn = baseRandom.generateSeed(8); System.arraycopy(rn, 0, seed, i * 8, rn.length); } int extra = byteLength - ((byteLength / 8) * 8); if (extra != 0) { sleep(ms); byte[] rn = baseRandom.generateSeed(extra); System.arraycopy(rn, 0, seed, seed.length - rn.length, rn.length); } entropy.set(seed); seedAvailable.set(true); } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy