diff --git a/core/java/src/gnu/crypto/prng/BasePRNG.java b/core/java/src/gnu/crypto/prng/BasePRNG.java
new file mode 100644
index 0000000000000000000000000000000000000000..c8840a61dbbe7b988ac132a1807ac7161535c62f
--- /dev/null
+++ b/core/java/src/gnu/crypto/prng/BasePRNG.java
@@ -0,0 +1,183 @@
+package gnu.crypto.prng;
+
+// ----------------------------------------------------------------------------
+// Copyright (C) 2001, 2002, Free Software Foundation, Inc.
+//
+// This file is part of GNU Crypto.
+//
+// GNU Crypto is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2, or (at your option)
+// any later version.
+//
+// GNU Crypto is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING.  If not, write to the
+//
+//    Free Software Foundation Inc.,
+//    51 Franklin Street, Fifth Floor,
+//    Boston, MA 02110-1301
+//    USA
+//
+// Linking this library statically or dynamically with other modules is
+// making a combined work based on this library.  Thus, the terms and
+// conditions of the GNU General Public License cover the whole
+// combination.
+//
+// As a special exception, the copyright holders of this library give
+// you permission to link this library with independent modules to
+// produce an executable, regardless of the license terms of these
+// independent modules, and to copy and distribute the resulting
+// executable under terms of your choice, provided that you also meet,
+// for each linked independent module, the terms and conditions of the
+// license of that module.  An independent module is a module which is
+// not derived from or based on this library.  If you modify this
+// library, you may extend this exception to your version of the
+// library, but you are not obligated to do so.  If you do not wish to
+// do so, delete this exception statement from your version.
+// ----------------------------------------------------------------------------
+
+import java.util.Map;
+
+/**
+ * <p>An abstract class to facilitate implementing PRNG algorithms.</p>
+ *
+ * Modified slightly by jrandom for I2P (removing unneeded exceptions)
+ * @version $Revision: 1.12 $
+ */
+public abstract class BasePRNG implements IRandom {
+
+   // Constants and variables
+   // -------------------------------------------------------------------------
+
+   /** The canonical name prefix of the PRNG algorithm. */
+   protected String name;
+
+   /** Indicate if this instance has already been initialised or not. */
+   protected boolean initialised;
+
+   /** A temporary buffer to serve random bytes. */
+   protected byte[] buffer;
+
+   /** The index into buffer of where the next byte will come from. */
+   protected int ndx;
+
+   // Constructor(s)
+   // -------------------------------------------------------------------------
+
+   /**
+    * <p>Trivial constructor for use by concrete subclasses.</p>
+    *
+    * @param name the canonical name of this instance.
+    */
+   protected BasePRNG(String name) {
+      super();
+
+      this.name = name;
+      initialised = false;
+      buffer = new byte[0];
+   }
+
+   // Class methods
+   // -------------------------------------------------------------------------
+
+   // Instance methods
+   // -------------------------------------------------------------------------
+
+   // IRandom interface implementation ----------------------------------------
+
+   public String name() {
+      return name;
+   }
+
+   public void init(Map attributes) {
+      this.setup(attributes);
+
+      ndx = 0;
+      initialised = true;
+   }
+
+   public byte nextByte() throws IllegalStateException {//, LimitReachedException {
+      if (!initialised) {
+         throw new IllegalStateException();
+      }
+      return nextByteInternal();
+   }
+
+   public void nextBytes(byte[] out) throws IllegalStateException {//, LimitReachedException {
+      nextBytes(out, 0, out.length);
+   }
+
+   public void nextBytes(byte[] out, int offset, int length)
+   throws IllegalStateException //, LimitReachedException
+   {
+      if (!initialised)
+         throw new IllegalStateException("not initialized");
+
+      if (length == 0)
+         return;
+
+      if (offset < 0 || length < 0 || offset + length > out.length)
+         throw new ArrayIndexOutOfBoundsException("offset=" + offset + " length="
+                                                  + length + " limit=" + out.length);
+
+      if (ndx >= buffer.length) {
+         fillBlock();
+         ndx = 0;
+      }
+      int count = 0;
+      while (count < length) {
+         int amount = Math.min(buffer.length - ndx, length - count);
+         System.arraycopy(buffer, ndx, out, offset+count, amount);
+         count += amount;
+         ndx += amount;
+         if (ndx >= buffer.length) {
+            fillBlock();
+            ndx = 0;
+         }
+      }
+   }
+
+   public void addRandomByte(byte b) {
+      throw new UnsupportedOperationException("random state is non-modifiable");
+   }
+
+   public void addRandomBytes(byte[] buffer) {
+      addRandomBytes(buffer, 0, buffer.length);
+   }
+
+   public void addRandomBytes(byte[] buffer, int offset, int length) {
+      throw new UnsupportedOperationException("random state is non-modifiable");
+   }
+
+   // Instance methods
+   // -------------------------------------------------------------------------
+
+   public boolean isInitialised() {
+      return initialised;
+   }
+
+   private byte nextByteInternal() {//throws LimitReachedException {
+      if (ndx >= buffer.length) {
+         this.fillBlock();
+         ndx = 0;
+      }
+
+      return buffer[ndx++];
+   }
+
+   // abstract methods to implement by subclasses -----------------------------
+
+  public Object clone() throws CloneNotSupportedException
+  {
+    return super.clone();
+  }
+
+   public abstract void setup(Map attributes);
+
+   public abstract void fillBlock(); //throws LimitReachedException;
+}
diff --git a/core/java/src/gnu/crypto/prng/Fortuna.java b/core/java/src/gnu/crypto/prng/Fortuna.java
new file mode 100644
index 0000000000000000000000000000000000000000..0a48b4c486cc9ae064bcf8b24f35f145a4e5e964
--- /dev/null
+++ b/core/java/src/gnu/crypto/prng/Fortuna.java
@@ -0,0 +1,358 @@
+/* Fortuna.java -- The Fortuna PRNG.
+   Copyright (C) 2004  Free Software Foundation, Inc.
+
+This file is part of GNU Crypto.
+
+GNU Crypto is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2, or (at your option) any
+later version.
+
+GNU Crypto is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; see the file COPYING.  If not, write to the
+
+   Free Software Foundation Inc.,
+   51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301
+   USA
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.  */
+
+
+package gnu.crypto.prng;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+import java.security.InvalidKeyException;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.HashMap;
+
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import net.i2p.crypto.CryptixRijndael_Algorithm;
+import net.i2p.crypto.CryptixAESKeyCache;
+
+/**
+ * The Fortuna continuously-seeded pseudo-random number generator. This
+ * generator is composed of two major pieces: the entropy accumulator
+ * and the generator function. The former takes in random bits and
+ * incorporates them into the generator's state. The latter takes this
+ * base entropy and generates pseudo-random bits from it.
+ *
+ * <p>There are some things users of this class <em>must</em> be aware of:
+ *
+ * <dl>
+ * <dt>Adding Random Data</dt>
+ * <dd>This class does not do any polling of random sources, but rather
+ * provides an interface for adding random events. Applications that use
+ * this code <em>must</em> provide this mechanism. We use this design
+ * because an application writer who knows the system he is targeting
+ * is in a better position to judge what random data is available.</dd>
+ *
+ * <dt>Storing the Seed</dt>
+ * <dd>This class implements {@link Serializable} in such a way that it
+ * writes a 64 byte seed to the stream, and reads it back again when being
+ * deserialized. This is the extent of seed file management, however, and
+ * those using this class are encouraged to think deeply about when, how
+ * often, and where to store the seed.</dd>
+ * </dl>
+ *
+ * <p><b>References:</b></p>
+ *
+ * <ul>
+ * <li>Niels Ferguson and Bruce Schneier, <i>Practical Cryptography</i>,
+ * pp. 155--184. Wiley Publishing, Indianapolis. (2003 Niels Ferguson and
+ * Bruce Schneier). ISBN 0-471-22357-3.</li>
+ * </ul>
+ *
+ * Modified by jrandom for I2P to use Bouncycastle's SHA256, Cryptix's AES,
+ * to strip out some unnecessary dependencies and increase the buffer size.
+ *
+ */
+public class Fortuna extends BasePRNG
+  implements Serializable, RandomEventListener
+{
+
+  private static final long serialVersionUID = 0xFACADE;
+
+  private static final int SEED_FILE_SIZE = 64;
+  private static final int NUM_POOLS = 32;
+  private static final int MIN_POOL_SIZE = 64;
+  private final Generator generator;
+  private final SHA256Digest[] pools;
+  private long lastReseed;
+  private int pool;
+  private int pool0Count;
+  private int reseedCount;
+
+  public static final String SEED = "gnu.crypto.prng.fortuna.seed";
+
+  public Fortuna()
+  {
+    super("Fortuna i2p");
+    generator = new Generator();
+    pools = new SHA256Digest[NUM_POOLS];
+    for (int i = 0; i < NUM_POOLS; i++)
+      pools[i] = new SHA256Digest();
+    lastReseed = 0;
+    pool = 0;
+    pool0Count = 0;
+    buffer = new byte[4*1024*1024]; //256]; // larger buffer to reduce churn
+  }
+
+  public void seed(byte val[]) {
+      Map props = new HashMap(1);
+      props.put(SEED, (Object)val);
+      init(props);
+      fillBlock();
+  }
+  
+  public void setup(Map attributes)
+  {
+    lastReseed = 0;
+    reseedCount = 0;
+    pool = 0;
+    pool0Count = 0;
+    generator.init(attributes);
+  }
+
+  /** fillBlock is not thread safe, so will be locked anyway */
+  private byte fillBlockBuf[] = new byte[32];
+  public void fillBlock()
+  {
+    if (pool0Count >= MIN_POOL_SIZE
+        && System.currentTimeMillis() - lastReseed > 100)
+      {
+        reseedCount++;
+        //byte[] seed = new byte[0];
+        for (int i = 0; i < NUM_POOLS; i++)
+          {
+            if (reseedCount % (1 << i) == 0) {
+              byte buf[] = fillBlockBuf;//new byte[32];
+              pools[i].doFinal(buf, 0);
+              generator.addRandomBytes(buf);//pools[i].digest());
+            }
+          }
+        lastReseed = System.currentTimeMillis();
+      }
+    generator.nextBytes(buffer);
+  }
+
+  public void addRandomByte(byte b)
+  {
+    pools[pool].update(b);
+    if (pool == 0)
+      pool0Count++;
+    pool = (pool + 1) % NUM_POOLS;
+  }
+
+  public void addRandomBytes(byte[] buf, int offset, int length)
+  {
+    pools[pool].update(buf, offset, length);
+    if (pool == 0)
+      pool0Count += length;
+    pool = (pool + 1) % NUM_POOLS;
+  }
+
+  public void addRandomEvent(RandomEvent event)
+  {
+    if (event.getPoolNumber() < 0 || event.getPoolNumber() >= pools.length)
+      throw new IllegalArgumentException("pool number out of range: "
+                                         + event.getPoolNumber());
+    pools[event.getPoolNumber()].update(event.getSourceNumber());
+    pools[event.getPoolNumber()].update((byte) event.getData().length);
+    byte data[] = event.getData();
+    pools[event.getPoolNumber()].update(data, 0, data.length); //event.getData());
+    if (event.getPoolNumber() == 0)
+      pool0Count += event.getData().length;
+  }
+
+  // Reading and writing this object is equivalent to storing and retrieving
+  // the seed.
+
+  private void writeObject(ObjectOutputStream out) throws IOException
+  {
+    byte[] seed = new byte[SEED_FILE_SIZE];
+    generator.nextBytes(seed);
+    out.write(seed);
+  }
+
+  private void readObject(ObjectInputStream in) throws IOException
+  {
+    byte[] seed = new byte[SEED_FILE_SIZE];
+    in.readFully(seed);
+    generator.addRandomBytes(seed);
+  }
+
+  /**
+   * The Fortuna generator function. The generator is a PRNG in its own
+   * right; Fortuna itself is basically a wrapper around this generator
+   * that manages reseeding in a secure way.
+   */
+  public static class Generator extends BasePRNG implements Cloneable
+  {
+
+    private static final int LIMIT = 1 << 20;
+
+    private final SHA256Digest hash;
+    private final byte[] counter;
+    private final byte[] key;
+    /** current encryption key built from the keying material */
+    private Object cryptixKey;
+    private CryptixAESKeyCache.KeyCacheEntry cryptixKeyBuf;
+    private boolean seeded;
+
+    public Generator ()
+    {
+      super("Fortuna.generator.i2p");
+      this.hash = new SHA256Digest();
+      counter = new byte[16]; //cipher.defaultBlockSize()];
+      buffer = new byte[16]; //cipher.defaultBlockSize()];
+      int keysize = 32;
+      key = new byte[keysize];
+      cryptixKeyBuf = CryptixAESKeyCache.createNew();
+    }
+
+    public final byte nextByte()
+    {
+      byte[] b = new byte[1];
+      nextBytes(b, 0, 1);
+      return b[0];
+    }
+
+    public final void nextBytes(byte[] out, int offset, int length)
+    {
+      if (!seeded)
+        throw new IllegalStateException("generator not seeded");
+
+      int count = 0;
+      do
+        {
+          int amount = Math.min(LIMIT, length - count);
+          super.nextBytes(out, offset+count, amount);
+          count += amount;
+
+          for (int i = 0; i < key.length; i += counter.length)
+            {
+              //fillBlock(); // inlined
+              CryptixRijndael_Algorithm.blockEncrypt(counter, buffer, 0, 0, cryptixKey);
+              incrementCounter();
+              int l = Math.min(key.length - i, 16);//cipher.currentBlockSize());
+              System.arraycopy(buffer, 0, key, i, l);
+            }
+          resetKey();
+        }
+      while (count < length);
+      //fillBlock(); // inlined
+      CryptixRijndael_Algorithm.blockEncrypt(counter, buffer, 0, 0, cryptixKey);
+      incrementCounter();
+      ndx = 0;
+    }
+
+    public final void addRandomByte(byte b)
+    {
+      addRandomBytes(new byte[] { b });
+    }
+
+    public final void addRandomBytes(byte[] seed, int offset, int length)
+    {
+      hash.update(key, 0, key.length);
+      hash.update(seed, offset, length);
+      //byte[] newkey = hash.digest();
+      //System.arraycopy(newkey, 0, key, 0, Math.min(key.length, newkey.length));
+      hash.doFinal(key, 0);
+      resetKey();
+      incrementCounter();
+      seeded = true;
+    }
+
+    public final void fillBlock()
+    {
+      ////i2p: this is not being checked as a microoptimization
+      //if (!seeded)
+      //  throw new IllegalStateException("generator not seeded");
+      CryptixRijndael_Algorithm.blockEncrypt(counter, buffer, 0, 0, cryptixKey);
+      incrementCounter();
+    }
+
+    public void setup(Map attributes)
+    {
+      seeded = false;
+      Arrays.fill(key, (byte) 0);
+      Arrays.fill(counter, (byte) 0);
+      byte[] seed = (byte[]) attributes.get(SEED);
+      if (seed != null)
+        addRandomBytes(seed);
+    }
+
+    /**
+     * Resets the cipher's key. This is done after every reseed, which
+     * combines the old key and the seed, and processes that throigh the
+     * hash function.
+     */
+    private final void resetKey()
+    {
+      try {
+          cryptixKey = CryptixRijndael_Algorithm.makeKey(key, 16, cryptixKeyBuf);
+      } catch (InvalidKeyException ike) {
+          throw new Error("hrmf", ike);
+      }
+    }
+
+    /**
+     * Increment `counter' as a sixteen-byte little-endian unsigned integer
+     * by one.
+     */
+    private final void incrementCounter()
+    {
+      for (int i = 0; i < counter.length; i++)
+        {
+          counter[i]++;
+          if (counter[i] != 0)
+            break;
+        }
+    }
+  }
+  
+  public static void main(String args[]) {
+      Fortuna f = new Fortuna();
+      java.util.HashMap props = new java.util.HashMap();
+      byte initSeed[] = new byte[1234];
+      new java.util.Random().nextBytes(initSeed);
+      long before = System.currentTimeMillis();
+      props.put(SEED, (byte[])initSeed);
+      f.init(props);
+      byte buf[] = new byte[8*1024];
+      for (int i = 0; i < 64*1024; i++) {
+        f.nextBytes(buf);
+      }
+      long time = System.currentTimeMillis() - before;
+      System.out.println("512MB took " + time + ", or " + (8*64d)/((double)time/1000d) +"MBps");
+  }
+}
diff --git a/core/java/src/gnu/crypto/prng/IRandom.java b/core/java/src/gnu/crypto/prng/IRandom.java
new file mode 100644
index 0000000000000000000000000000000000000000..9dc2109cdbc5bbf99472fc267ca8173fc8c624d5
--- /dev/null
+++ b/core/java/src/gnu/crypto/prng/IRandom.java
@@ -0,0 +1,186 @@
+package gnu.crypto.prng;
+
+// ----------------------------------------------------------------------------
+// $Id: IRandom.java,v 1.12 2005/10/06 04:24:17 rsdio Exp $
+//
+// Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc.
+//
+// This file is part of GNU Crypto.
+//
+// GNU Crypto is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2, or (at your option)
+// any later version.
+//
+// GNU Crypto is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING.  If not, write to the
+//
+//    Free Software Foundation Inc.,
+//    51 Franklin Street, Fifth Floor,
+//    Boston, MA 02110-1301
+//    USA
+//
+// Linking this library statically or dynamically with other modules is
+// making a combined work based on this library.  Thus, the terms and
+// conditions of the GNU General Public License cover the whole
+// combination.
+//
+// As a special exception, the copyright holders of this library give
+// you permission to link this library with independent modules to
+// produce an executable, regardless of the license terms of these
+// independent modules, and to copy and distribute the resulting
+// executable under terms of your choice, provided that you also meet,
+// for each linked independent module, the terms and conditions of the
+// license of that module.  An independent module is a module which is
+// not derived from or based on this library.  If you modify this
+// library, you may extend this exception to your version of the
+// library, but you are not obligated to do so.  If you do not wish to
+// do so, delete this exception statement from your version.
+// ----------------------------------------------------------------------------
+
+import java.util.Map;
+
+/**
+ * <p>The basic visible methods of any pseudo-random number generator.</p>
+ *
+ * <p>The [HAC] defines a PRNG (as implemented in this library) as follows:</p>
+ *
+ * <ul>
+ *    <li>"5.6 Definition: A pseudorandom bit generator (PRBG) is said to pass
+ *    the <em>next-bit test</em> if there is no polynomial-time algorithm which,
+ *    on input of the first <code>L</code> bits of an output sequence <code>S</code>,
+ *    can predict the <code>(L+1)</code>st bit of <code>S</code> with a
+ *    probability significantly grater than <code>1/2</code>."</li>
+ *
+ *    <li>"5.8 Definition: A PRBG that passes the <em>next-bit test</em>
+ *    (possibly under some plausible but unproved mathematical assumption such
+ *    as the intractability of factoring integers) is called a
+ *    <em>cryptographically secure pseudorandom bit generator</em> (CSPRBG)."</li>
+ * </ul>
+ *
+ * <p><b>IMPLEMENTATION NOTE</b>: Although all the concrete classes in this
+ * package implement the {@link Cloneable} interface, it is important to note
+ * here that such an operation, for those algorithms that use an underlting
+ * symmetric key block cipher, <b>DOES NOT</b> clone any session key material
+ * that may have been used in initialising the source PRNG (the instance to be
+ * cloned). Instead a clone of an already initialised PRNG, that uses and
+ * underlying symmetric key block cipher, is another instance with a clone of
+ * the same cipher that operates with the <b>same block size</b> but without any
+ * knowledge of neither key material nor key size.</p>
+ *
+ * <p>References:</p>
+ *
+ * <ol>
+ *    <li><a href="http://www.cacr.math.uwaterloo.ca/hac">[HAC]</a>: Handbook of
+ *    Applied Cryptography.<br>
+ *    CRC Press, Inc. ISBN 0-8493-8523-7, 1997<br>
+ *    Menezes, A., van Oorschot, P. and S. Vanstone.</li>
+ * </ol>
+ *
+ * @version $Revision: 1.12 $
+ */
+public interface IRandom extends Cloneable {
+
+   // Constants
+   // -------------------------------------------------------------------------
+
+   // Methods
+   // -------------------------------------------------------------------------
+
+   /**
+    * <p>Returns the canonical name of this instance.</p>
+    *
+    * @return the canonical name of this instance. */
+   String name();
+
+   /**
+    * <p>Initialises the pseudo-random number generator scheme with the
+    * appropriate attributes.</p>
+    *
+    * @param attributes a set of name-value pairs that describe the desired
+    * future instance behaviour.
+    * @exception IllegalArgumentException if at least one of the defined name/
+    * value pairs contains invalid data.
+    */
+   void init(Map attributes);
+
+   /**
+    * <p>Returns the next 8 bits of random data generated from this instance.</p>
+    *
+    * @return the next 8 bits of random data generated from this instance.
+    * @exception IllegalStateException if the instance is not yet initialised.
+    * @exception LimitReachedException if this instance has reached its
+    * theoretical limit for generating non-repetitive pseudo-random data.
+    */
+   byte nextByte() throws IllegalStateException, LimitReachedException;
+
+   /**
+    * <p>Fills the designated byte array, starting from byte at index
+    * <code>offset</code>, for a maximum of <code>length</code> bytes with the
+    * output of this generator instance.
+    *
+    * @param out the placeholder to contain the generated random bytes.
+    * @param offset the starting index in <i>out</i> to consider. This method
+    * does nothing if this parameter is not within <code>0</code> and
+    * <code>out.length</code>.
+    * @param length the maximum number of required random bytes. This method
+    * does nothing if this parameter is less than <code>1</code>.
+    * @exception IllegalStateException if the instance is not yet initialised.
+    * @exception LimitReachedException if this instance has reached its
+    * theoretical limit for generating non-repetitive pseudo-random data.
+    */
+   void nextBytes(byte[] out, int offset, int length)
+   throws IllegalStateException, LimitReachedException;
+
+   /**
+    * <p>Supplement, or possibly replace, the random state of this PRNG with
+    * a random byte.</p>
+    *
+    * <p>Implementations are not required to implement this method in any
+    * meaningful way; this may be a no-operation, and implementations may
+    * throw an {@link UnsupportedOperationException}.</p>
+    *
+    * @param b The byte to add.
+    */
+   void addRandomByte(byte b);
+
+   /**
+    * <p>Supplement, or possibly replace, the random state of this PRNG with
+    * a sequence of new random bytes.</p>
+    *
+    * <p>Implementations are not required to implement this method in any
+    * meaningful way; this may be a no-operation, and implementations may
+    * throw an {@link UnsupportedOperationException}.</p>
+    *
+    * @param in The buffer of new random bytes to add.
+    */
+   void addRandomBytes(byte[] in);
+
+   /**
+    * <p>Supplement, or possibly replace, the random state of this PRNG with
+    * a sequence of new random bytes.</p>
+    *
+    * <p>Implementations are not required to implement this method in any
+    * meaningful way; this may be a no-operation, and implementations may
+    * throw an {@link UnsupportedOperationException}.</p>
+    *
+    * @param in The buffer of new random bytes to add.
+    * @param offset The offset from whence to begin reading random bytes.
+    * @param length The number of random bytes to add.
+    * @exception IndexOutOfBoundsException If <i>offset</i>, <i>length</i>,
+    * or <i>offset</i>+<i>length</i> is out of bounds.
+    */
+   void addRandomBytes(byte[] in, int offset, int length);
+
+   /**
+    * <p>Returns a clone copy of this instance.</p>
+    *
+    * @return a clone copy of this instance.
+    */
+   Object clone() throws CloneNotSupportedException;
+}
diff --git a/core/java/src/gnu/crypto/prng/LimitReachedException.java b/core/java/src/gnu/crypto/prng/LimitReachedException.java
new file mode 100644
index 0000000000000000000000000000000000000000..4e5f26f3f0fe0f5e26f999f07fa9e32bf3564515
--- /dev/null
+++ b/core/java/src/gnu/crypto/prng/LimitReachedException.java
@@ -0,0 +1,73 @@
+package gnu.crypto.prng;
+
+// ----------------------------------------------------------------------------
+// $Id: LimitReachedException.java,v 1.5 2005/10/06 04:24:17 rsdio Exp $
+//
+// Copyright (C) 2001, 2002, Free Software Foundation, Inc.
+//
+// This file is part of GNU Crypto.
+//
+// GNU Crypto is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2, or (at your option)
+// any later version.
+//
+// GNU Crypto is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING.  If not, write to the
+//
+//    Free Software Foundation Inc.,
+//    51 Franklin Street, Fifth Floor,
+//    Boston, MA 02110-1301
+//    USA
+//
+// Linking this library statically or dynamically with other modules is
+// making a combined work based on this library.  Thus, the terms and
+// conditions of the GNU General Public License cover the whole
+// combination.
+//
+// As a special exception, the copyright holders of this library give
+// you permission to link this library with independent modules to
+// produce an executable, regardless of the license terms of these
+// independent modules, and to copy and distribute the resulting
+// executable under terms of your choice, provided that you also meet,
+// for each linked independent module, the terms and conditions of the
+// license of that module.  An independent module is a module which is
+// not derived from or based on this library.  If you modify this
+// library, you may extend this exception to your version of the
+// library, but you are not obligated to do so.  If you do not wish to
+// do so, delete this exception statement from your version.
+// ----------------------------------------------------------------------------
+
+/**
+ * A checked exception that indicates that a pseudo random number generated has
+ * reached its theoretical limit in generating random bytes.
+ *
+ * @version $Revision: 1.5 $
+ */
+public class LimitReachedException extends Exception {
+
+   // Constants and variables
+   // -------------------------------------------------------------------------
+
+   // Constructor(s)
+   // -------------------------------------------------------------------------
+
+   public LimitReachedException() {
+      super();
+   }
+
+   public LimitReachedException(String msg) {
+      super(msg);
+   }
+
+   // Class methods
+   // -------------------------------------------------------------------------
+
+   // Instant methods
+   // -------------------------------------------------------------------------
+}
diff --git a/core/java/src/gnu/crypto/prng/RandomEvent.java b/core/java/src/gnu/crypto/prng/RandomEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..f9a678f9515c139f0a5c794c3f9a429b253d306d
--- /dev/null
+++ b/core/java/src/gnu/crypto/prng/RandomEvent.java
@@ -0,0 +1,82 @@
+/* RandomEvent.java -- a random event.
+   Copyright (C) 2004  Free Software Foundation, Inc.
+
+This file is part of GNU Crypto.
+
+GNU Crypto is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2, or (at your option) any
+later version.
+
+GNU Crypto is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; see the file COPYING.  If not, write to the
+
+   Free Software Foundation Inc.,
+   51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301
+   USA
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.  */
+
+
+package gnu.crypto.prng;
+
+import java.util.EventObject;
+
+/**
+ * An interface for entropy accumulators that will be notified of random
+ * events.
+ */
+public class RandomEvent extends EventObject
+{
+
+  private final byte sourceNumber;
+  private final byte poolNumber;
+  private final byte[] data;
+
+  public RandomEvent(Object source, byte sourceNumber, byte poolNumber,
+                     byte[] data)
+  {
+    super(source);
+    this.sourceNumber = sourceNumber;
+    this.poolNumber = poolNumber;
+    if (data.length == 0 || data.length > 32)
+      throw new IllegalArgumentException("random events take between 1 and 32 bytes of data");
+    this.data = (byte[]) data.clone();
+  }
+
+  public byte getSourceNumber()
+  {
+    return sourceNumber;
+  }
+
+  public byte getPoolNumber()
+  {
+    return poolNumber;
+  }
+
+  public byte[] getData()
+  {
+    return data;
+  }
+}
diff --git a/core/java/src/gnu/crypto/prng/RandomEventListener.java b/core/java/src/gnu/crypto/prng/RandomEventListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..0c4542217fe8e20aed90dfe13be6fb5bb1fa9780
--- /dev/null
+++ b/core/java/src/gnu/crypto/prng/RandomEventListener.java
@@ -0,0 +1,53 @@
+/* RandomEventListener.java -- event listener
+   Copyright (C) 2004  Free Software Foundation, Inc.
+
+This file is part of GNU Crypto.
+
+GNU Crypto is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2, or (at your option) any
+later version.
+
+GNU Crypto is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; see the file COPYING.  If not, write to the
+
+   Free Software Foundation Inc.,
+   51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301
+   USA
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.  */
+
+
+package gnu.crypto.prng;
+
+import java.util.EventListener;
+
+/**
+ * An interface for entropy accumulators that will be notified of random
+ * events.
+ */
+public interface RandomEventListener extends EventListener
+{
+  void addRandomEvent(RandomEvent event);
+}
diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java
index 0ae7279bed2887d5b0446caad52889d0191174aa..437d0c0d25a239ff10936ea8c72059780e6383eb 100644
--- a/core/java/src/net/i2p/I2PAppContext.java
+++ b/core/java/src/net/i2p/I2PAppContext.java
@@ -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;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/core/java/src/net/i2p/crypto/CryptixAESKeyCache.java b/core/java/src/net/i2p/crypto/CryptixAESKeyCache.java
index c41c72092ce82020056db8efd3d233f7bb0e75d1..43513c188b0f105a73bff40ba99ca36a107b725d 100644
--- a/core/java/src/net/i2p/crypto/CryptixAESKeyCache.java
+++ b/core/java/src/net/i2p/crypto/CryptixAESKeyCache.java
@@ -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
diff --git a/core/java/src/net/i2p/crypto/DSAEngine.java b/core/java/src/net/i2p/crypto/DSAEngine.java
index 46aef00e7d239c74d36a397b0f825177cd4d70d1..113627cafdb6787a50b824af1a195b8531d85dc6 100644
--- a/core/java/src/net/i2p/crypto/DSAEngine.java
+++ b/core/java/src/net/i2p/crypto/DSAEngine.java
@@ -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);
     }
