package net.i2p.crypto; /* * free (adj.): unencumbered; not under the control of others * Written by jrandom in 2003 and released into the public domain * with no warranty of any kind, either expressed or implied. * It probably won't make your computer catch on fire, or eat * your children, but it might. Use at your own risk. * */ import java.math.BigInteger; import java.util.concurrent.LinkedBlockingQueue; import net.i2p.I2PAppContext; import net.i2p.util.I2PThread; import net.i2p.util.NativeBigInteger; import net.i2p.util.SystemVersion; /** * Precalculate the Y and K for ElGamal encryption operations. * * This class precalcs a set of values on its own thread, using those transparently * when a new instance is created. By default, the minimum threshold for creating * new values for the pool is 20, and the max pool size is 50. Whenever the pool has * less than the minimum, it fills it up again to the max. There is a delay after * each precalculation so that the CPU isn't hosed during startup. * These three parameters are controlled by java environmental variables and * can be adjusted via: * -Dcrypto.yk.precalc.min=40 -Dcrypto.yk.precalc.max=100 -Dcrypto.yk.precalc.delay=60000 * * (delay is milliseconds) * * To disable precalculation, set min to 0 * * @author jrandom */ final class YKGenerator { //private final static Log _log = new Log(YKGenerator.class); private final int MIN_NUM_BUILDERS; private final int MAX_NUM_BUILDERS; private final int CALC_DELAY; private final LinkedBlockingQueue<BigInteger[]> _values; private Thread _precalcThread; private final I2PAppContext ctx; private volatile boolean _isRunning; public final static String PROP_YK_PRECALC_MIN = "crypto.yk.precalc.min"; public final static String PROP_YK_PRECALC_MAX = "crypto.yk.precalc.max"; public final static String PROP_YK_PRECALC_DELAY = "crypto.yk.precalc.delay"; public final static int DEFAULT_YK_PRECALC_MIN = 20; public final static int DEFAULT_YK_PRECALC_MAX = 50; public final static int DEFAULT_YK_PRECALC_DELAY = 200; /** * Caller must also call start() to start the background precalc thread. * Unit tests will still work without calling start(). */ public YKGenerator(I2PAppContext context) { ctx = context; // add to the defaults for every 128MB of RAM, up to 1GB long maxMemory = SystemVersion.getMaxMemory(); int factor = (int) Math.max(1l, Math.min(8l, 1 + (maxMemory / (128*1024*1024l)))); int defaultMin = DEFAULT_YK_PRECALC_MIN * factor; int defaultMax = DEFAULT_YK_PRECALC_MAX * factor; MIN_NUM_BUILDERS = ctx.getProperty(PROP_YK_PRECALC_MIN, defaultMin); MAX_NUM_BUILDERS = ctx.getProperty(PROP_YK_PRECALC_MAX, defaultMax); CALC_DELAY = ctx.getProperty(PROP_YK_PRECALC_DELAY, DEFAULT_YK_PRECALC_DELAY); _values = new LinkedBlockingQueue<BigInteger[]>(MAX_NUM_BUILDERS); //if (_log.shouldLog(Log.DEBUG)) // _log.debug("ElGamal YK Precalc (minimum: " + MIN_NUM_BUILDERS + " max: " + MAX_NUM_BUILDERS + ", delay: " // + CALC_DELAY + ")"); ctx.statManager().createRateStat("crypto.YKUsed", "Need a YK from the queue", "Encryption", new long[] { 60*60*1000 }); ctx.statManager().createRateStat("crypto.YKEmpty", "YK queue empty", "Encryption", new long[] { 60*60*1000 }); } /** * Start the background precalc thread. * Must be called for normal operation. * If not called, all generation happens in the foreground. * Not required for unit tests. * * @since 0.9.14 */ public synchronized void start() { if (_isRunning) return; _precalcThread = new I2PThread(new YKPrecalcRunner(MIN_NUM_BUILDERS, MAX_NUM_BUILDERS), "YK Precalc", true); _precalcThread.setPriority(Thread.NORM_PRIORITY - 2); _isRunning = true; _precalcThread.start(); } /** * Stop the background precalc thread. * Can be restarted. * Not required for unit tests. * * @since 0.8.8 */ public synchronized void shutdown() { _isRunning = false; if (_precalcThread != null) _precalcThread.interrupt(); _values.clear(); } private final int getSize() { return _values.size(); } /** @return true if successful, false if full */ private final boolean addValues(BigInteger yk[]) { return _values.offer(yk); } /** @return rv[0] = Y; rv[1] = K */ public BigInteger[] getNextYK() { ctx.statManager().addRateData("crypto.YKUsed", 1); BigInteger[] rv = _values.poll(); if (rv != null) return rv; ctx.statManager().addRateData("crypto.YKEmpty", 1); return generateYK(); } private final static BigInteger _two = new NativeBigInteger(1, new byte[] { 0x02}); /** @return rv[0] = Y; rv[1] = K */ private final BigInteger[] generateYK() { NativeBigInteger k = null; BigInteger y = null; //long t0 = 0; //long t1 = 0; while (k == null) { //t0 = Clock.getInstance().now(); k = new NativeBigInteger(ctx.keyGenerator().getElGamalExponentSize(), ctx.random()); //t1 = Clock.getInstance().now(); if (BigInteger.ZERO.compareTo(k) == 0) { k = null; continue; } BigInteger kPlus2 = k.add(_two); if (kPlus2.compareTo(CryptoConstants.elgp) > 0) k = null; } //long t2 = Clock.getInstance().now(); y = CryptoConstants.elgg.modPow(k, CryptoConstants.elgp); BigInteger yk[] = new BigInteger[2]; yk[0] = y; yk[1] = k; //long diff = t2 - t0; //if (diff > 1000) { // if (_log.shouldLog(Log.WARN)) _log.warn("Took too long to generate YK value for ElGamal (" + diff + "ms)"); //} return yk; } /**** private static final int RUNS = 500; public static void main(String args[]) { // warmup crypto ctx.random().nextInt(); System.out.println("Begin YK generator speed test"); long startNeg = Clock.getInstance().now(); for (int i = 0; i < RUNS; i++) { getNextYK(); } long endNeg = Clock.getInstance().now(); long negTime = endNeg - startNeg; // 14 ms each on a 2008 netbook (with jbigi) System.out.println("YK fetch time for " + RUNS + " runs: " + negTime + " @ " + (negTime / RUNS) + "ms each"); } ****/ /** the thread */ private class YKPrecalcRunner implements Runnable { private final int _minSize; private final int _maxSize; /** check every 30 seconds whether we have less than the minimum */ private long _checkDelay = 30 * 1000; private YKPrecalcRunner(int minSize, int maxSize) { _minSize = minSize; _maxSize = maxSize; } public void run() { while (_isRunning) { //long start = Clock.getInstance().now(); int startSize = getSize(); // Adjust delay if (startSize <= (_minSize * 2 / 3) && _checkDelay > 1000) _checkDelay -= 1000; else if (startSize > (_minSize * 3 / 2) && _checkDelay < 60*1000) _checkDelay += 1000; if (startSize < _minSize) { // fill all the way up, do the check here so we don't // throw away one when full in addValues() while (getSize() < _maxSize && _isRunning) { //long begin = Clock.getInstance().now(); if (!addValues(generateYK())) break; //long end = Clock.getInstance().now(); //if (_log.shouldLog(Log.DEBUG)) _log.debug("Precalculated YK value in " + (end - begin) + "ms"); // for some relief... try { Thread.sleep(CALC_DELAY); } catch (InterruptedException ie) { // nop } } } //long end = Clock.getInstance().now(); //int numCalc = curSize - startSize; //if (numCalc > 0) { // if (_log.shouldLog(Log.DEBUG)) // _log.debug("Precalced " + numCalc + " to " + curSize + " in " // + (end - start - CALC_DELAY * numCalc) + "ms (not counting " // + (CALC_DELAY * numCalc) + "ms relief). now sleeping"); //} if (!_isRunning) break; try { Thread.sleep(_checkDelay); } catch (InterruptedException ie) { // nop } } } } }