Import a private key into a Java Key Store

OpenSSL and Java never quite seem to get along. OpenSSL, in addition to being the primary library used for SSL functionality in open source as well as commercial software products, is also a set of tools used to create all of the peripheral SSL-related artifacts such as X.509 certificates. Java, however, doesn't use OpenSSL and hasn't since the first release of the Java Cryptography Extensions (JCE) — Java has had its own native implementation of SSL since JDK 1.3. This can cause some interoperability problems for people who use both out of necessity.

One such interoperability headache surrounds key stores. Sun's cryptography implementation includes a thoroughly integrated public/private key management infrastructure called the Java Key Store (JKS), along with its own file format. The Java Virtual Machine knows how to read these key stores and extract public/private keypairs from them for code signing as well as key negotiation (e.g. SSL handshaking) purposes. In the ideal (from the Java user's perspective) scenario, a keypair would be generated using Java's keytool, the corresponding certificate exported and signed by a certificate authority, and then re-imported into the key store once signed. To create a private key and its corresponding public-key certificate using Java tools, you would do something like:

$ keytool -genkeypair -keyalg rsa -keysize 2048 -alias jdavies -keystore jdavieskeys.jks -dname "CN=Joshua Davies"
$ keytool -certreq -alias jdavies -keystore jdavieskeys.jks > jdaviescert.csr
(get the CSR signed by a CA)
$ keytool -import -alias jdavies -file jdaviescert.pem -keystore jdavieskeys.jks

Example 1: Create a certificate and private key for Java

If you use the Tomcat application server (or any other Java- based application server), you're undoubtedly familiar with this process. One principal benefit of this process is that Java keystores are nice, neat self-contained bundles — you can apply operating system security measures to them and move them around in a single operation.

OpenSSL's artifacts, by contrast, are discrete. If you want to create a keypair using OpenSSL, you get a key file and a CSR in two separate files; it's up to you to keep track of which private key file is associated with which signed certificate. Generating a certificate in OpenSSL is something like:

$ openssl req -newkey rsa:2048 -keyout jdavies.key -out jdavies.csr -subj "/CN=Joshua Davies"

Example 2: Create a certificate and private key for everything else

Once you have the CSR signed, you get back a certificate, but you don't associate it with the key file. If you use the Apache Web Server (or pretty much any other non-Java-based web server), you're likely familiar with this process.

Since there's a real monetary cost associated with each of the CA signatures, there's always interest in using certificates in a JKS file for OpenSSL-related purposes and vice versa. For example, Sun made it deliberately hard to get a private key out of a JKS file, but if you want to use a certificate exported from a JKS file anywhere outside of Java, you'll need to convert the JKS file into a PKCS #12 file (I talk more about this process here). Conversely, if you have a key/certificate pair that you generated using OpenSSL tools and you want to use it in a Java-based implementation, the keytool utility that comes with Java doesn't offer much help. You could, if you wanted, invert the private key export process I described above: convert the JKS file to PKCS #12 and import the keys that way, but that strikes me as quite a hassle. As it turns out, it's not really that much extra work to develop a utility that can import OpenSSL-formatted private keys into a Java Key Store while still preserving the JKS file format.

First, it's worth examining what, exactly, an OpenSSL-formatted private key looks like. By default, the jdavies.key that would be produced by example 2, above, would be a Base-64 encoded representation of an encrypted message. Because it's Base64 encoded, you can open it up and view it in a standard text editor, but it's not very interesting to look at (unless you're into that sort of thing):

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQItmQ4gm2gJy0CAggA
MBQGCCqGSIb3DQMHBAg9NJTnjKg6aASCBMhp0b5nXyEp4j8ekugAVAdDQqXgHytD
Tgd30CZLkXCO0lZ8dtu394DX+v54GOD5U7esl6/aUL6KQdd23wMeo2uIWVf0bbiT
MmTjtgy58ziV8pVnzTU7yo+PVJpBK7jh4QZjpdKLT3Y2GIQjaF/uQAgabVJpJmx/
hI7KjJ8mXctTcJKvxtqnBpG22La3F2b1YKUMXPbH5iiuK/YKyAg2FdPTYtCh6SyD
...
0SwrD3aBShifkNYno7H/rkgn3aWDpHlS5DSUe8+a9OPDjDJsjCDYC9sCYlANupb6
Vh8=
-----END ENCRYPTED PRIVATE KEY-----