-}
\ No newline at end of file
+
+    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();
+    }
+}
diff --git a/core/java/src/net/i2p/util/FortunaRandomSource.java b/core/java/src/net/i2p/util/FortunaRandomSource.java
new file mode 100644
index 0000000000000000000000000000000000000000..b86158a804425b948f613340ff3915fbd67856de
--- /dev/null
+++ b/core/java/src/net/i2p/util/FortunaRandomSource.java
@@ -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);
+    }
+}
diff --git a/core/java/src/net/i2p/util/PooledRandomSource.java b/core/java/src/net/i2p/util/PooledRandomSource.java
index 2e70bbaa9b67c20068401bdd937b3c112324f093..5a6bb7bddfe907a46cb4a48a9c53e3c3735a88ff 100644
--- a/core/java/src/net/i2p/util/PooledRandomSource.java
+++ b/core/java/src/net/i2p/util/PooledRandomSource.java
@@ -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();
     }    
 }
diff --git a/core/java/src/net/i2p/util/RandomSource.java b/core/java/src/net/i2p/util/RandomSource.java
index bb26828c419adb32a01f95c115f2448ab2d8d33d..51e340a57ee002e5b6e0b48bc43b4c449c666b7a 100644
--- a/core/java/src/net/i2p/util/RandomSource.java
+++ b/core/java/src/net/i2p/util/RandomSource.java
@@ -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) {}
     }
