Search code examples
javacryptographyopensaml

How to read a private key for use with OpenSAML?


OK, this is another of those "I have no real idea where to start" questions, so hopefully the answer is simple. However, I don't really know what to search for, and my attempts so far haven't turned up much of use.

I want to read a private key from a (currently on-disk) file. Ultimately the key will reside in a database, but this will be good enough for the moment and that difference should have no real bearing on parsing the key material. I have been able to create a Credential instance that holds the public part of the key (confirmed by debugger), but I can't seem to figure out how to read the private part. The key pair was generated as:

openssl genrsa 512 > d:\host.key
openssl req -new -x509 -nodes -sha1 -days 365 -key d:\host.key > d:\host.cert

(Yes, I know that 512 bit RSA keys were broken long ago. However, for trying to get the API to work, I see no reason to exhaust the system entropy supply needlessly.)

The code thus far is:

import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.security.x509.BasicX509Credential;

private Credential getSigningCredential()
throws java.security.cert.CertificateException, IOException {
    BasicX509Credential credential = new BasicX509Credential();

    credential.setUsageType(UsageType.SIGNING);

    // read public key
    InputStream inStream = new FileInputStream("d:\\host.cert");
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream);
    inStream.close();
    credential.setEntityCertificate(cert);

    // TODO: read private key

    // done.
    return credential;
}

But how do I read the file host.key into the private key portion of credential, so I can use the generated Credential instance to sign data?


Solution

  • BasicX509Credential is not part from standard Java; I suppose you are talking about org.opensaml.xml.security.x509.BasicX509Credential from OpenSAML.

    You want a PrivateKey which you will set with credential.setPrivateKey(). To get a PrivateKey, you must first convert the private key into a format that Java can read, namely PKCS#8:

    openssl pkcs8 -topk8 -nocrypt -outform DER < D:\host.key > D:\host.pk8
    

    Then, from Java:

    RandomAccessFile raf = new RandomAccessFile("d:\\host.pk8", "r");
    byte[] buf = new byte[(int)raf.length()];
    raf.readFully(buf);
    raf.close();
    PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(buf);
    KeyFactory kf = KeyFactory.getInstance("RSA");
    PrivateKey privKey = kf.generatePrivate(kspec);
    

    and voilà! you have your PrivateKey.

    By default, openssl writes key in its own format (for RSA keys, PKCS#8 happens to be a wrapper around that format), and it encodes them in PEM, which Base64 with a header and a footer. Both characteristics are unsupported by plain Java, hence the conversion to PKCS#8. The -nocrypt option is because PKCS#8 supports optional password-based encryption of private key.

    Warning: you really really want to use a longer RSA key. 512 bits are weak; a 512-bit RSA key was broken in 1999 with a few hundred computers. In 2011, with 12 years of technological advances, one should assume that a 512-bit RSA key can be broken by almost anybody. Therefore, use 1024-bit RSA keys at least (preferably, 2048-bit; the computational overhead when using the key is not that bad, you will still be able to perform hundred of signatures per second).