Example 3: Base64-encoded, Encrypted private key

If you were to Base64 decode this, you'd get an ASN.1 formatted file with three elements: the name of the encryption algorithm of the payload (des-ede3-cbc by default, if you're curious), the key-stretching algorithm of the key to the encryption algorithm (PBKDF2, again if you're curious) and the payload which itself is encrypted by the stretched key and algorithm indicated previously. As you can see, OpenSSL is pretty serious about protecting private keys.

OpenSSL does, however, come with a utility for viewing and even decrypting RSA private keys.

$ openssl rsa -in localhost_rsa.key -noout -text
Enter pass phrase for localhost_rsa.key:
Private-Key: (2048 bit)
modulus:
    00:b7:2f:a9:8d:70:79:69:0e:09:7c:37:34:73:f6:
...
publicExponent: 65537 (0x10001)
privateExponent:
    6f:17:38:7b:cd:f9:d2:fb:f0:44:a4:35:eb:1d:39:
...
prime1:
    00:ee:6d:32:92:1f:fe:54:d2:f0:fc:5b:fb:ff:ef:
...
prime2:
    00:c4:b0:26:65:8e:ba:44:25:9f:de:8f:95:95:2a:
...
exponent1:
    36:4f:09:45:df:a3:bf:0e:8d:75:ee:3d:e8:7e:5b:
...
exponent2:
    31:f4:5f:3c:29:fc:ea:f3:f7:5f:aa:6e:1e:5d:93:
...
coefficient:
    00:dd:da:f6:00:13:b3:8b:2e:64:94:37:b4:f1:33:
...

Example 4: A PKCS #1-formatted RSA private key

As you can see illustrated above, an RSA private key, encoded on disk, is a list of 8 integer values, shown here expanded out in hexadecimal form. The first two, the modulus and publicExponent, comprise the public key (you can see the exact same values listed in the certificate itself, under the same names). The third, the privateExponent, is the private key: the remaining five values are optimizations for the Chinese Remainder Theorem to speed up private-key computations. Internally, these are gathered together in an ASN.1 struct as specified by PKCS #1, Appendix C.

Thus, the trick to importing these into a Java Key Store is to parse the ASN.1 structure of the private key file and present it to the JCE API for import. ASN.1 parsing, in general, is a hard problem, but in this case I can minimize it by recognizing that there are only two ASN.1 tags in use in this format: the STRUCT tag 0x30 and the INTEGER tag 0x02.

If you're not familiar with ASN.1, (in which case you're in good company: most people aren't, as it's a very obsolete format only found in OpenSSL anymore), it's a data interchange format, somewhat similar in concept to XML as it's typically used these days. However, unlike the very verbose XML, ASN.1 is designed to be almost hyper-efficient in its use of space. As such, every ASN.1 data element is represented by a one-byte tag indicating what type the data is, a one-byte length identifier, followed by that many bytes of data, followed by another tag. Data elements can be grouped under a STRUCT tag but it's worth noting that none of the data elements are named inside an ASN.1-encoded file: the caller has to know what each element represents.

You may have spotted a crippling limitation in the description of ASN.1 elements as I presented them above, though: if an ASN.1 element is prefixed by a tag indicating its type and a single byte indicating its length, then a data element can never be longer than 255 bytes. So, although a single-byte tag and a single-byte length is the default, ASN.1 does include a provision for longer data elements. If the high-order bit in the length is set, then the length byte doesn't indicate the length of the data element, but instead the 7 lower-order bits indicate the length of the length. (This implies also that if the data is longer than 127 bytes, it has to use this extended length encoding). In other words, 0x82 tells me that the next two bytes encode the length of the data element (which starts immediately after those two bytes). This extended-length encoding turns out to be the most complex part of parsing a PKCS #1 formatted private key.

There's a LOT more to ASN.1 than I've presented here, but this is enough to parse an RSA private key file.

Example 5, below, is the hexadecimal representation of the first part of a PKCS #1-formatted RSA private key:

