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.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import net.i2p.I2PAppContext; import net.i2p.crypto.EntropyHarvester; /** * Singleton for whatever PRNG i2p uses. * * @author jrandom */ public class RandomSource extends SecureRandom implements EntropyHarvester { private static final long serialVersionUID = 1L; private final EntropyHarvester _entropyHarvester; protected transient final I2PAppContext _context; /** * Deprecated - do not instantiate this directly, as you won't get the * good one (Fortuna). Use getInstance() or * I2PAppContext.getGlobalContext().random() to get the FortunaRandomSource * instance. */ public RandomSource(I2PAppContext context) { super(); _context = context; // when we replace to have hooks for fortuna (etc), replace with // a factory (or just a factory method) _entropyHarvester = this; } /** * Singleton for whatever PRNG i2p uses. * Same as I2PAppContext.getGlobalContext().random(); * use context.random() if you have a context already. * @return I2PAppContext.getGlobalContext().random() */ public static RandomSource getInstance() { return I2PAppContext.getGlobalContext().random(); } /** * 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. * 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. * * This code unused, see FortunaRandomSource override */ @Override public int nextInt(int n) { if (n == 0) return 0; int val = super.nextInt(n); if (val < 0) val = 0 - val; if (val >= n) val = val % n; return val; } /** * Like the modified nextInt, nextLong(n) returns a random number from 0 through n, * including 0, excluding n. * * This code unused, see FortunaRandomSource override */ public long nextLong(long n) { long v = super.nextLong(); if (v < 0) v = 0 - v; if (v >= n) v = v % n; return v; } /** * Not part of java.util.SecureRandom, but added since Fortuna supports it. * * This code unused, see FortunaRandomSource override * * @since 0.8.12 */ public void nextBytes(byte buf[], int offset, int length) { // inefficient, just in case anybody actually instantiates this if (offset == 0 && buf.length == length) { nextBytes(buf); } else { byte[] tmp = new byte[length]; nextBytes(tmp); System.arraycopy(tmp, 0, buf, offset, length); } } /** * override as synchronized, for those JVMs that don't always pull via * nextBytes (cough ibm) @Override public boolean nextBoolean() { return super.nextBoolean(); } @Override public void nextBytes(byte buf[]) { super.nextBytes(buf); } @Override public double nextDouble() { return super.nextDouble(); } @Override public float nextFloat() { return super.nextFloat(); } @Override public double nextGaussian() { return super.nextGaussian(); } @Override public int nextInt() { return super.nextInt(); } @Override public long nextLong() { return super.nextLong(); } *****/ /** */ 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()); } } /** * May block up to 10 seconds */ 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(I2PAppContext.getGlobalContext().getConfigDir(), SEEDFILE); FileOutputStream fos = null; try { fos = new SecureFileOutputStream(f); fos.write(buf); } catch (IOException ioe) { // ignore } finally { if (fos != null) try { fos.close(); } catch (IOException ioe) {} } } /** * May block up to 10 seconds */ public final boolean initSeed(byte buf[]) { boolean ok = false; final byte[] tbuf = new byte[buf.length]; Thread t = new I2PThread(new SecureRandomInit(tbuf), "SecureRandomInit", true); t.start(); try { t.join(10*1000); synchronized(tbuf) { for (int i = 0; i < tbuf.length; i++) { if (tbuf[i] != 0) { ok = true; break; } } if (ok) System.arraycopy(tbuf, 0, buf, 0, buf.length); // See FortunaRandomSource constructor for fallback //else // System.out.println("INFO: SecureRandom init failed or took too long"); } } catch (InterruptedException ie) {} // why urandom? because /dev/random blocks if (!SystemVersion.isWindows()) ok = seedFromFile(new File("/dev/urandom"), buf) || ok; // we merge (XOR) in the data from /dev/urandom with our own seedfile File localFile = new File(_context.getConfigDir(), SEEDFILE); ok = seedFromFile(localFile, buf) || ok; return ok; } /** * Thread to prevent hanging on init, * presumably due to /dev/random blocking, * which is common in VMs. * * @since 0.9.18 */ private static class SecureRandomInit implements Runnable { private final byte[] buf; private static final int SZ = 64; public SecureRandomInit(byte[] buf) { this.buf = buf; } public void run() { byte[] buf2 = new byte[SZ]; // do this 64 bytes at a time, so if system is low on entropy we will // hopefully get something before the timeout try { SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); for (int i = 0; i < buf.length; i += SZ) { sr.nextBytes(buf2); synchronized(buf) { System.arraycopy(buf2, 0, buf, i, Math.min(SZ, buf.length - i)); } } } catch (NoSuchAlgorithmException e) {} } } /** * XORs the seed into buf * * @param f absolute path * @return success */ private static final boolean seedFromFile(File f, byte buf[]) { 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(); } } ****/ }