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)

Name: Name is required
Email (will not be displayed publicly):
Comment:
Comment is required
Lynda, 2011-12-07
Kick the tires and light the fires, problem officailly solved!
Bardo, 2011-12-30
Holy szihnit, this is so cool thank you.
backlinks, 2013-06-10
Thanks again for the article post.Really looking forward to read more.
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