30 82 04 A3 02 01 00 02 82 01 01 00 B7 2F A9 8D 70 79 69 0E 09 7C 37 34 73 F6 58 36 B7 B9 5B 5B E7 69 73 07 7E 3A E1 E5 BA 38 31 B0 D1 A0 08 A9 8B 64 3B 32 D2 BC A6 A3 20 E5 6B DA 5F 0A AC 66 88 D2 38 ED 8C 69 EB 16 9B B3 BC EB A6 4A 15 F0 37 80 F1 F1 0F DF 7F BA 2E B8 8E 98 6D 1B 7F 53 4D 3F 8E F0 6D DF 57 6C 47 4B 6D 77 4A 60 81 F0 9B 48 8A AD 7F EA 36 0A 82 EE E2 83 19 E0 44 3E FA 7D 2E 69 86 7E C4 64 32 05 E0 E4 EC DC E3 56 BD 9A 1C AA 94 51 8D 22 2E 8D D4 50 59 74 1C C3 16 72 B7 C2 AF EC 1B 98 D2 30 AC 88 67 99 D2 5E 0C 91 3C C3 7E AB 2A EC 8B 2F F2 67 FD C5 D3 BC 9B C9 1E 69 1B 27 38 C9 90 30 17 46 0B A1 2C 1D 4E 52 74 98 11 F1 E4 DF 24 40 94 E1 52 C3 4C 93 9D 80 25 73 B3 9A 3C D1 89 33 59 1F 95 60 8E 16 E1 E6 A9 58 9D D9 1D 9A 44 A2 72 25 16 E4 3A 58 4A 26 3A 9C E1 10 72 AB 19 06 0C A1 02 03 01 00 01 02 82 01 00 6F 17 38 7B CD F9 D2 FB F0 44 A4 ...

Example 5: Hexadecimal representation of a PCKS #1-formatted RSA private key

The first byte is 0x30 which you may recall I mentioned is the ASN.1 tag for a STRUCT: indicating that the data element is itself a grouping of other ASN.1 data elements. This is followed by the byte 0x82. Since the high-order bit of 0x82 is set, this tells me that the following 2 bytes encode the length of the structure. These two bytes are 0x04A3, which is the big-endian hexadecimal representation of the decimal number 1,187: the next 1,187 bytes are the struct itself. This means also that the following byte, the fifth, must be an ASN.1 tag indicating the first data element of the (unnamed, you may recall) structure. And it is: 0x02 is the ASN.1 tag for an integer. This is followed by the byte 0x01, indicating that this integer is one byte long. The next single byte, then, is the data value: 0, the version identifier.

The very next byte must again be a valid ASN.1 tag — and it's 0x02, another integer (recall that this structure is easy to parse because it's all integers). This time, though, the length byte is 0x82 again, indicating that the length of the integer is encoded in the following two bytes: 0x0101, or 257 decimal. This is followed by a 257-byte value, in big-endian format. If you're inclined to fastidiously count all 257 bytes here, you'll see that this is followed by the sequence: 02 03 01 00 01. These 5 bytes together are the ASN.1 encoding of a 3-byte integer whose decimal value is 65,537; if you peek back at example 4, you'll see that this is the correct public exponent that OpenSSL reported. This value is followed by 02 82 01 00: another 256 byte integer. You can probably guess at this point that the first 257 byte integer was the modulus and the second is the private exponent.

Example 6 shows the breakdown of tags, lengths, extended lengths and actual values of these first few parts of an ASN.1-encoded private key.

30 82 04 A3 02 01 00 02 82 01 01 00 B7 2F A9 8D 70 79 69 0E 09 7C 37 34 73 F6 58 36 B7 B9 5B 5B E7 69 73 07 7E 3A E1 E5 BA 38 31 B0 D1 A0 08 A9 8B 64 3B 32 D2 BC A6 A3 20 E5 6B DA 5F 0A AC 66 88 D2 38 ED 8C 69 EB 16 9B B3 BC EB A6 4A 15 F0 37 80 F1 F1 0F DF 7F BA 2E B8 8E 98 6D 1B 7F 53 4D 3F 8E F0 6D DF 57 6C 47 4B 6D 77 4A 60 81 F0 9B 48 8A AD 7F EA 36 0A 82 EE E2 83 19 E0 44 3E FA 7D 2E 69 86 7E C4 64 32 05 E0 E4 EC DC E3 56 BD 9A 1C AA 94 51 8D 22 2E 8D D4 50 59 74 1C C3 16 72 B7 C2 AF EC 1B 98 D2 30 AC 88 67 99 D2 5E 0C 91 3C C3 7E AB 2A EC 8B 2F F2 67 FD C5 D3 BC 9B C9 1E 69 1B 27 38 C9 90 30 17 46 0B A1 2C 1D 4E 52 74 98 11 F1 E4 DF 24 40 94 E1 52 C3 4C 93 9D 80 25 73 B3 9A 3C D1 89 33 59 1F 95 60 8E 16 E1 E6 A9 58 9D D9 1D 9A 44 A2 72 25 16 E4 3A 58 4A 26 3A 9C E1 10 72 AB 19 06 0C A1 02 03 01 00 01 02 82 01 00 6F 17 38 7B CD F9 D2 FB F0 44 A4 ...

