diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java
index b669f1447c17cbfef7f0f629b552dc621870f865..712830f59a8c0cca05d73d9ed35ef66a2f4cd299 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java
@@ -199,10 +199,10 @@ public class NetDbRenderer {
             FloodfillNetworkDatabaseFacade netdb = (FloodfillNetworkDatabaseFacade)_context.netDb();
             buf.append("<p><b>Total Leasesets: ").append(leases.size());
             buf.append("</b></p><p><b>Published (RAP) Leasesets: ").append(netdb.getKnownLeaseSets());
-            buf.append("</b></p><p><b>Mod Data: \"").append(DataHelper.getUTF8(_context.routingKeyGenerator().getModData()))
-               .append("\" Last Changed: ").append(new Date(_context.routingKeyGenerator().getLastChanged()));
-            buf.append("</b></p><p><b>Next Mod Data: \"").append(DataHelper.getUTF8(_context.routingKeyGenerator().getNextModData()))
-               .append("\" Change in: ").append(DataHelper.formatDuration(_context.routingKeyGenerator().getTimeTillMidnight()));
+            buf.append("</b></p><p><b>Mod Data: \"").append(DataHelper.getUTF8(_context.routerKeyGenerator().getModData()))
+               .append("\" Last Changed: ").append(new Date(_context.routerKeyGenerator().getLastChanged()));
+            buf.append("</b></p><p><b>Next Mod Data: \"").append(DataHelper.getUTF8(_context.routerKeyGenerator().getNextModData()))
+               .append("\" Change in: ").append(DataHelper.formatDuration(_context.routerKeyGenerator().getTimeTillMidnight()));
             int ff = _context.peerManager().getPeersByCapability(FloodfillNetworkDatabaseFacade.CAPABILITY_FLOODFILL).size();
             buf.append("</b></p><p><b>Known Floodfills: ").append(ff);
             buf.append("</b></p><p><b>Currently Floodfill? ");
diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java
index 8357a485ac14110622603bf06e90e7c1153c3942..79c3108f78d9c4b27d530056819aa8ce053129b5 100644
--- a/core/java/src/net/i2p/I2PAppContext.java
+++ b/core/java/src/net/i2p/I2PAppContext.java
@@ -86,7 +86,6 @@ public class I2PAppContext {
     private SHA256Generator _sha;
     protected Clock _clock; // overridden in RouterContext
     private DSAEngine _dsa;
-    private RoutingKeyGenerator _routingKeyGenerator;
     private RandomSource _random;
     private KeyGenerator _keyGenerator;
     protected KeyRing _keyRing; // overridden in RouterContext
@@ -106,7 +105,6 @@ public class I2PAppContext {
     private volatile boolean _shaInitialized;
     protected volatile boolean _clockInitialized; // used in RouterContext
     private volatile boolean _dsaInitialized;
-    private volatile boolean _routingKeyGeneratorInitialized;
     private volatile boolean _randomInitialized;
     private volatile boolean _keyGeneratorInitialized;
     protected volatile boolean _keyRingInitialized; // used in RouterContext
@@ -126,7 +124,7 @@ public class I2PAppContext {
     private final Object _lock1 = new Object(), _lock2 = new Object(), _lock3 = new Object(), _lock4 = new Object(),
                          _lock5 = new Object(), _lock6 = new Object(), _lock7 = new Object(), _lock8 = new Object(),
                          _lock9 = new Object(), _lock10 = new Object(), _lock11 = new Object(), _lock12 = new Object(),
-                         _lock13 = new Object(), _lock14 = new Object(), _lock15 = new Object(), _lock16 = new Object(),
+                         _lock13 = new Object(), _lock14 = new Object(), _lock16 = new Object(),
                          _lock17 = new Object(), _lock18 = new Object(), _lock19 = new Object(), _lock20 = new Object();
 
     /**
@@ -851,19 +849,13 @@ public class I2PAppContext {
      * may want to test out how things react when peers don't agree on 
      * how to skew.
      *
+     * As of 0.9.16, returns null in I2PAppContext.
+     * You must be in RouterContext to get a generator.
+     *
+     * @return null always
      */
     public RoutingKeyGenerator routingKeyGenerator() {
-        if (!_routingKeyGeneratorInitialized)
-            initializeRoutingKeyGenerator();
-        return _routingKeyGenerator;
-    }
-
-    private void initializeRoutingKeyGenerator() {
-        synchronized (_lock15) {
-            if (_routingKeyGenerator == null)
-                _routingKeyGenerator = new RoutingKeyGenerator(this);
-            _routingKeyGeneratorInitialized = true;
-        }
+        return null;
     }
     
     /**
diff --git a/core/java/src/net/i2p/data/DatabaseEntry.java b/core/java/src/net/i2p/data/DatabaseEntry.java
index 4b84ed106e0775a620f2b112794a1486ac384968..2adc066bfee05f4b1243e3030cbcfb7e992cd9c4 100644
--- a/core/java/src/net/i2p/data/DatabaseEntry.java
+++ b/core/java/src/net/i2p/data/DatabaseEntry.java
@@ -11,6 +11,7 @@ package net.i2p.data;
 
 import java.util.Arrays;
 
+import net.i2p.I2PAppContext;
 import net.i2p.crypto.DSAEngine;
 
 /**
@@ -47,7 +48,7 @@ public abstract class DatabaseEntry extends DataStructureImpl {
 
     protected volatile Signature _signature;
     protected volatile Hash _currentRoutingKey;
-    protected volatile byte[] _routingKeyGenMod;
+    protected volatile long _routingKeyGenMod;
 
     /**
      * A common interface to the timestamp of the two subclasses.
@@ -106,11 +107,15 @@ public abstract class DatabaseEntry extends DataStructureImpl {
      * Get the routing key for the structure using the current modifier in the RoutingKeyGenerator.
      * This only calculates a new one when necessary though (if the generator's key modifier changes)
      *
+     * @throws IllegalStateException if not in RouterContext
      */
     public Hash getRoutingKey() {
-        RoutingKeyGenerator gen = RoutingKeyGenerator.getInstance();
-        byte[] mod = gen.getModData();
-        if (!Arrays.equals(mod, _routingKeyGenMod)) {
+        I2PAppContext ctx = I2PAppContext.getGlobalContext();
+        if (!ctx.isRouterContext())
+            throw new IllegalStateException("Not in router context");
+        RoutingKeyGenerator gen = ctx.routingKeyGenerator();
+        long mod = gen.getLastChanged();
+        if (mod != _routingKeyGenMod) {
             _currentRoutingKey = gen.getRoutingKey(getHash());
             _routingKeyGenMod = mod;
         }
@@ -124,9 +129,16 @@ public abstract class DatabaseEntry extends DataStructureImpl {
         _currentRoutingKey = key;
     }
 
+    /**
+     * @throws IllegalStateException if not in RouterContext
+     */
     public boolean validateRoutingKey() {
+        I2PAppContext ctx = I2PAppContext.getGlobalContext();
+        if (!ctx.isRouterContext())
+            throw new IllegalStateException("Not in router context");
+        RoutingKeyGenerator gen = ctx.routingKeyGenerator();
         Hash destKey = getHash();
-        Hash rk = RoutingKeyGenerator.getInstance().getRoutingKey(destKey);
+        Hash rk = gen.getRoutingKey(destKey);
         return rk.equals(getRoutingKey());
     }
 
diff --git a/core/java/src/net/i2p/data/RoutingKeyGenerator.java b/core/java/src/net/i2p/data/RoutingKeyGenerator.java
index 367089442a8fcda8346a039709d9947bd0323318..20e2218ee5548421c2011319ee1ecfb887b7110a 100644
--- a/core/java/src/net/i2p/data/RoutingKeyGenerator.java
+++ b/core/java/src/net/i2p/data/RoutingKeyGenerator.java
@@ -9,215 +9,40 @@ package net.i2p.data;
  *
  */
 
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.Arrays;
-import java.util.GregorianCalendar;
-import java.util.Locale;
-import java.util.TimeZone;
-
 import net.i2p.I2PAppContext;
-import net.i2p.crypto.SHA256Generator;
-import net.i2p.util.HexDump;
-import net.i2p.util.Log;
 
 /**
  * Component to manage the munging of hashes into routing keys - given a hash, 
  * perform some consistent transformation against it and return the result.
  * This transformation is fed by the current "mod data".  
  *
- * Right now the mod data is the current date (GMT) as a string: "yyyyMMdd",
- * and the transformation takes the original hash, appends the bytes of that mod data,
- * then returns the SHA256 of that concatenation.
- *
- * Do we want this to simply do the XOR of the SHA256 of the current mod data and
- * the key?  does that provide the randomization we need?  It'd save an SHA256 op.
- * Bah, too much effort to think about for so little gain.  Other algorithms may come
- * into play layer on about making periodic updates to the routing key for data elements
- * to mess with Sybil.  This may be good enough though.
- *
- * Also - the method generateDateBasedModData() should be called after midnight GMT 
- * once per day to generate the correct routing keys!
- *
- * Warning - API subject to change. Not for use outside the router.
+ * As of 0.9.16, this is essentially just an interface.
+ * Implementation moved to net.i2p.data.router.RouterKeyGenerator.
+ * No generator is available in I2PAppContext; you must be in RouterContext.
  *
  */
-public class RoutingKeyGenerator {
-    private final Log _log;
-    private final I2PAppContext _context;
-
-    public RoutingKeyGenerator(I2PAppContext context) {
-        _log = context.logManager().getLog(RoutingKeyGenerator.class);
-        _context = context;
-        // ensure non-null mod data
-        generateDateBasedModData();
-    }
-
-    public static RoutingKeyGenerator getInstance() {
-        return I2PAppContext.getGlobalContext().routingKeyGenerator();
-    }
-    
-    private volatile byte _currentModData[];
-    private volatile byte _nextModData[];
-    private volatile long _nextMidnight;
-    private volatile long _lastChanged;
-
-    private final static Calendar _cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));
-    private static final String FORMAT = "yyyyMMdd";
-    private static final int LENGTH = FORMAT.length();
-    private final static SimpleDateFormat _fmt = new SimpleDateFormat(FORMAT, Locale.US);
-    static {
-        // make sure GMT is set, azi2phelper Vuze plugin is disabling static JVM TZ setting in Router.java
-        _fmt.setCalendar(_cal);
-    }
-
-    /**
-     *  The current (today's) mod data.
-     *  Warning - not a copy, do not corrupt.
-     *
-     *  @return non-null, 8 bytes
-     */
-    public byte[] getModData() {
-        return _currentModData;
-    }
-
-    /**
-     *  Tomorrow's mod data.
-     *  Warning - not a copy, do not corrupt.
-     *  For debugging use only.
-     *
-     *  @return non-null, 8 bytes
-     *  @since 0.9.10
-     */
-    public byte[] getNextModData() {
-        return _nextModData;
-    }
-
-    public long getLastChanged() {
-        return _lastChanged;
-    }
+public abstract class RoutingKeyGenerator {
 
     /**
-     *  How long until midnight (ms)
+     * Get the generator for this context.
      *
-     *  @return could be slightly negative
-     *  @since 0.9.10 moved from UpdateRoutingKeyModifierJob
+     * @return null in I2PAppContext; non-null in RouterContext.
      */
-    public long getTimeTillMidnight() {
-        return _nextMidnight - _context.clock().now();
-    }
-
-    /**
-     *  Set _cal to midnight for the time given.
-     *  Caller must synch.
-     *  @since 0.9.10
-     */
-    private void setCalToPreviousMidnight(long now) {
-            _cal.setTime(new Date(now));
-            _cal.set(Calendar.YEAR, _cal.get(Calendar.YEAR));               // gcj <= 4.0 workaround
-            _cal.set(Calendar.DAY_OF_YEAR, _cal.get(Calendar.DAY_OF_YEAR)); // gcj <= 4.0 workaround
-            _cal.set(Calendar.HOUR_OF_DAY, 0);
-            _cal.set(Calendar.MINUTE, 0);
-            _cal.set(Calendar.SECOND, 0);
-            _cal.set(Calendar.MILLISECOND, 0);
+    public static RoutingKeyGenerator getInstance() {
+        return I2PAppContext.getGlobalContext().routingKeyGenerator();
     }
 
     /**
-     *  Generate mod data from _cal.
-     *  Caller must synch.
-     *  @since 0.9.10
+     *  The version of the current (today's) mod data.
+     *  Use to determine if the routing key should be regenerated.
      */
-    private byte[] generateModDataFromCal() {
-        Date today = _cal.getTime();
-        
-        String modVal = _fmt.format(today);
-        if (modVal.length() != LENGTH)
-            throw new IllegalStateException();
-        byte[] mod = new byte[LENGTH];
-        for (int i = 0; i < LENGTH; i++)
-            mod[i] = (byte)(modVal.charAt(i) & 0xFF);
-        return mod;
-    }
+    public abstract long getLastChanged();
 
     /**
-     * Update the current modifier data with some bytes derived from the current
-     * date (yyyyMMdd in GMT)
-     *
-     * @return true if changed
-     */
-    public synchronized boolean generateDateBasedModData() {
-        long now = _context.clock().now();
-        setCalToPreviousMidnight(now);
-        byte[] mod = generateModDataFromCal();
-        boolean changed = !Arrays.equals(_currentModData, mod);
-        if (changed) {
-            // add a day and store next midnight and mod data for convenience
-            _cal.add(Calendar.DATE, 1);
-            _nextMidnight = _cal.getTime().getTime();
-            byte[] next = generateModDataFromCal();
-            _currentModData = mod;
-            _nextModData = next;
-            _lastChanged = now;
-            if (_log.shouldLog(Log.INFO))
-                _log.info("Routing modifier generated: " + HexDump.dump(mod));
-        }
-        return changed;
-    }
-    
-    /**
-     * Generate a modified (yet consistent) hash from the origKey by generating the
-     * SHA256 of the targetKey with the current modData appended to it
-     *
-     * This makes Sybil's job a lot harder, as she needs to essentially take over the
-     * whole keyspace.
-     *
-     * @throws IllegalArgumentException if origKey is null
-     */
-    public Hash getRoutingKey(Hash origKey) {
-        return getKey(origKey, _currentModData);
-    }
-    
-    /**
-     * Get the routing key using tomorrow's modData, not today's
-     *
-     * @since 0.9.10
-     */
-    public Hash getNextRoutingKey(Hash origKey) {
-        return getKey(origKey, _nextModData);
-    }
-    
-    /**
-     * Generate a modified (yet consistent) hash from the origKey by generating the
-     * SHA256 of the targetKey with the specified modData appended to it
+     * Get the routing key for a key.
      *
      * @throws IllegalArgumentException if origKey is null
      */
-    private static Hash getKey(Hash origKey, byte[] modData) {
-        if (origKey == null) throw new IllegalArgumentException("Original key is null");
-        byte modVal[] = new byte[Hash.HASH_LENGTH + LENGTH];
-        System.arraycopy(origKey.getData(), 0, modVal, 0, Hash.HASH_LENGTH);
-        System.arraycopy(modData, 0, modVal, Hash.HASH_LENGTH, LENGTH);
-        return SHA256Generator.getInstance().calculateHash(modVal);
-    }
-
-/****
-    public static void main(String args[]) {
-        Hash k1 = new Hash();
-        byte k1d[] = new byte[Hash.HASH_LENGTH];
-        RandomSource.getInstance().nextBytes(k1d);
-        k1.setData(k1d);
+    public abstract Hash getRoutingKey(Hash origKey);
 
-        for (int i = 0; i < 10; i++) {
-            System.out.println("K1:  " + k1);
-            Hash k1m = RoutingKeyGenerator.getInstance().getRoutingKey(k1);
-            System.out.println("MOD: " + new String(RoutingKeyGenerator.getInstance().getModData()));
-            System.out.println("K1M: " + k1m);
-        }
-        try {
-            Thread.sleep(2000);
-        } catch (Throwable t) { // nop
-        }
-    }
-****/
 }
diff --git a/router/java/src/net/i2p/data/router/RouterKeyGenerator.java b/router/java/src/net/i2p/data/router/RouterKeyGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..69e6aaceca317488cce3034b2d7e705dc5d7740a
--- /dev/null
+++ b/router/java/src/net/i2p/data/router/RouterKeyGenerator.java
@@ -0,0 +1,224 @@
+package net.i2p.data.router;
+
+/*
+ * 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.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Arrays;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import net.i2p.I2PAppContext;
+import net.i2p.crypto.SHA256Generator;
+import net.i2p.data.Hash;
+import net.i2p.data.RoutingKeyGenerator;
+import net.i2p.util.HexDump;
+import net.i2p.util.Log;
+
+/**
+ * Component to manage the munging of hashes into routing keys - given a hash, 
+ * perform some consistent transformation against it and return the result.
+ * This transformation is fed by the current "mod data".  
+ *
+ * Right now the mod data is the current date (GMT) as a string: "yyyyMMdd",
+ * and the transformation takes the original hash, appends the bytes of that mod data,
+ * then returns the SHA256 of that concatenation.
+ *
+ * Do we want this to simply do the XOR of the SHA256 of the current mod data and
+ * the key?  does that provide the randomization we need?  It'd save an SHA256 op.
+ * Bah, too much effort to think about for so little gain.  Other algorithms may come
+ * into play layer on about making periodic updates to the routing key for data elements
+ * to mess with Sybil.  This may be good enough though.
+ *
+ * Also - the method generateDateBasedModData() should be called after midnight GMT 
+ * once per day to generate the correct routing keys!
+ *
+ * @since 0.9.16 moved from net.i2p.data.RoutingKeyGenerator..
+ *
+ */
+public class RouterKeyGenerator extends RoutingKeyGenerator {
+    private final Log _log;
+    private final I2PAppContext _context;
+
+    public RouterKeyGenerator(I2PAppContext context) {
+        _log = context.logManager().getLog(RoutingKeyGenerator.class);
+        _context = context;
+        // ensure non-null mod data
+        generateDateBasedModData();
+    }
+    
+    private volatile byte _currentModData[];
+    private volatile byte _nextModData[];
+    private volatile long _nextMidnight;
+    private volatile long _lastChanged;
+
+    private final static Calendar _cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));
+    private static final String FORMAT = "yyyyMMdd";
+    private static final int LENGTH = FORMAT.length();
+    private final static SimpleDateFormat _fmt = new SimpleDateFormat(FORMAT, Locale.US);
+    static {
+        // make sure GMT is set, azi2phelper Vuze plugin is disabling static JVM TZ setting in Router.java
+        _fmt.setCalendar(_cal);
+    }
+
+    /**
+     *  The current (today's) mod data.
+     *  Warning - not a copy, do not corrupt.
+     *
+     *  @return non-null, 8 bytes
+     */
+    public byte[] getModData() {
+        return _currentModData;
+    }
+
+    /**
+     *  Tomorrow's mod data.
+     *  Warning - not a copy, do not corrupt.
+     *  For debugging use only.
+     *
+     *  @return non-null, 8 bytes
+     *  @since 0.9.10
+     */
+    public byte[] getNextModData() {
+        return _nextModData;
+    }
+
+    public long getLastChanged() {
+        return _lastChanged;
+    }
+
+    /**
+     *  How long until midnight (ms)
+     *
+     *  @return could be slightly negative
+     *  @since 0.9.10 moved from UpdateRoutingKeyModifierJob
+     */
+    public long getTimeTillMidnight() {
+        return _nextMidnight - _context.clock().now();
+    }
+
+    /**
+     *  Set _cal to midnight for the time given.
+     *  Caller must synch.
+     *  @since 0.9.10
+     */
+    private void setCalToPreviousMidnight(long now) {
+            _cal.setTime(new Date(now));
+            _cal.set(Calendar.YEAR, _cal.get(Calendar.YEAR));               // gcj <= 4.0 workaround
+            _cal.set(Calendar.DAY_OF_YEAR, _cal.get(Calendar.DAY_OF_YEAR)); // gcj <= 4.0 workaround
+            _cal.set(Calendar.HOUR_OF_DAY, 0);
+            _cal.set(Calendar.MINUTE, 0);
+            _cal.set(Calendar.SECOND, 0);
+            _cal.set(Calendar.MILLISECOND, 0);
+    }
+
+    /**
+     *  Generate mod data from _cal.
+     *  Caller must synch.
+     *  @since 0.9.10
+     */
+    private byte[] generateModDataFromCal() {
+        Date today = _cal.getTime();
+        
+        String modVal = _fmt.format(today);
+        if (modVal.length() != LENGTH)
+            throw new IllegalStateException();
+        byte[] mod = new byte[LENGTH];
+        for (int i = 0; i < LENGTH; i++)
+            mod[i] = (byte)(modVal.charAt(i) & 0xFF);
+        return mod;
+    }
+
+    /**
+     * Update the current modifier data with some bytes derived from the current
+     * date (yyyyMMdd in GMT)
+     *
+     * @return true if changed
+     */
+    public synchronized boolean generateDateBasedModData() {
+        long now = _context.clock().now();
+        setCalToPreviousMidnight(now);
+        byte[] mod = generateModDataFromCal();
+        boolean changed = !Arrays.equals(_currentModData, mod);
+        if (changed) {
+            // add a day and store next midnight and mod data for convenience
+            _cal.add(Calendar.DATE, 1);
+            _nextMidnight = _cal.getTime().getTime();
+            byte[] next = generateModDataFromCal();
+            _currentModData = mod;
+            _nextModData = next;
+            // ensure version is bumped
+            if (_lastChanged == now)
+                now++;
+            _lastChanged = now;
+            if (_log.shouldLog(Log.INFO))
+                _log.info("Routing modifier generated: " + HexDump.dump(mod));
+        }
+        return changed;
+    }
+    
+    /**
+     * Generate a modified (yet consistent) hash from the origKey by generating the
+     * SHA256 of the targetKey with the current modData appended to it
+     *
+     * This makes Sybil's job a lot harder, as she needs to essentially take over the
+     * whole keyspace.
+     *
+     * @throws IllegalArgumentException if origKey is null
+     */
+    public Hash getRoutingKey(Hash origKey) {
+        return getKey(origKey, _currentModData);
+    }
+    
+    /**
+     * Get the routing key using tomorrow's modData, not today's
+     *
+     * @since 0.9.10
+     */
+    public Hash getNextRoutingKey(Hash origKey) {
+        return getKey(origKey, _nextModData);
+    }
+    
+    /**
+     * Generate a modified (yet consistent) hash from the origKey by generating the
+     * SHA256 of the targetKey with the specified modData appended to it
+     *
+     * @throws IllegalArgumentException if origKey is null
+     */
+    private static Hash getKey(Hash origKey, byte[] modData) {
+        if (origKey == null) throw new IllegalArgumentException("Original key is null");
+        byte modVal[] = new byte[Hash.HASH_LENGTH + LENGTH];
+        System.arraycopy(origKey.getData(), 0, modVal, 0, Hash.HASH_LENGTH);
+        System.arraycopy(modData, 0, modVal, Hash.HASH_LENGTH, LENGTH);
+        return SHA256Generator.getInstance().calculateHash(modVal);
+    }
+
+/****
+    public static void main(String args[]) {
+        Hash k1 = new Hash();
+        byte k1d[] = new byte[Hash.HASH_LENGTH];
+        RandomSource.getInstance().nextBytes(k1d);
+        k1.setData(k1d);
+
+        for (int i = 0; i < 10; i++) {
+            System.out.println("K1:  " + k1);
+            Hash k1m = RoutingKeyGenerator.getInstance().getRoutingKey(k1);
+            System.out.println("MOD: " + new String(RoutingKeyGenerator.getInstance().getModData()));
+            System.out.println("K1M: " + k1m);
+        }
+        try {
+            Thread.sleep(2000);
+        } catch (Throwable t) { // nop
+        }
+    }
+****/
+}
diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java
index 3afd99de260102de22a4d4a1cabb21e59f1c7a20..8fd08eb795f47e7cfb797fd6cc349754543b1aab 100644
--- a/router/java/src/net/i2p/router/Router.java
+++ b/router/java/src/net/i2p/router/Router.java
@@ -1070,7 +1070,7 @@ public class Router implements RouterClock.ClockShiftListener {
             return;
         _eventLog.addEvent(EventLog.CLOCK_SHIFT, Long.toString(delta));
         // update the routing key modifier
-        _context.routingKeyGenerator().generateDateBasedModData();
+        _context.routerKeyGenerator().generateDateBasedModData();
         if (_context.commSystem().countActivePeers() <= 0)
             return;
         if (delta > 0)
diff --git a/router/java/src/net/i2p/router/RouterContext.java b/router/java/src/net/i2p/router/RouterContext.java
index 20b2fe9b7d10ba643854210e13693fc03103142f..fd5c65775953215c6623473f4d9979c3e6e1a55b 100644
--- a/router/java/src/net/i2p/router/RouterContext.java
+++ b/router/java/src/net/i2p/router/RouterContext.java
@@ -10,7 +10,9 @@ import java.util.concurrent.CopyOnWriteArrayList;
 import net.i2p.I2PAppContext;
 import net.i2p.app.ClientAppManager;
 import net.i2p.data.Hash;
+import net.i2p.data.RoutingKeyGenerator;
 import net.i2p.data.router.RouterInfo;
+import net.i2p.data.router.RouterKeyGenerator;
 import net.i2p.internal.InternalClientManager;
 import net.i2p.router.client.ClientManagerFacadeImpl;
 import net.i2p.router.crypto.TransientSessionKeyManager;
@@ -65,6 +67,7 @@ public class RouterContext extends I2PAppContext {
     //private MessageStateMonitor _messageStateMonitor;
     private RouterThrottle _throttle;
     private RouterAppManager _appManager;
+    private RouterKeyGenerator _routingKeyGenerator;
     private final Set<Runnable> _finalShutdownTasks;
     // split up big lock on this to avoid deadlocks
     private volatile boolean _initialized;
@@ -183,6 +186,7 @@ public class RouterContext extends I2PAppContext {
         _messageHistory = new MessageHistory(this);
         _messageRegistry = new OutboundMessageRegistry(this);
         //_messageStateMonitor = new MessageStateMonitor(this);
+        _routingKeyGenerator = new RouterKeyGenerator(this);
         if (!getBooleanProperty("i2p.dummyNetDb"))
             _netDb = new FloodfillNetworkDatabaseFacade(this); // new KademliaNetworkDatabaseFacade(this);
         else
@@ -582,4 +586,35 @@ public class RouterContext extends I2PAppContext {
             _sessionKeyManagerInitialized = true;
         }
     }
+    
+    /**
+     * Determine how much do we want to mess with the keys to turn them 
+     * into something we can route.  This is context specific because we 
+     * may want to test out how things react when peers don't agree on 
+     * how to skew.
+     *
+     * Returns same thing as routerKeyGenerator()
+     *
+     * @return non-null
+     * @since 0.9.16 Overrides I2PAppContext. Returns non-null in RouterContext and null in I2PAppcontext.
+     */
+    @Override
+    public RoutingKeyGenerator routingKeyGenerator() {
+        return _routingKeyGenerator;
+    }
+
+    /**
+     * Determine how much do we want to mess with the keys to turn them 
+     * into something we can route.  This is context specific because we 
+     * may want to test out how things react when peers don't agree on 
+     * how to skew.
+     *
+     * Returns same thing as routingKeyGenerator()
+     *
+     * @return non-null
+     * @since 0.9.16
+     */
+    public RouterKeyGenerator routerKeyGenerator() {
+        return _routingKeyGenerator;
+    }
 }
diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java
index e8a80368c4946514496c7ece8ebc3c0183c7e85a..9358092260ea0e75d9e0f2c3b72718096d18129c 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java
@@ -13,6 +13,7 @@ import net.i2p.data.TunnelId;
 import net.i2p.data.i2np.DatabaseLookupMessage;
 import net.i2p.data.i2np.DatabaseStoreMessage;
 import net.i2p.data.router.RouterInfo;
+import net.i2p.data.router.RouterKeyGenerator;
 import net.i2p.router.Job;
 import net.i2p.router.JobImpl;
 import net.i2p.router.OutNetMessage;
@@ -176,16 +177,17 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
      */
     public void flood(DatabaseEntry ds) {
         Hash key = ds.getHash();
-        Hash rkey = _context.routingKeyGenerator().getRoutingKey(key);
+        RouterKeyGenerator gen = _context.routerKeyGenerator();
+        Hash rkey = gen.getRoutingKey(key);
         FloodfillPeerSelector sel = (FloodfillPeerSelector)getPeerSelector();
         List<Hash> peers = sel.selectFloodfillParticipants(rkey, MAX_TO_FLOOD, getKBuckets());
         // todo key cert skip?
-        long until = _context.routingKeyGenerator().getTimeTillMidnight();
+        long until = gen.getTimeTillMidnight();
         if (until < NEXT_RKEY_LS_ADVANCE_TIME ||
             (ds.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO && until < NEXT_RKEY_RI_ADVANCE_TIME)) {
-            // to avoid lookup failures after midnight, also flood to some closest to the
+            // to avoid lookup faulures after midnight, also flood to some closest to the
             // next routing key for a period of time before midnight.
-            Hash nkey = _context.routingKeyGenerator().getNextRoutingKey(key);
+            Hash nkey = gen.getNextRoutingKey(key);
             List<Hash> nextPeers = sel.selectFloodfillParticipants(nkey, NEXT_FLOOD_QTY, getKBuckets());
             int i = 0;
             for (Hash h : nextPeers) {
diff --git a/router/java/src/net/i2p/router/tasks/UpdateRoutingKeyModifierJob.java b/router/java/src/net/i2p/router/tasks/UpdateRoutingKeyModifierJob.java
index 2952c2eda430fc37df6a487414ea52a48995e838..5b367620be13d277279258cbc486b23c67d3239f 100644
--- a/router/java/src/net/i2p/router/tasks/UpdateRoutingKeyModifierJob.java
+++ b/router/java/src/net/i2p/router/tasks/UpdateRoutingKeyModifierJob.java
@@ -8,7 +8,7 @@ package net.i2p.router.tasks;
  *
  */
 
-import net.i2p.data.RoutingKeyGenerator;
+import net.i2p.data.router.RouterKeyGenerator;
 import net.i2p.router.JobImpl;
 import net.i2p.router.RouterContext;
 import net.i2p.util.Log;
@@ -33,7 +33,7 @@ public class UpdateRoutingKeyModifierJob extends JobImpl {
     public String getName() { return "Update Routing Key Modifier"; }
 
     public void runJob() {
-        RoutingKeyGenerator gen = getContext().routingKeyGenerator();
+        RouterKeyGenerator gen = getContext().routerKeyGenerator();
         // make sure we requeue quickly if just before midnight
         long delay = Math.max(5, Math.min(MAX_DELAY_FAILSAFE, gen.getTimeTillMidnight()));
         // TODO tell netdb if mod data changed?