-}
\ No newline at end of file
+}
diff --git a/history.txt b/history.txt
index b20fb5950008e92c2a98b50aaf9998c72fa4fa4a..08ae2f2c0778d70fcbd83aaf996fe9b7d18018c3 100644
--- a/history.txt
+++ b/history.txt
@@ -1,4 +1,15 @@
-$Id: history.txt,v 1.303 2005/10/20 03:56:40 jrandom Exp $
+$Id: history.txt,v 1.304 2005/10/20 14:42:13 dust Exp $
+
+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.
 
 2005-10-20  dust
     * Fix bug in ircclient that prevented it to use its own dest (i.e. was
diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java
index ece9cc54f2887605bfa6ee8f78d2a53b9b2540b4..7fe3cc0b57ce538a0cb2962e805105b97fb8ed22 100644
--- a/router/java/src/net/i2p/router/Router.java
+++ b/router/java/src/net/i2p/router/Router.java
@@ -770,6 +770,7 @@ public class Router {
     
     public void shutdown(int exitCode) {
         _isAlive = false;
+        _context.random().saveSeed();
         I2PThread.removeOOMEventListener(_oomListener);
         try { _context.jobQueue().shutdown(); } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting down the job queue", t); }
         //try { _context.adminManager().shutdown(); } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting down the admin manager", t); }        
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index 0d71e42e9379903209d100ae3d425114d71d3f8f..03711224b2a36ca8801c349ca1239c6a3fc38989 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -15,9 +15,9 @@ import net.i2p.CoreVersion;
  *
  */
 public class RouterVersion {
-    public final static String ID = "$Revision: 1.274 $ $Date: 2005/10/20 03:56:39 $";
+    public final static String ID = "$Revision: 1.275 $ $Date: 2005/10/20 14:42:16 $";
     public final static String VERSION = "0.6.1.3";
-    public final static long BUILD = 5;
+    public final static long BUILD = 6;
     public static void main(String args[]) {
         System.out.println("I2P Router version: " + VERSION + "-" + BUILD);
         System.out.println("Router ID: " + RouterVersion.ID);