Example 6: Color-coded ASN.1 breakdown - tag, length, extended length, value

Since all I need out of this file is the list of the integers in order, I can almost parse it as in example 7.

private static void ASN1Parse(byte[] b, List<BigInteger> integers)  {
    int pos = 0;
    while (pos < b.length)  {
      byte tag = b[pos++];
      int length = b[pos++];
      byte[] contents = new byte[length];
      System.arraycopy(b, pos, contents, 0, length);
      pos += length;

      if (tag == 0x30)  { // sequence
        ASN1Parse(contents, integers);
      } else if (tag == 0x02) { // Integer
        BigInteger i = new BigInteger(contents);
        integers.add(i);
      }
    }
  }

Example 7: ASN.1 parsing code

The idea behind example 7 is that I take as input a "blob" of bytes b like the one in example 5 and every time I successfully parse a properly-formatted integer, I append it to the out parameter integers. It's up to the caller to recognize that integers[1] is the modulus, for example.

So I first read the tag, I then read the length, and then I slurp all of the data represented by the length into the byte array contents. If the tag was an integer, I give the contents directly to Java's BigInteger constructor which was designed for just this sort of thing, and append that BigInteger to the integers list. If, on the other hand, the tag was a struct, I just recursively call the parser again with the contents which, by the structure of ASN.1, must itself be a valid ASN.1-formatted byte array. A full-featured ASN.1 parser would, of course, deal with quite a few other tags, but since I know I have an RSA key, I don't have to worry about them in this case.

However, this doesn't quite work — it doesn't take into account extended-length tags which, if you recall from example 5, my very first tag is. Handling extended-length tags in Java turns out to be a bit of a pain because Java is just a tad byte-hostile. java.lang.Integer doesn't have a byte-array constructor analogous to the char array constructor in java.lang.String. BigInteger does (but see below), but ugh... a whole BigInteger just to decode a couple of bytes? You would expect something like this to work:

if ((length & 0x80) !=0)  {
  int extLen = 0;
  for (int i = 0; i < (length & 0x7F); i++)  {
    extLen = (extLen << 8) | b[pos++];
  }
  length = extLen;
}

Example 8: constructing an integer from bytes

However, it doesn't, because of the way Java treats bytes — specifically, it interprets every byte as being signed, and then promotes them to integers besides. What I want it to do, when presented with the two hexadecimal bytes 0x04 0xA3, this loop would construct the final integer as:
0x00 0x00 0x00 0x04
<< 8
0x00 0x00 0x04 0x00 |
               0xA3
-------------------
0x00 0x00 0x04 0xA3
But it doesn't, because 0xA3 is treated as a signed, two's-complement byte, and then promoted to an integer, preserving the sign bit. So what happens instead is:
0x00 0x00 0x00 0x04
<< 8
0x00 0x00 0x04 0x00 |
0xFF 0xFF 0xFF 0xA3
-------------------
0xFF 0xFF 0xFF 0xA3
Which is actually the two's complement representation of decimal integer -93. What I want is some way to force Java to not interpret this byte as a signed byte. Java doesn't have an unsigned byte type, though. There is a sneaky trick to make this work, though: ANDing each byte with 0xFF wipes out the signed upper bytes and makes this work as it's supposed to:

if ((length & 0x80) !=0)  {
  int extLen = 0;
  for (int i = 0; i < (length & 0x7F); i++)  {
    extLen = (extLen << 8) | (b[pos++] & 0xFF);
  }
  length = extLen;
}

Example 9: constructing an unsigned integer from bytes

I mentioned that you can do this with BigIntegers as well. If you do, you have to watch out for the high order bit there — BigInteger assumes that if the high-order bit is set, then the resulting integer is negative. Instead, you have to use the two-args constructor that takes a sign indicator, followed by the byte array.

This is, incidentally, why the RSA public exponent shown in example 5 is 257, rather than 256, bytes long in the encoded file: 256 bytes = 2,048 bits, which is the length of the key. The extra byte is the 0-pad that stops Java from recognizing this as a negative number. That's why I didn't have to be tricky when I created the BigInteger in example 7; the encoder was tricky on my behalf.

