diff --git a/core/java/src/net/i2p/crypto/CertUtil.java b/core/java/src/net/i2p/crypto/CertUtil.java
index ecb6a5f5597ea7dcce8099985f2bab4f5b895b64..97032065acbadfc7d11ccfc52f3f0b55fcd4dced 100644
--- a/core/java/src/net/i2p/crypto/CertUtil.java
+++ b/core/java/src/net/i2p/crypto/CertUtil.java
@@ -97,9 +97,9 @@ public final class CertUtil {
      *  Writes a certificate in base64 format.
      *  Does NOT close the stream. Throws on all errors.
      *
-     *  @since 0.9.24, pulled out of saveCert()
+     *  @since 0.9.24, pulled out of saveCert(), public since 0.9.25
      */
-    private static void exportCert(Certificate cert, OutputStream out)
+    public static void exportCert(Certificate cert, OutputStream out)
                                                 throws IOException, CertificateEncodingException {
         // Get the encoded form which is suitable for exporting
         byte[] buf = cert.getEncoded();
@@ -365,7 +365,7 @@ public final class CertUtil {
      *  @throws CRLException if the crl does not support encoding
      *  @since 0.9.25
      */
-    private static void exportCRL(X509CRL crl, OutputStream out)
+    public static void exportCRL(X509CRL crl, OutputStream out)
                                                 throws IOException, CRLException {
         byte[] buf = crl.getEncoded();
         writePEM(buf, "X509 CRL", out);
diff --git a/core/java/src/net/i2p/crypto/KeyStoreUtil.java b/core/java/src/net/i2p/crypto/KeyStoreUtil.java
index 1de4687de21ab7581236a99283848293cb9d3d7d..24844c2a55dac4c65c84966ea8849d56b1c81c15 100644
--- a/core/java/src/net/i2p/crypto/KeyStoreUtil.java
+++ b/core/java/src/net/i2p/crypto/KeyStoreUtil.java
@@ -9,12 +9,16 @@ import java.math.BigInteger;
 import java.security.GeneralSecurityException;
 import java.security.KeyStore;
 import java.security.PrivateKey;
+import java.security.PublicKey;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateExpiredException;
 import java.security.cert.CertificateNotYetValidException;
 import java.security.cert.X509Certificate;
+import java.security.cert.X509CRL;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Enumeration;
+import java.util.EnumSet;
 import java.util.List;
 import java.util.Locale;
 
@@ -430,6 +434,9 @@ public final class KeyStoreUtil {
     /**
      *  Create a keypair and store it in the keystore at ks, creating it if necessary.
      *
+     *  For new code, the createKeys() with the SigType argument is recommended over this one,
+     *  as it throws exceptions, and returns the certificate and CRL.
+     *
      *  Warning, may take a long time.
      *
      *  @param ks path to the keystore
@@ -447,6 +454,137 @@ public final class KeyStoreUtil {
      */
     public static boolean createKeys(File ks, String ksPW, String alias, String cname, String ou,
                                      int validDays, String keyAlg, int keySize, String keyPW) {
+        boolean useKeytool = I2PAppContext.getGlobalContext().getBooleanProperty("crypto.useExternalKeytool");
+        if (useKeytool) {
+            return createKeysCLI(ks, ksPW, alias, cname, ou, validDays, keyAlg, keySize, keyPW);
+        } else {
+            try {
+                createKeysAndCRL(ks, ksPW, alias, cname, ou, validDays, keyAlg, keySize, keyPW);
+                return true;
+            } catch (GeneralSecurityException gse) {
+                error("Create keys error", gse);
+                return false;
+            } catch (IOException ioe) {
+                error("Create keys error", ioe);
+                return false;
+            }
+        }
+    }
+
+    /**
+     *  New way - Native Java, does not call out to keytool.
+     *  Create a keypair and store it in the keystore at ks, creating it if necessary.
+     *
+     *  This returns the public key, private key, certificate, and CRL in an array.
+     *  All of these are Java classes. Keys may be converted to I2P classes with SigUtil.
+     *  The private key and selfsigned cert are stored in the keystore.
+     *  The public key may be derived from the private key with KeyGenerator.getSigningPublicKey().
+     *  The public key certificate may be stored separately with
+     *  CertUtil.saveCert() if desired.
+     *  The CRL is not stored by this method, store it with
+     *  CertUtil.saveCRL() or CertUtil.exportCRL() if desired.
+     *
+     *  Throws on all errors.
+     *  Warning, may take a long time.
+     *
+     *  @param ks path to the keystore
+     *  @param ksPW the keystore password
+     *  @param alias the name of the key
+     *  @param cname e.g. randomstuff.console.i2p.net
+     *  @param ou e.g. console
+     *  @param validDays e.g. 3652 (10 years)
+     *  @param keyAlg e.g. DSA , RSA, EC
+     *  @param keySize e.g. 1024
+     *  @param keyPW the key password, must be at least 6 characters
+     *  @return all you need:
+     *      rv[0] is a Java PublicKey
+     *      rv[1] is a Java PrivateKey
+     *      rv[2] is a Java X509Certificate
+     *      rv[3] is a Java X509CRL
+     *  @since 0.9.25
+     */
+    public static Object[] createKeysAndCRL(File ks, String ksPW, String alias, String cname, String ou,
+                                            int validDays, String keyAlg, int keySize, String keyPW)
+                                                throws GeneralSecurityException, IOException {
+        String algoName = getSigAlg(keySize, keyAlg);
+        SigType type = null;
+        for (SigType t : EnumSet.allOf(SigType.class)) {
+            if (t.getAlgorithmName().equals(algoName)) {
+                type = t;
+                break;
+            }
+        }
+        if (type == null)
+            throw new GeneralSecurityException("Unsupported algorithm/size: " + keyAlg + '/' + keySize);
+        return createKeysAndCRL(ks, ksPW, alias, cname, ou, validDays, type, keyPW);
+    }
+
+    /**
+     *  New way - Native Java, does not call out to keytool.
+     *  Create a keypair and store it in the keystore at ks, creating it if necessary.
+     *
+     *  This returns the public key, private key, certificate, and CRL in an array.
+     *  All of these are Java classes. Keys may be converted to I2P classes with SigUtil.
+     *  The private key and selfsigned cert are stored in the keystore.
+     *  The public key may be derived from the private key with KeyGenerator.getSigningPublicKey().
+     *  The public key certificate may be stored separately with
+     *  CertUtil.saveCert() if desired.
+     *  The CRL is not stored by this method, store it with
+     *  CertUtil.saveCRL() or CertUtil.exportCRL() if desired.
+     *
+     *  Throws on all errors.
+     *  Warning, may take a long time.
+     *
+     *  @param ks path to the keystore
+     *  @param ksPW the keystore password
+     *  @param alias the name of the key
+     *  @param cname e.g. randomstuff.console.i2p.net
+     *  @param ou e.g. console
+     *  @param validDays e.g. 3652 (10 years)
+     *  @param keyAlg e.g. DSA , RSA, EC
+     *  @param keySize e.g. 1024
+     *  @param keyPW the key password, must be at least 6 characters
+     *  @return all you need:
+     *      rv[0] is a Java PublicKey
+     *      rv[1] is a Java PrivateKey
+     *      rv[2] is a Java X509Certificate
+     *      rv[3] is a Java X509CRL
+     *  @since 0.9.25
+     */
+    public static Object[] createKeysAndCRL(File ks, String ksPW, String alias, String cname, String ou,
+                                            int validDays, SigType type, String keyPW)
+                                                throws GeneralSecurityException, IOException {
+        Object[] rv = SelfSignedGenerator.generate(cname, ou, "XX", "I2P Anonymous Network", "XX", "XX", validDays, type);
+        PublicKey jpub = (PublicKey) rv[0];
+        PrivateKey jpriv = (PrivateKey) rv[1];
+        X509Certificate cert = (X509Certificate) rv[2];
+        X509CRL crl = (X509CRL) rv[3];
+        List<X509Certificate> certs = Collections.singletonList(cert);
+        storePrivateKey(ks, ksPW, alias, keyPW, jpriv, certs);
+        return rv;
+    }
+
+    /**
+     *  OLD way - keytool
+     *  Create a keypair and store it in the keystore at ks, creating it if necessary.
+     *
+     *  Warning, may take a long time.
+     *
+     *  @param ks path to the keystore
+     *  @param ksPW the keystore password
+     *  @param alias the name of the key
+     *  @param cname e.g. randomstuff.console.i2p.net
+     *  @param ou e.g. console
+     *  @param validDays e.g. 3652 (10 years)
+     *  @param keyAlg e.g. DSA , RSA, EC
+     *  @param keySize e.g. 1024
+     *  @param keyPW the key password, must be at least 6 characters
+     *
+     *  @return success
+     *  @since 0.8.3, consolidated from RouterConsoleRunner and SSLClientListenerRunner in 0.9.9
+     */
+    private static boolean createKeysCLI(File ks, String ksPW, String alias, String cname, String ou,
+                                     int validDays, String keyAlg, int keySize, String keyPW) {
         if (ks.exists()) {
             try {
                 if (getCert(ks, ksPW, alias) != null) {
diff --git a/core/java/src/net/i2p/crypto/SU3File.java b/core/java/src/net/i2p/crypto/SU3File.java
index 3c8154c6eeab3e9f87aed62b0f813e46d9f2e196..38302e52ec9d92a3aa4bb9c14ee13787cefb9870 100644
--- a/core/java/src/net/i2p/crypto/SU3File.java
+++ b/core/java/src/net/i2p/crypto/SU3File.java
@@ -15,6 +15,8 @@ import java.security.GeneralSecurityException;
 import java.security.MessageDigest;
 import java.security.PrivateKey;
 import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.security.cert.X509CRL;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.EnumSet;
@@ -33,6 +35,7 @@ import net.i2p.data.DataFormatException;
 import net.i2p.data.DataHelper;
 import net.i2p.data.Signature;
 import net.i2p.data.SimpleDataStructure;
+import net.i2p.util.SecureFileOutputStream;
 
 /**
  *  Succesor to the ".sud" format used in TrustedUpdate.
@@ -538,10 +541,11 @@ public class SU3File {
             String ctype = null;
             String ftype = null;
             String kfile = null;
+            String crlfile = null;
             String kspass = KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD;
             boolean error = false;
             boolean shouldVerify = true;
-            Getopt g = new Getopt("SU3File", args, "t:c:f:k:xp:");
+            Getopt g = new Getopt("SU3File", args, "t:c:f:k:xp:r:");
             int c;
             while ((c = g.getopt()) != -1) {
               switch (c) {
@@ -561,6 +565,10 @@ public class SU3File {
                     kfile = g.getOptarg();
                     break;
 
+                case 'r':
+                    crlfile = g.getOptarg();
+                    break;
+
                 case 'x':
                     shouldVerify = false;
                     break;
@@ -598,7 +606,10 @@ public class SU3File {
             } else if ("verifysig".equals(cmd)) {
                 ok = verifySigCLI(a.get(0), kfile);
             } else if ("keygen".equals(cmd)) {
-                ok = genKeysCLI(stype, a.get(0), a.get(1), a.get(2), kspass);
+                Properties props = new Properties();
+                props.setProperty("prng.bufferSize", "16384");
+                new I2PAppContext(props);
+                ok = genKeysCLI(stype, a.get(0), a.get(1), crlfile, a.get(2), kspass);
             } else if ("extract".equals(cmd)) {
                 ok = extractCLI(a.get(0), a.get(1), shouldVerify, kfile);
             } else {
@@ -614,9 +625,10 @@ public class SU3File {
     }
 
     private static final void showUsageCLI() {
-        System.err.println("Usage: SU3File keygen       [-t type|code] [-p keystorepw] publicKeyFile keystore.ks you@mail.i2p\n" +
+        System.err.println("Usage: SU3File keygen       [-t type|code] [-p keystorepw] [-r crlFile.crl] publicKeyFile.crt keystore.ks you@mail.i2p\n" +
                            "       SU3File sign         [-t type|code] [-c type|code] [-f type|code] [-p keystorepw] inputFile.zip signedFile.su3 keystore.ks version you@mail.i2p\n" +
                            "       SU3File bulksign     [-t type|code] [-c type|code] [-p keystorepw] directory keystore.ks version you@mail.i2p\n" +
+                           "                            (signs all .zip and .xml files in the directory)\n" +
                            "       SU3File showversion  signedFile.su3\n" +
                            "       SU3File verifysig    [-k file.crt] signedFile.su3  ## -k use this pubkey cert for verification\n" +
                            "       SU3File extract      [-x] [-k file.crt] signedFile.su3 outFile   ## -x don't check sig");
@@ -750,7 +762,7 @@ public class SU3File {
         int success = 0;
         for (File in : files) {
             String inputFile = in.getPath();
-            if (!inputFile.endsWith(".zip"))
+            if (!inputFile.endsWith(".zip") && !inputFile.endsWith(".xml"))
                 continue;
             String signedFile = inputFile.substring(0, inputFile.length() - 4) + ".su3";
             boolean rv = signCLI(stype, ctype, null, inputFile, signedFile,
@@ -897,26 +909,29 @@ public class SU3File {
     }
 
     /**
+     *  @param crlFile may be null; non-null to save
      *  @return success
      *  @since 0.9.9
      */
     private static final boolean genKeysCLI(String stype, String publicKeyFile, String privateKeyFile,
-                                            String alias, String kspass) {
+                                            String crlFile, String alias, String kspass) {
         SigType type = stype == null ? SigType.getByCode(Integer.valueOf(DEFAULT_SIG_CODE)) : SigType.parseSigType(stype);
         if (type == null) {
             System.out.println("Signature type " + stype + " is not supported");
             return false;
         }
-        return genKeysCLI(type, publicKeyFile, privateKeyFile, alias, kspass);
+        return genKeysCLI(type, publicKeyFile, privateKeyFile, crlFile, alias, kspass);
     }
 
     /**
      *  Writes Java-encoded keys (X.509 for public and PKCS#8 for private)
+     *
+     *  @param crlFile may be null; non-null to save
      *  @return success
      *  @since 0.9.9
      */
     private static final boolean genKeysCLI(SigType type, String publicKeyFile, String privateKeyFile,
-                                            String alias, String kspass) {
+                                            String crlFile, String alias, String kspass) {
         File pubFile = new File(publicKeyFile);
         if (pubFile.exists()) {
             System.out.println("Error: Not overwriting file " + publicKeyFile);
@@ -948,24 +963,29 @@ public class SU3File {
         } catch (IOException ioe) {
             return false;
         }
-        int keylen = type.getPubkeyLen() * 8;
-        if (type.getBaseAlgorithm() == SigAlgo.EC) {
-            keylen /= 2;
-            if (keylen == 528)
-                keylen = 521;
-        }
-        boolean success = KeyStoreUtil.createKeys(ksFile, kspass, alias,
-                                                  alias, "I2P", 3652, type.getBaseAlgorithm().getName(),
-                                                  keylen, keypw);
-        if (!success) {
+        OutputStream out = null;
+        try {
+            Object[] rv =  KeyStoreUtil.createKeysAndCRL(ksFile, kspass, alias,
+                                                         alias, "I2P", 3652, type, keypw);
+            X509Certificate cert = (X509Certificate) rv[2];
+            out = new SecureFileOutputStream(publicKeyFile);
+            CertUtil.exportCert(cert, out);
+            if (crlFile != null) {
+                out.close();
+                X509CRL crl = (X509CRL) rv[3];
+                out = new SecureFileOutputStream(crlFile);
+                CertUtil.exportCRL(crl, out);
+            }
+        } catch (GeneralSecurityException gse) {
             System.err.println("Error creating keys for " + alias);
+            gse.printStackTrace();
             return false;
-        }
-        File outfile = new File(publicKeyFile);
-        success = KeyStoreUtil.exportCert(ksFile, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD, alias, outfile);
-        if (!success) {
-            System.err.println("Error writing public key for " + alias + " to " + outfile);
+        } catch (IOException ioe) {
+            System.err.println("Error creating keys for " + alias);
+            ioe.printStackTrace();
             return false;
+        } finally {
+            if (out != null) try { out.close(); } catch (IOException ioe) {}
         }
         return true;
     }
diff --git a/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java b/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java
index 39b81458a243744683d001dcabc7957ffdb684a0..cdb0811dc7b52a7d83cfaeaf1a84e1ea81e29ea4 100644
--- a/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java
+++ b/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java
@@ -8,6 +8,8 @@ import java.security.KeyStore;
 import java.security.GeneralSecurityException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.security.cert.X509CRL;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
@@ -51,6 +53,7 @@ public class FamilyKeyCrypto {
     public static final String PROP_FAMILY_NAME = "netdb.family.name";
     public static final String PROP_KEY_PASSWORD = "netdb.family.keyPassword";
     public static final String CERT_SUFFIX = ".crt";
+    public static final String CRL_SUFFIX = ".crl";
     public static final String KEYSTORE_PREFIX = "family-";
     public static final String KEYSTORE_SUFFIX = ".ks";
     public static final String CN_SUFFIX = ".family.i2p.net";
@@ -61,6 +64,7 @@ public class FamilyKeyCrypto {
     private static final int DEFAULT_KEY_SIZE = SigType.ECDSA_SHA256_P256.isAvailable() ? 256 : 1024;
     private static final String KS_DIR = "keystore";
     private static final String CERT_DIR = "certificates/family";
+    private static final String CRL_DIR = "crls";
     public static final String OPT_NAME = "family";
     public static final String OPT_SIG = "family.sig";
     public static final String OPT_KEY = "family.key";
@@ -270,11 +274,12 @@ public class FamilyKeyCrypto {
                 throw new GeneralSecurityException(s);
             }
         }
-        createKeyStore(ks);
 
-        // Now read it back out of the new keystore and save it in ascii form
-        // where the clients can get to it.
-        exportCert(ks);
+        try {
+            createKeyStore(ks);
+        } catch (IOException ioe) {
+            throw new GeneralSecurityException("Failed to create NetDb family keystore", ioe);
+        }
     }
 
 
@@ -286,26 +291,22 @@ public class FamilyKeyCrypto {
      *
      * @throws GeneralSecurityException on all errors
      */
-    private void createKeyStore(File ks) throws GeneralSecurityException {
+    private void createKeyStore(File ks) throws GeneralSecurityException, IOException {
         // make a random 48 character password (30 * 8 / 5)
         String keyPassword = KeyStoreUtil.randomString();
         // and one for the cname
         String cname = _fname + CN_SUFFIX;
 
-        boolean success = KeyStoreUtil.createKeys(ks, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD, _fname, cname, "family",
+        Object[] rv = KeyStoreUtil.createKeysAndCRL(ks, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD, _fname, cname, "family",
                                                   DEFAULT_KEY_VALID_DAYS, DEFAULT_KEY_ALGORITHM,
                                                   DEFAULT_KEY_SIZE, keyPassword);
-        if (success) {
-            success = ks.exists();
-            if (success) {
+
                 Map<String, String> changes = new HashMap<String, String>();
                 changes.put(PROP_KEYSTORE_PASSWORD, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD);
                 changes.put(PROP_KEY_PASSWORD, keyPassword);
                 changes.put(PROP_FAMILY_NAME, _fname);
                 _context.router().saveConfig(changes, null);
-            }
-        }
-        if (success) {
+
             _log.logAlways(Log.INFO, "Created new private key for netdb family \"" + _fname +
                            "\" in keystore: " + ks.getAbsolutePath() + "\n" +
                            "Copy the keystore to the other routers in the family,\n" +
@@ -314,27 +315,22 @@ public class FamilyKeyCrypto {
                            PROP_KEYSTORE_PASSWORD + '=' + KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD + '\n' +
                            PROP_KEY_PASSWORD + '=' + keyPassword);
 
-        } else {
-            String s = "Failed to create NetDb family keystore.\n" +
-                       "This is for the Sun/Oracle keytool, others may be incompatible.\n" +
-                       "If you create the keystore manually, you must add " + PROP_KEYSTORE_PASSWORD + " and " + PROP_KEY_PASSWORD +
-                       " to " + (new File(_context.getConfigDir(), "router.config")).getAbsolutePath();
-            _log.error(s);
-            throw new GeneralSecurityException(s);
-        }
+        X509Certificate cert = (X509Certificate) rv[2];
+        exportCert(cert);
+        X509CRL crl = (X509CRL) rv[3];
+        exportCRL(ks.getParentFile(), crl);
     }
 
     /** 
-     * Pull the cert back OUT of the keystore and save it as ascii
+     * Save the public key certificate
      * so the clients can get to it.
      */
-    private void exportCert(File ks) {
+    private void exportCert(X509Certificate cert) {
         File sdir = new SecureDirectory(_context.getConfigDir(), CERT_DIR);
         if (sdir.exists() || sdir.mkdirs()) {
-            String ksPass = _context.getProperty(PROP_KEYSTORE_PASSWORD, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD);
             String name = _fname.replace("@", "_at_") + CERT_SUFFIX;
             File out = new File(sdir, name);
-            boolean success = KeyStoreUtil.exportCert(ks, ksPass, _fname, out);
+            boolean success = CertUtil.saveCert(cert, out);
             if (success) {
                 _log.logAlways(Log.INFO, "Created new public key certificate for netdb family \"" + _fname +
                            "\" in file: " + out.getAbsolutePath() + "\n" +
@@ -342,10 +338,34 @@ public class FamilyKeyCrypto {
                            "Copy the certificate to the directory $I2P/" + CERT_DIR + " for each of the other routers in the family.\n" +
                            "Give this certificate to an I2P developer for inclusion in the next I2P release.");
             } else {
-                _log.error("Error getting SSL cert to save as ASCII");
+                _log.error("Error saving family key certificate");
+            }
+        } else {
+            _log.error("Error saving family key certificate");
+        }
+    }
+
+    /** 
+     * Save the CRL just in case.
+     * @param ksdir parent of directory to save in
+     * @since 0.9.25
+     */
+    private void exportCRL(File ksdir, X509CRL crl) {
+        File sdir = new SecureDirectory(ksdir, CRL_DIR);
+        if (sdir.exists() || sdir.mkdirs()) {
+            String name = KEYSTORE_PREFIX + _fname.replace("@", "_at_") + '-' + System.currentTimeMillis() + CRL_SUFFIX;
+            File out = new File(sdir, name);
+            boolean success = CertUtil.saveCRL(crl, out);
+            if (success) {
+                _log.logAlways(Log.INFO, "Created certificate revocation list (CRL) for netdb family \"" + _fname +
+                           "\" in file: " + out.getAbsolutePath() + "\n" +
+                           "Back up the keystore and CRL files and keep them secure.\n" +
+                           "If your private key is ever compromised, give the CRL to an I2P developer for publication.");
+            } else {
+                _log.error("Error saving family key CRL");
             }
         } else {
-            _log.error("Error saving ASCII SSL keys");
+            _log.error("Error saving family key CRL");
         }
     }