diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
index dc9dfd2fcccb6dec67173603a8b19e254d94e971..b11f954e7e3dd097f2890fcabc843cadc3f96b42 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
@@ -552,14 +552,14 @@ public class I2PTunnel implements Logging, EventDispatcher {
      * Integer port number if the client is listening
      * sharedClient parameter is a String "true" or "false"
      *
-     * @param args {portNumber, destinationBase64 or "file:filename"[, sharedClient]}
+     * @param args {portNumber, destinationBase64 or "file:filename"[, sharedClient [, privKeyFile]]}
      * @param l logger to receive events and output
      */
     public void runClient(String args[], Logging l) {
         boolean isShared = true;
-        if (args.length == 3)
+        if (args.length >= 3)
             isShared = Boolean.valueOf(args[2].trim()).booleanValue();
-        if ( (args.length == 2) || (args.length == 3) ) {
+        if (args.length >= 2) {
             int portNum = -1;
             try {
                 portNum = Integer.parseInt(args[0]);
@@ -572,7 +572,10 @@ public class I2PTunnel implements Logging, EventDispatcher {
             I2PTunnelTask task;
             ownDest = !isShared;
             try {
-                task = new I2PTunnelClient(portNum, args[1], l, ownDest, (EventDispatcher) this, this);
+                String privateKeyFile = null;
+                if (args.length >= 4)
+                    privateKeyFile = args[3];
+                task = new I2PTunnelClient(portNum, args[1], l, ownDest, (EventDispatcher) this, this, privateKeyFile);
                 addtask(task);
                 notifyEvent("clientTaskId", Integer.valueOf(task.getId()));
             } catch (IllegalArgumentException iae) {
@@ -581,7 +584,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
                 notifyEvent("clientTaskId", Integer.valueOf(-1));
             }
         } else {
-            l.log("client <port> <pubkey>[,<pubkey>]|file:<pubkeyfile>[ <sharedClient>]");
+            l.log("client <port> <pubkey>[,<pubkey>]|file:<pubkeyfile>[ <sharedClient>] [<privKeyFile>]");
             l.log("  creates a client that forwards port to the pubkey.\n"
                   + "  use 0 as port to get a free port assigned.  If you specify\n"
                   + "  a comma delimited list of pubkeys, it will rotate among them\n"
@@ -720,11 +723,11 @@ public class I2PTunnel implements Logging, EventDispatcher {
      * Also sets "ircclientStatus" = "ok" or "error" after the client tunnel has started.
      * parameter sharedClient is a String, either "true" or "false"
      *
-     * @param args {portNumber,destinationBase64 or "file:filename" [, sharedClient]}
+     * @param args {portNumber,destinationBase64 or "file:filename" [, sharedClient [, privKeyFile]]}
      * @param l logger to receive events and output
      */
     public void runIrcClient(String args[], Logging l) {
-        if (args.length >= 2 && args.length <= 3) {
+        if (args.length >= 2) {
             int port = -1;
             try {
                 port = Integer.parseInt(args[0]);
@@ -751,7 +754,10 @@ public class I2PTunnel implements Logging, EventDispatcher {
             I2PTunnelTask task;
             ownDest = !isShared;
             try {
-                task = new I2PTunnelIRCClient(port, args[1],l, ownDest, (EventDispatcher) this, this);
+                String privateKeyFile = null;
+                if (args.length >= 4)
+                    privateKeyFile = args[3];
+                task = new I2PTunnelIRCClient(port, args[1], l, ownDest, (EventDispatcher) this, this, privateKeyFile);
                 addtask(task);
                 notifyEvent("ircclientTaskId", Integer.valueOf(task.getId()));
             } catch (IllegalArgumentException iae) {
@@ -760,7 +766,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
                 notifyEvent("ircclientTaskId", Integer.valueOf(-1));
             }
         } else {
-            l.log("ircclient <port> [<sharedClient>]");
+            l.log("ircclient <port> [<sharedClient> [<privKeyFile>]]");
             l.log("  creates a client that filter IRC protocol.");
             l.log("  <sharedClient> (optional) indicates if this client shares tunnels with other clients (true of false)");
             notifyEvent("ircclientTaskId", Integer.valueOf(-1));
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java
index 4739a07f47bcec19361a09363e841c35967c4898..502bb28d5ca9724fe149a9c9eedf256c0b7d6fdc 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java
@@ -31,8 +31,8 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
      */
     public I2PTunnelClient(int localPort, String destinations, Logging l, 
                            boolean ownDest, EventDispatcher notifyThis, 
-                           I2PTunnel tunnel) throws IllegalArgumentException {
-        super(localPort, ownDest, l, notifyThis, "SynSender", tunnel);
+                           I2PTunnel tunnel, String pkf) throws IllegalArgumentException {
+        super(localPort, ownDest, l, notifyThis, "SynSender", tunnel, pkf);
 
         if (waitEventValue("openBaseClientResult").equals("error")) {
             notifyEvent("openClientResult", "error");
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java
index c926156f42f07dfdd1e23658c70df6c8b1ff9d76..fadbf9fa1cc8d9d128b20174d78db1ad92921644 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java
@@ -3,6 +3,7 @@
  */
 package net.i2p.i2ptunnel;
 
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InterruptedIOException;
 import java.net.ConnectException;
@@ -59,6 +60,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
     private byte[] pubkey;
 
     private String handlerName;
+    private String privKeyFile;
 
     private Object conLock = new Object();
     
@@ -91,18 +93,28 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
     //    I2PTunnelClientBase(localPort, ownDest, l, (EventDispatcher)null);
     //}
 
+    public I2PTunnelClientBase(int localPort, boolean ownDest, Logging l, 
+                               EventDispatcher notifyThis, String handlerName, 
+                               I2PTunnel tunnel) throws IllegalArgumentException {
+        this(localPort, ownDest, l, notifyThis, handlerName, tunnel, null);
+    }
+
     /**
+     * @param privKeyFile null to generate a transient key
+     *
      * @throws IllegalArgumentException if the I2CP configuration is b0rked so
      *                                  badly that we cant create a socketManager
      */
     public I2PTunnelClientBase(int localPort, boolean ownDest, Logging l, 
                                EventDispatcher notifyThis, String handlerName, 
-                               I2PTunnel tunnel) throws IllegalArgumentException{
+                               I2PTunnel tunnel, String pkf) throws IllegalArgumentException{
         super(localPort + " (uninitialized)", notifyThis, tunnel);
         _clientId = ++__clientId;
         this.localPort = localPort;
         this.l = l;
         this.handlerName = handlerName + _clientId;
+        this.privKeyFile = pkf;
+
 
         _context = tunnel.getContext();
         _context.statManager().createRateStat("i2ptunnel.client.closeBacklog", "How many pending sockets remain when we close one due to backlog?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
@@ -195,28 +207,34 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
     private static I2PSocketManager socketManager;
 
     protected synchronized I2PSocketManager getSocketManager() {
-        return getSocketManager(getTunnel());
+        return getSocketManager(getTunnel(), this.privKeyFile);
     }
     protected static synchronized I2PSocketManager getSocketManager(I2PTunnel tunnel) {
+        return getSocketManager(tunnel, null);
+    }
+    protected static synchronized I2PSocketManager getSocketManager(I2PTunnel tunnel, String pkf) {
         if (socketManager != null) {
             I2PSession s = socketManager.getSession();
             if ( (s == null) || (s.isClosed()) ) {
                 _log.info("Building a new socket manager since the old one closed [s=" + s + "]");
-                socketManager = buildSocketManager(tunnel);
+                socketManager = buildSocketManager(tunnel, pkf);
             } else {
                 _log.info("Not building a new socket manager since the old one is open [s=" + s + "]");
             }
         } else {
             _log.info("Building a new socket manager since there is no other one");
-            socketManager = buildSocketManager(tunnel);
+            socketManager = buildSocketManager(tunnel, pkf);
         }
         return socketManager;
     }
 
     protected I2PSocketManager buildSocketManager() {
-        return buildSocketManager(getTunnel());
+        return buildSocketManager(getTunnel(), this.privKeyFile);
     }
     protected static I2PSocketManager buildSocketManager(I2PTunnel tunnel) {
+        return buildSocketManager(tunnel, null);
+    }
+    protected static I2PSocketManager buildSocketManager(I2PTunnel tunnel, String pkf) {
         Properties props = new Properties();
         props.putAll(tunnel.getClientOptions());
         int portNum = 7654;
@@ -230,10 +248,22 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
         
         I2PSocketManager sockManager = null;
         while (sockManager == null) {
-            // if persistent dest
-            //      sockManager = I2PSocketManagerFactory.createManager(privData, tunnel.host, portNum, props);
-            // else
-            sockManager = I2PSocketManagerFactory.createManager(tunnel.host, portNum, props);
+            if (pkf != null) {
+                // Persistent client dest
+                FileInputStream fis = null;
+                try {
+                    fis = new FileInputStream(pkf);
+                    sockManager = I2PSocketManagerFactory.createManager(fis, tunnel.host, portNum, props);
+                } catch (IOException ioe) {
+                    _log.error("Error opening key file", ioe);
+                    // this is going to loop but if we break we'll get a NPE
+                } finally {
+                    if (fis != null)
+                        try { fis.close(); } catch (IOException ioe) {}
+                }
+            } else {
+                sockManager = I2PSocketManagerFactory.createManager(tunnel.host, portNum, props);
+            }
             
             if (sockManager == null) {
                 _log.log(Log.CRIT, "Unable to create socket manager");
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java
index 5b223b1a42655d604ca983787f28dbd9d367d071..732c222a7199cc92b27aa548cc4a491e7e0939c6 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java
@@ -39,12 +39,12 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
                               Logging l, 
                               boolean ownDest,
                               EventDispatcher notifyThis,
-                              I2PTunnel tunnel) throws IllegalArgumentException {
+                              I2PTunnel tunnel, String pkf) throws IllegalArgumentException {
         super(localPort, 
               ownDest, 
               l, 
               notifyThis, 
-              "IRCHandler " + (++__clientId), tunnel);
+              "IRCHandler " + (++__clientId), tunnel, pkf);
         
         StringTokenizer tok = new StringTokenizer(destinations, ",");
         dests = new ArrayList(1);
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java
index 727b181581d4c17e92c28b1e9f2f89f27a9a909e..419e5a899d9e262353177e6c6bd57d5020dc16c0 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java
@@ -192,7 +192,12 @@ public class TunnelController implements Logging {
         String listenPort = getListenPort();
         String dest = getTargetDestination();
         String sharedClient = getSharedClient();
-        _tunnel.runIrcClient(new String[] { listenPort, dest, sharedClient }, this);
+        if (getPersistentClientKey()) {
+            String privKeyFile = getPrivKeyFile(); 
+            _tunnel.runIrcClient(new String[] { listenPort, dest, sharedClient, privKeyFile }, this);
+        } else {
+            _tunnel.runIrcClient(new String[] { listenPort, dest, sharedClient }, this);
+        }
     }
     
     private void startSocksClient() {
@@ -264,7 +269,12 @@ public class TunnelController implements Logging {
         String listenPort = getListenPort(); 
         String dest = getTargetDestination();
         String sharedClient = getSharedClient();
-        _tunnel.runClient(new String[] { listenPort, dest, sharedClient }, this);
+        if (getPersistentClientKey()) {
+            String privKeyFile = getPrivKeyFile(); 
+            _tunnel.runClient(new String[] { listenPort, dest, sharedClient, privKeyFile }, this);
+        } else {
+            _tunnel.runClient(new String[] { listenPort, dest, sharedClient }, this);
+        }
     }
 
     private void startServer() {
@@ -395,7 +405,7 @@ public class TunnelController implements Logging {
     public String getProxyList() { return _config.getProperty("proxyList"); }
     public String getSharedClient() { return _config.getProperty("sharedClient", "true"); }
     public boolean getStartOnLoad() { return "true".equalsIgnoreCase(_config.getProperty("startOnLoad", "true")); }
-    public boolean getPersistentClientKey() { return Boolean.valueOf(_config.getProperty("persistentClientKey")).booleanValue(); }
+    public boolean getPersistentClientKey() { return Boolean.valueOf(_config.getProperty("option.persistentClientKey")).booleanValue(); }
     public String getMyDestination() {
         if (_tunnel != null) {
             List sessions = _tunnel.getSessions();
diff --git a/apps/i2ptunnel/jsp/editClient.jsp b/apps/i2ptunnel/jsp/editClient.jsp
index 3c046baddc1958a2d3e7a2b72daee98d61a611a6..178f564ef9439736a5cc502ba68b3f2024e17eb2 100644
--- a/apps/i2ptunnel/jsp/editClient.jsp
+++ b/apps/i2ptunnel/jsp/editClient.jsp
@@ -351,6 +351,7 @@
                 <hr />
             </div>
 
+         <% if ("client".equals(tunnelType) || "ircclient".equals(tunnelType)) { %>
             <div id="optionsField" class="rowItem">
                 <label for="privKeyFile" accesskey="k">
                     Persistent private <span class="accessKey">k</span>ey:
@@ -365,10 +366,18 @@
                 <label>File:</label>
                 <input type="text" size="30" id="clientHost" name="privKeyFile" title="Path to Private Key File" value="<%=editBean.getPrivateKeyFile(curTunnel)%>" class="freetext" />               
             </div>
+            <div id="destinationField" class="rowItem">
+                <label for="localDestination" accesskey="L">
+                    <span class="accessKey">L</span>ocal destination:
+                </label>
+                <textarea rows="1" cols="60" readonly="readonly" id="localDestination" title="Read Only: Local Destination (if known)" wrap="off"><%=editBean.getDestinationBase64(curTunnel)%></textarea>               
+                <span class="comment">(if known)</span>
+            </div>
 
             <div class="subdivider">
                 <hr />
             </div>
+         <% } %>
            
             <div id="customOptionsField" class="rowItem">
                 <label for="customOptions" accesskey="u">