Once you have parsed the integers, it's easy to use the JCE API to construct an RSA private key and associate it with a certificate — this is all boilerplate JCE. Example 10 is a complete example that mimics the JDK's keytool. I've also added support for PKCS #8 (since it's part of the JDK) and DSA keys, since they're simple to add once you have basic ASN.1 support.

import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Arrays;
import java.math.BigInteger;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.NoSuchAlgorithmException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateException;

class KeyImportException extends Exception  {
  public KeyImportException(String msg)  {
    super(msg);
  }
}

/**
 * Import an existing key into a keystore.  This requires both the private key file and
 * the corresponding certificate file.
 */
public class KeyImport  {
  // Base64 decoding helper
  private static final int invCodes[] = {
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 64, -1, -1,
    -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
    -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
  };

  private static byte[] base64Decode(String input)    {
    if (input.length() % 4 != 0)    {
        throw new IllegalArgumentException("Invalid base64 input");
    }
    byte decoded[] = new byte[((input.length() * 3) / 4) - 
      (input.indexOf('=') > 0 ? (input.length() - input.indexOf('=')) : 0)];
    char[] inChars = input.toCharArray();
    int j = 0;
    int b[] = new int[4];
    for (int i = 0; i < inChars.length; i += 4)     {
      b[0] = invCodes[inChars[i]];
      b[1] = invCodes[inChars[i + 1]];
      b[2] = invCodes[inChars[i + 2]];
      b[3] = invCodes[inChars[i + 3]];
      decoded[j++] = (byte) ((b[0] << 2) | (b[1] >> 4));
      if (b[2] < 64)      {
        decoded[j++] = (byte) ((b[1] << 4) | (b[2] >> 2));
        if (b[3] < 64)  {
          decoded[j++] = (byte) ((b[2] << 6) | b[3]);
        }
      }
    }

    return decoded;
  }

  /**
   * Bare-bones ASN.1 parser that can only deal with a structure that contains integers
   * (as I expect for the RSA private key format given in PKCS #1 and RFC 3447).
   * @param b the bytes to be parsed as ASN.1 DER
   * @param integers an output array to which all integers encountered during the parse
   *   will be appended in the order they're encountered.  It's up to the caller to determine
   *   which is which.
   */
  private static void ASN1Parse(byte[] b, List<BigInteger> integers) 
      throws KeyImportException  {
    int pos = 0;
    while (pos < b.length)  {
      byte tag = b[pos++];
      int length = b[pos++];
      if ((length & 0x80) != 0)  {
        int extLen = 0;
        for (int i = 0; i < (length & 0x7F); i++)  {
          extLen = (extLen << 8) | (b[pos++] & 0xFF);
        }
        length = extLen;
      }
      byte[] contents = new byte[length];
      System.arraycopy(b, pos, contents, 0, length);
      pos += length;

      if (tag == 0x30)  {  // sequence
        ASN1Parse(contents, integers);
      } else if (tag == 0x02)  {  // Integer
        BigInteger i = new BigInteger(contents);
        integers.add(i);
      } else  {
        throw new KeyImportException("Unsupported ASN.1 tag " + tag + " encountered.  Is this a " +
          "valid RSA key?");
      }
    }
  }

