2005-10-22 jrandom

* Integrated GNU-Crypto's Fortuna PRNG, seeding it off /dev/urandom and
      ./prngseed.rnd (if they exist), and reseeding it with data out of
      various crypto operations (unused bits in a DH exchange, intermediary
      bits in a DSA signature generation, extra bits in an ElGamal decrypt).
      The Fortuna implementation under gnu.crypto.prng has been modified to
      use BouncyCastle's SHA256 and Cryptix's AES (since those are the ones
      I2P uses), and the resulting gnu.crypto.prng.* are therefor available
      under GPL+Classpath's linking exception (~= LGPL).  I2P's SecureRandom
      wrapper around it is, of course, public domain.
This commit is contained in:
jrandom
2005-10-22 18:06:02 +00:00
committed by zzz
parent 3fbc6f41af
commit c7b9525d2c
15 changed files with 1283 additions and 15 deletions

View File

@@ -26,6 +26,7 @@ import net.i2p.util.Clock;
import net.i2p.util.LogManager;
import net.i2p.util.RandomSource;
import net.i2p.util.PooledRandomSource;
import net.i2p.util.FortunaRandomSource;
/**
* <p>Provide a base scope for accessing singletons that I2P exposes. Rather than
@@ -456,7 +457,9 @@ public class I2PAppContext {
private void initializeRandom() {
synchronized (this) {
if (_random == null) {
if ("true".equals(getProperty("i2p.weakPRNG", "false")))
if (true)
_random = new FortunaRandomSource(this);
else if ("true".equals(getProperty("i2p.weakPRNG", "false")))
_random = new DummyPooledRandomSource(this);
else
_random = new PooledRandomSource(this);
@@ -464,4 +467,4 @@ public class I2PAppContext {
_randomInitialized = true;
}
}
}
}

View File

@@ -10,7 +10,7 @@ import java.util.List;
* of code)
*
*/
final class CryptixAESKeyCache {
public final class CryptixAESKeyCache {
private List _availableKeys;
private static final int KEYSIZE = 32; // 256bit AES
@@ -48,7 +48,7 @@ final class CryptixAESKeyCache {
}
}
private static final KeyCacheEntry createNew() {
public static final KeyCacheEntry createNew() {
KeyCacheEntry e = new KeyCacheEntry();
e.Ke = new int[ROUNDS + 1][BC]; // encryption round keys
e.Kd = new int[ROUNDS + 1][BC]; // decryption round keys

View File

@@ -130,9 +130,13 @@ public class DSAEngine {
Signature sig = new Signature();
BigInteger k;
boolean ok = false;
do {
k = new BigInteger(160, _context.random());
} while (k.compareTo(CryptoConstants.dsaq) != 1);
ok = k.compareTo(CryptoConstants.dsaq) != 1;
ok = ok && !k.equals(BigInteger.ZERO);
//System.out.println("K picked (ok? " + ok + "): " + k.bitLength() + ": " + k.toString());
} while (!ok);
BigInteger r = CryptoConstants.dsag.modPow(k, CryptoConstants.dsap).mod(CryptoConstants.dsaq);
BigInteger kinv = k.modInverse(CryptoConstants.dsaq);
@@ -145,6 +149,9 @@ public class DSAEngine {
byte[] sbytes = s.toByteArray();
byte[] out = new byte[40];
// (q^random)%p is computationally random
_context.random().harvester().feedEntropy("DSA.sign", rbytes, 0, rbytes.length);
if (rbytes.length == 20) {
for (int i = 0; i < 20; i++) {
out[i] = rbytes[i];
@@ -203,4 +210,19 @@ public class DSAEngine {
byte digested[] = h.digest();
return new Hash(digested);
}
}
public static void main(String args[]) {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
byte data[] = new byte[4096];
ctx.random().nextBytes(data);
Object keys[] = ctx.keyGenerator().generateSigningKeypair();
try {
for (int i = 0; i < 10; i++) {
Signature sig = ctx.dsa().sign(data, (SigningPrivateKey)keys[1]);
boolean ok = ctx.dsa().verifySignature(sig, data, (SigningPublicKey)keys[0]);
System.out.println("OK: " + ok);
}
} catch (Exception e) { e.printStackTrace(); }
ctx.random().saveSeed();
}
}

View File

@@ -0,0 +1,178 @@
package net.i2p.util;
/*
* 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.security.SecureRandom;
import net.i2p.I2PAppContext;
import net.i2p.crypto.EntropyHarvester;
import gnu.crypto.prng.Fortuna;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
/**
* Wrapper around GNU-Crypto's Fortuna PRNG. This seeds from /dev/urandom and
* ./prngseed.rnd on startup (if they exist), writing a new seed to ./prngseed.rnd
* on an explicit call to saveSeed().
*
*/
public class FortunaRandomSource extends RandomSource implements EntropyHarvester {
private Fortuna _fortuna;
private double _nextGaussian;
private boolean _haveNextGaussian;
public FortunaRandomSource(I2PAppContext context) {
super(context);
_fortuna = new Fortuna();
byte seed[] = new byte[1024];
if (initSeed(seed)) {
_fortuna.seed(seed);
} else {
SecureRandom sr = new SecureRandom();
sr.nextBytes(seed);
_fortuna.seed(seed);
}
// kickstart it
_fortuna.nextBytes(seed);
_haveNextGaussian = false;
}
public synchronized void setSeed(byte buf[]) {
_fortuna.addRandomBytes(buf);
}
/**
* According to the java docs (http://java.sun.com/j2se/1.4.1/docs/api/java/util/Random.html#nextInt(int))
* nextInt(n) should return a number between 0 and n (including 0 and excluding n). However, their pseudocode,
* as well as sun's, kaffe's, and classpath's implementation INCLUDES NEGATIVE VALUES.
* WTF. Ok, so we're going to have it return between 0 and n (including 0, excluding n), since
* thats what it has been used for.
*
*/
public int nextInt(int n) {
if (n == 0) return 0;
int rv = signedNextInt(n);
if (rv < 0)
rv = 0 - rv;
rv %= n;
return rv;
}
public int nextInt() { return signedNextInt(Integer.MAX_VALUE); }
/**
* Implementation from Sun's java.util.Random javadocs
*/
private int signedNextInt(int n) {
if (n<=0)
throw new IllegalArgumentException("n must be positive");
if ((n & -n) == n) // i.e., n is a power of 2
return (int)((n * (long)nextBits(31)) >> 31);
int bits, val;
do {
bits = nextBits(31);
val = bits % n;
} while(bits - val + (n-1) < 0);
return val;
}
/**
* Like the modified nextInt, nextLong(n) returns a random number from 0 through n,
* including 0, excluding n.
*/
public long nextLong(long n) {
if (n == 0) return 0;
long rv = signedNextLong(n);
if (rv < 0)
rv = 0 - rv;
rv %= n;
return rv;
}
public long nextLong() { return signedNextLong(Long.MAX_VALUE); }
/**
* Implementation from Sun's java.util.Random javadocs
*/
private long signedNextLong(long n) {
return ((long)nextBits(32) << 32) + nextBits(32);
}
public synchronized boolean nextBoolean() {
// wasteful, might be worth caching the boolean byte later
byte val = _fortuna.nextByte();
return ((val & 0x01) == 1);
}
public synchronized void nextBytes(byte buf[]) {
_fortuna.nextBytes(buf);
}
/**
* Implementation from sun's java.util.Random javadocs
*/
public double nextDouble() {
return (((long)nextBits(26) << 27) + nextBits(27)) / (double)(1L << 53);
}
/**
* Implementation from sun's java.util.Random javadocs
*/
public float nextFloat() {
return nextBits(24) / ((float)(1 << 24));
}
/**
* Implementation from sun's java.util.Random javadocs
*/
public synchronized double nextGaussian() {
if (_haveNextGaussian) {
_haveNextGaussian = false;
return _nextGaussian;
} else {
double v1, v2, s;
do {
v1 = 2 * nextDouble() - 1; // between -1.0 and 1.0
v2 = 2 * nextDouble() - 1; // between -1.0 and 1.0
s = v1 * v1 + v2 * v2;
} while (s >= 1 || s == 0);
double multiplier = Math.sqrt(-2 * Math.log(s)/s);
_nextGaussian = v2 * multiplier;
_haveNextGaussian = true;
return v1 * multiplier;
}
}
/**
* Pull the next numBits of random data off the fortuna instance (returning -2^numBits-1
* through 2^numBits-1
*/
protected synchronized int nextBits(int numBits) {
int rv = 0;
int bytes = (numBits + 7) / 8;
for (int i = 0; i < bytes; i++)
rv += ((_fortuna.nextByte() & 0xFF) << i*8);
return rv;
}
public EntropyHarvester harvester() { return this; }
/** reseed the fortuna */
public synchronized void feedEntropy(String source, long data, int bitoffset, int bits) {
_fortuna.addRandomByte((byte)(data & 0xFF));
}
/** reseed the fortuna */
public synchronized void feedEntropy(String source, byte[] data, int offset, int len) {
_fortuna.addRandomBytes(data, offset, len);
}
}

View File

@@ -11,6 +11,7 @@ package net.i2p.util;
import net.i2p.I2PAppContext;
import net.i2p.crypto.EntropyHarvester;
import net.i2p.data.Base64;
/**
* Maintain a set of PRNGs to feed the apps
@@ -48,6 +49,9 @@ public class PooledRandomSource extends RandomSource {
totalSize = -1;
}
}
byte buf[] = new byte[1024];
initSeed(buf);
for (int i = 0; i < POOL_SIZE; i++) {
if (totalSize < 0)
_pool[i] = new BufferedRandomSource(context);
@@ -55,8 +59,14 @@ public class PooledRandomSource extends RandomSource {
_pool[i] = new BufferedRandomSource(context, (totalSize*1024) / POOL_SIZE);
else
_pool[i] = new RandomSource(context);
_pool[i].nextBoolean();
_pool[i].setSeed(buf);
if (i > 0) {
_pool[i-1].nextBytes(buf);
_pool[i].setSeed(buf);
}
}
_pool[0].nextBytes(buf);
System.out.println("seeded and initialized: " + Base64.encode(buf));
_nextPool = 0;
}
@@ -174,7 +184,11 @@ public class PooledRandomSource extends RandomSource {
}
public static void main(String args[]) {
PooledRandomSource prng = new PooledRandomSource(I2PAppContext.getGlobalContext());
//PooledRandomSource prng = new PooledRandomSource(I2PAppContext.getGlobalContext());
long start = System.currentTimeMillis();
RandomSource prng = I2PAppContext.getGlobalContext().random();
long created = System.currentTimeMillis();
System.out.println("prng type: " + prng.getClass().getName());
int size = 8*1024*1024;
try {
java.io.FileOutputStream out = new java.io.FileOutputStream("random.file");
@@ -183,6 +197,8 @@ public class PooledRandomSource extends RandomSource {
}
out.close();
} catch (Exception e) { e.printStackTrace(); }
System.out.println("Written to random.file");
long done = System.currentTimeMillis();
System.out.println("Written to random.file: create took " + (created-start) + ", generate took " + (done-created));
prng.saveSeed();
}
}

View File

@@ -13,13 +13,19 @@ import java.security.SecureRandom;
import net.i2p.I2PAppContext;
import net.i2p.crypto.EntropyHarvester;
import net.i2p.data.Base64;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Singleton for whatever PRNG i2p uses.
*
* @author jrandom
*/
public class RandomSource extends SecureRandom {
public class RandomSource extends SecureRandom implements EntropyHarvester {
private Log _log;
private EntropyHarvester _entropyHarvester;
protected I2PAppContext _context;
@@ -30,7 +36,7 @@ public class RandomSource extends SecureRandom {
_log = context.logManager().getLog(RandomSource.class);
// when we replace to have hooks for fortuna (etc), replace with
// a factory (or just a factory method)
_entropyHarvester = new DummyEntropyHarvester();
_entropyHarvester = this;
}
public static RandomSource getInstance() {
return I2PAppContext.getGlobalContext().random();
@@ -101,9 +107,105 @@ public class RandomSource extends SecureRandom {
public EntropyHarvester harvester() { return _entropyHarvester; }
public void feedEntropy(String source, long data, int bitoffset, int bits) {
if (bitoffset == 0)
setSeed(data);
}
public void feedEntropy(String source, byte[] data, int offset, int len) {
if ( (offset == 0) && (len == data.length) ) {
setSeed(data);
} else {
setSeed(_context.sha().calculateHash(data, offset, len).getData());
}
}
public void loadSeed() {
byte buf[] = new byte[1024];
if (initSeed(buf))
setSeed(buf);
}
public void saveSeed() {
byte buf[] = new byte[1024];
nextBytes(buf);
writeSeed(buf);
}
private static final String SEEDFILE = "prngseed.rnd";
public static final void writeSeed(byte buf[]) {
File f = new File(SEEDFILE);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(f);
fos.write(buf);
} catch (IOException ioe) {
// ignore
} finally {
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
}
}
public final boolean initSeed(byte buf[]) {
// why urandom? because /dev/random blocks, and there are arguments
// suggesting such blockages are largely meaningless
boolean ok = seedFromFile("/dev/urandom", buf);
// we merge (XOR) in the data from /dev/urandom with our own seedfile
ok = seedFromFile("prngseed.rnd", buf) || ok;
return ok;
}
private static final boolean seedFromFile(String filename, byte buf[]) {
File f = new File(filename);
if (f.exists()) {
FileInputStream fis = null;
try {
fis = new FileInputStream(f);
int read = 0;
byte tbuf[] = new byte[buf.length];
while (read < buf.length) {
int curRead = fis.read(tbuf, read, tbuf.length - read);
if (curRead < 0)
break;
read += curRead;
}
for (int i = 0; i < read; i++)
buf[i] ^= tbuf[i];
return true;
} catch (IOException ioe) {
// ignore
} finally {
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
}
}
return false;
}
public static void main(String args[]) {
for (int j = 0; j < 2; j++) {
RandomSource rs = new RandomSource(I2PAppContext.getGlobalContext());
byte buf[] = new byte[1024];
boolean seeded = rs.initSeed(buf);
System.out.println("PRNG class hierarchy: ");
Class c = rs.getClass();
while (c != null) {
System.out.println("\t" + c.getName());
c = c.getSuperclass();
}
System.out.println("Provider: \n" + rs.getProvider());
if (seeded) {
System.out.println("Initialized seed: " + Base64.encode(buf));
rs.setSeed(buf);
}
for (int i = 0; i < 64; i++) rs.nextBytes(buf);
rs.saveSeed();
}
}
// noop
private static class DummyEntropyHarvester implements EntropyHarvester {
public void feedEntropy(String source, long data, int bitoffset, int bits) {}
public void feedEntropy(String source, byte[] data, int offset, int len) {}
}
}
}