Extract a private key from a Gnu Keyring file
Oct 4, 2011
If you've spent any time working with SSL, you know that you need a certificate to make it work. In the simplest case, this can be a self-signed certificate, but if you want to distribute a website to a wide audience, the certificate must be signed by a certificate authority trusted by your entire target audience. There are quite a few such certificate authorities — Verisign and Thawte, for example — although coordinating with them securely can be a somewhat labyrinthine experience.
Generally, you create a certificate signing request that contains the public key which you want to use to secure your site. You send this certificate signing request (CSR) off to the certificate authority and they send you back, if you're lucky, a signed certificate you can then install in your website. This whole process is fairly well standardized and, as long as you understand it, works about as you'd expect.
The only trick is where to get the certificate signing request file in the first place. You can't just create one in a text editor - they have to be in a very specific format, using an arcane encoding called ASN.1. Nor can you just let the certificate authority create one for you and send it back. The public key in the signing request must correspond to a private key, and that private key should never be distributed to anybody — not even the certificate authority.
There are (at least) two tools in common use for generating these CSR's; the OpenSSL tool and the keytool that comes bundles with Java. Of course, although both of these tools generate compatible CSR's, they have wildly differing opinions of what and how the private key should be stored.
OpenSSL takes the simplest approach and just outputs the numeric value of the private key, in ASN.1 format, encrypted using DES. Java's keytool, on the other hand, creates a keystore which correlates private keys with certificates. The default format for a Java keystore is a proprietary format they refer to as JKS which is just short for "Java Key Store". The Java approach is somewhat better because a single file contains both the private key, the public key, and the corresponding certificate; the OpenSSL way opens the door to accidentally losing one or the other, rendering both useless. OpenSSL does support a keystore-like format called PKCS #12, which the Java keytool actually understands, but it isn't the default; if you generate a certificate request using OpenSSL, you get back two files: the certificate request and the private key file. If you want a PKCS #12 file, you have to create it yourself after the certificate has been signed.
Of course, as (bad) luck would have it, different web servers prefer different private key formats. Apache's HTTPD server prefers the OpenSSL format with two separate files; Java application servers such as Tomcat, JBoss, Weblogic, etc. prefer the Java keystore approach. I believe that Microsoft's IIS actually prefers the PKCS #12 format (which they denote by the extension .pfx) but I've been fortunate enough not to have to affirm this first-hand.
As a result, it's (somewhat) common to have a private key in one format that you need to convert to the other. The most straightforward way to make a keypair which was originally formatted using OpenSSL available to a Java process is to just create a PKCS #12 file:
openssl pkcs12 -inkey <filename> -certfile <filename> -out <file>.pfx
Getting a keytool-formatted key into OpenSSL friendly format (for the Apache
HTTPD server, for example) isn't quite so straightforward, however. It's made
deliberately difficult, since you're supposed to treat the keystore as an
opaque data store. As a result, there are a few sites like
this one
that include code for Mark Foster's ExportPriv
utility that can extract
a private key into
PKCS #8 format, which you can then convert to the RSA format that OpenSSL
likes.
I recently found myself in such a position — I was given a signed certificate and a .keystore file containing its private key, but I needed to install the certificate (and, of course, its key) in an Apache server instance. So I fired up ExportPriv application and got back:
Exception in thread "main" java.io.IOException: Invalid keystore format at sun.security.provider.JavaKeyStore.engineLoad(JavaKeyStore.java:633) at sun.security.provider.JavaKeyStore$JKS.engineLoad(JavaKeyStore.java:38) at java.security.KeyStore.load(KeyStore.java:1185) at ExportPriv.doit(ExportPriv.java:33) at ExportPriv.main(ExportPriv.java:21)Hm. Did I get the password wrong? Nope, I can list the aliases using the keytool format using the password I was provided. So what's going on here?
Well, as it turns out, there's yet another format lurking out there,
waiting to trap unsuspecting server administrators. The
GNU project maintains an open-source
version of Java called GCJ which includes its own keytool... and its own
keystore format. Most Linux distributions, if they come bundled with Java,
only include GCJ due to the restrictive licensing conditions that Sun (and
now Oracle) put on redistributions of their JDK. If an administrator is
used to the Java keytool and happens to try to run it on such a Linux distribution,
he can find himself in for a surprise when the resulting keystore turns out
to be incompatible with Sun's JKS format — and the ExportPriv
tool.
Ok, so now that I know what the problem is, I should be able to easily tweak ExportPriv to work with the GNU key store format. Just change the getInstance line to read:
KeyStore ks = KeyStore.getInstance( "GKR" );for the GNU Keyring format, and re-run the app. Success! I now have a private key file which... fails when I restart Apache. As it turns out, if you just invoke
PrivateKey.getEncoded()
on a GNU Key ring-formatted keystore,
you get back the GNU Keyring format,
which is not at all compatible with Apache or OpenSSL (in fact, it's not
even ASN.1 formatted).
After delving down into the depths of the GNU code and the GNU keyring format,
I finally found an undocumented feature in the GNU code - you can pass
a specifier into getEncoded
which will allow you to request
a PKCS #8-formatted private key. Below are the changes I needed to make
to ExportPriv.java in order to be able to extract the private key into an
OpenSSL friendly format:
import gnu.java.security.key.rsa.GnuRSAPrivateKey; import gnu.java.security.key.IKeyPairCodec; ... KeyStore ks = KeyStore.getInstance("GKR"); char[] passPhrase = pass.toCharArray(); //BASE64Encoder myB64 = new BASE64Encoder(); File certificateFile = new File(fileName); ks.load(new FileInputStream(certificateFile), passPhrase); Key privKey = ks.getKey( keyAlias, passPhrase ); char[] b64 = Base64Coder.encode( ( ( GnuRSAPrivateKey ) privKey ).getEncoded( IKeyPairCodec.PKCS8_FORMAT ));
If you look over the original, you'll notice I'm bypassing the
getPrivateKey
trick that ExportPriv borrows from
the Java Almanac;
(the GNU code actually doesn't work with the trick demonstrated there). Of course, this won't compile unless you have the
GNU Classpath installed —
but if you're dealing with a GNU Keyring file, it must have been created
on such a machine.
Hopefully this will save somebody else a few day's worth of headache; once I figured it out, it turned out to be simple to do, but it took some research to figure out exactly what that something was.
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)