  /**
   * Read a PKCS #8, Base64-encrypted file as a Key instance.
   * If the file is encrypted, decrypt it via:
   * openssl rsa -in keyfilename -out decryptedkeyfilename
   * TODO deal with an encrypted private key internally
   */
  private static PrivateKey readPrivateKeyFile(String keyFileName) 
      throws IOException,
             NoSuchAlgorithmException,
             InvalidKeySpecException,
             KeyImportException  {
    BufferedReader in = new BufferedReader(new FileReader(keyFileName));
    try  {
      String line;
      boolean readingKey = false;
      boolean pkcs8Format = false;
      boolean rsaFormat = false;
      boolean dsaFormat = false;
      StringBuffer base64EncodedKey = new StringBuffer();
      while ((line = in.readLine()) != null)  {
        if (readingKey)  {
          if (line.trim().equals("-----END RSA PRIVATE KEY-----"))  {  // PKCS #1
            readingKey = false;
          } else if (line.trim().equals("-----END DSA PRIVATE KEY-----"))  {
            readingKey = false;
          } else if (line.trim().equals("-----END PRIVATE KEY-----"))  {  // PKCS #8
            readingKey = false;
          } else  {
            base64EncodedKey.append(line.trim());
          }
        } else if  (line.trim().equals("-----BEGIN RSA PRIVATE KEY-----"))  {
          readingKey = true;
          rsaFormat = true;
        } else if  (line.trim().equals("-----BEGIN DSA PRIVATE KEY-----"))  {
          readingKey = true;
          dsaFormat = true;
        } else if  (line.trim().equals("-----BEGIN PRIVATE KEY-----"))  {
          readingKey = true;
          pkcs8Format = true;
        }
      }
      if (base64EncodedKey.length() == 0)  {
        throw new IOException("File '" + keyFileName + 
          "' did not contain an unencrypted private key");
      }

      byte[] bytes = base64Decode(base64EncodedKey.toString());

      KeyFactory kf = null;
      KeySpec spec = null;
      if (pkcs8Format)  {
        kf = KeyFactory.getInstance("RSA");
        spec = new PKCS8EncodedKeySpec(bytes);
      } else if (rsaFormat)  {
        // PKCS#1 format
        kf = KeyFactory.getInstance("RSA");
        List<BigInteger> rsaIntegers = new ArrayList<BigInteger>();
        ASN1Parse(bytes, rsaIntegers);
        if (rsaIntegers.size() < 8)  {
          throw new KeyImportException("'" + keyFileName + 
            "' does not appear to be a properly formatted RSA key");
        }
        BigInteger publicExponent = rsaIntegers.get(2);
        BigInteger privateExponent = rsaIntegers.get(3);
        BigInteger modulus = rsaIntegers.get(1);
        BigInteger primeP = rsaIntegers.get(4);
        BigInteger primeQ = rsaIntegers.get(5);
        BigInteger primeExponentP = rsaIntegers.get(6);
        BigInteger primeExponentQ = rsaIntegers.get(7);
        BigInteger crtCoefficient = rsaIntegers.get(8);
        //spec = new RSAPrivateKeySpec(modulus, privateExponent);
        spec = new RSAPrivateCrtKeySpec(modulus, publicExponent, privateExponent,
          primeP, primeQ, primeExponentP, primeExponentQ, crtCoefficient);
      } else if (dsaFormat)  {
        kf = KeyFactory.getInstance("DSA");
        List<BigInteger> dsaIntegers = new ArrayList<BigInteger>();
        ASN1Parse(bytes, dsaIntegers);
        if (dsaIntegers.size() < 5)  {
          throw new KeyImportException("'" + keyFileName + 
            "' does not appear to be a properly formatted DSA key");
        }
        BigInteger privateExponent = dsaIntegers.get(1);
        BigInteger publicExponent = dsaIntegers.get(2);
        BigInteger P = dsaIntegers.get(3);
        BigInteger Q = dsaIntegers.get(4);
        BigInteger G = dsaIntegers.get(5);
        spec = new DSAPrivateKeySpec(privateExponent, P, Q, G);
      }
      return kf.generatePrivate(spec);
    } finally  {
      in.close();
    }
  }

  /**
   * Support for parsing command line parameters of the form "-id value"
   */
  static class Parameter  {
    String flag;
    boolean required;
    String description;
    String defaultValue;

    public Parameter(String flag, boolean required, String description, String defaultValue)  {
      this.flag = flag;
      this.required = required;
      this.description = description;
      this.defaultValue = defaultValue;
    }

    public boolean equals(Object o)  {
      return (o instanceof Parameter) && (this.flag.equals(((Parameter) o).flag));
    }
  }

  private static String KEY_FILE = "-keyFile";
  private static String ALIAS = "-alias";
  private static String CERT_FILE = "-certificateFile";
  private static String KEY_STORE = "-keystore";
  private static String KEY_STORE_PASSWORD = "-keystorePassword";
  private static String KEY_STORE_TYPE = "-keystoreType";
  private static String KEY_PASSWORD = "-keyPassword";

  private static List<Parameter> paramDesc = Arrays.asList(
    new Parameter[] {
      new Parameter(KEY_FILE, true, "Name of file containing a private key in PEM or DER form", null),
      new Parameter(ALIAS, true, "The alias that this key should be imported as", null),
      new Parameter(CERT_FILE, true, "Name of file containing the certificate that corresponds to the key named by '-keyFile'", null),
      new Parameter(KEY_STORE, false, "Name of the keystore to import the private key into.", "~/.keystore"),
      new Parameter(KEY_STORE_PASSWORD, false, "Keystore password", "changeit"),
      new Parameter(KEY_STORE_TYPE, false, "Type of keystore; must be JKS or PKCS12", "JKS"),
      // If this password is different than the key store password, Tomcat (at least) chokes 
			// on it with: java.security.UnrecoverableKeyException: Cannot recover key
      new Parameter(KEY_PASSWORD, false, "The password to protect the imported key with", "changeit")
    });

  private static void usage()  {
    for (Parameter param : paramDesc)  {
      System.out.println(param.flag + "\t" + (param.required ? "required" : "optional") + "\t" +
        param.description + "\t" + 
        (param.defaultValue != null ? ("default '" + param.defaultValue + "'") : ""));
    }
  }

  public static void main(String[] args) throws IOException, 
                                                KeyStoreException,
                                                NoSuchAlgorithmException,
                                                CertificateException,
                                                InvalidKeySpecException,
                                                KeyImportException  {
    Map<String, String> parsedArgs = new HashMap<String, String>();
    for (Parameter param : paramDesc)  {
      if (param.defaultValue != null)  {
        parsedArgs.put(param.flag, param.defaultValue);
      }
    }
    for (int i = 0; i < args.length; i += 2)  {
      parsedArgs.put(args[i], args[i + 1]);
    }

    boolean invalidParameters = false;
    for (Parameter param : paramDesc)  {
      if (param.required && parsedArgs.get(param.flag) == null)  {
        System.err.println("Missing required parameter " + param.flag);
        invalidParameters = true;
      }
    }
    for (String key : parsedArgs.keySet())  {
      if (!paramDesc.contains(new Parameter(key, false, null, null)))  {
        System.err.println("Invalid parameter '" + key + "'");
        invalidParameters = true;
      }
    }
    if (invalidParameters)  {
      usage();
      System.exit(0);
    }

    KeyStore ks = KeyStore.getInstance(parsedArgs.get(KEY_STORE_TYPE));
    InputStream keyStoreIn = new FileInputStream(parsedArgs.get(KEY_STORE));
    try  {
      ks.load(keyStoreIn, parsedArgs.get(KEY_STORE_PASSWORD).toCharArray());
    } finally  {
      keyStoreIn.close();
    }

    Certificate cert;
    CertificateFactory fact = CertificateFactory.getInstance("X.509");
    FileInputStream certIn = new FileInputStream(parsedArgs.get(CERT_FILE));
    try  {
      cert = fact.generateCertificate(certIn);
    } finally  {
      certIn.close();
    }

    PrivateKey privateKey = readPrivateKeyFile(parsedArgs.get(KEY_FILE));
    ks.setKeyEntry(parsedArgs.get(ALIAS), privateKey, 
      parsedArgs.get(KEY_PASSWORD).toCharArray(), new Certificate[] {cert});

    OutputStream keyStoreOut = new FileOutputStream(parsedArgs.get(KEY_STORE));
    try  {
      ks.store(keyStoreOut, parsedArgs.get(KEY_STORE_PASSWORD).toCharArray());
    } finally  {
      keyStoreOut.close();
    }
  }
}

Example 10: Command-line utility to import OpenSSL PKCS #1-formatted RSA keys into an existing JKS keystore

This does still require that you decrypt the key before importing it as in:

$ openssl rsa -in encrypted.key -out decrypted.key
As it turns out, tackling in-place decryption of an RSA key requires a more sophisticated ASN.1 parser than the one in example 10. I'll come back to this topic in my next post.

Add a comment:

Completely off-topic or spam comments will be removed at the discretion of the moderator.

You may preserve formatting (e.g. a code sample) by indenting with four spaces preceding the formatted line(s)

Name: Name is required
Email (will not be displayed publicly):
Comment:
Comment is required
My Book

I'm the author of the book "Implementing SSL/TLS Using Cryptography and PKI". Like the title says, this is a from-the-ground-up examination of the SSL protocol that provides security, integrity and privacy to most application-level internet protocols, most notably HTTP. I include the source code to a complete working SSL implementation, including the most popular cryptographic algorithms (DES, 3DES, RC4, AES, RSA, DSA, Diffie-Hellman, HMAC, MD5, SHA-1, SHA-256, and ECC), and show how they all fit together to provide transport-layer security.

My Picture

Joshua Davies

Past Posts