Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 923
user Avatar asked Mar 08 '11 10:03

user


2 Answers

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).

like image 91
Thomas Pornin Avatar answered Oct 19 '22 11:10

Thomas Pornin


This question is related to SAML and is also relevant for someone who wants to retrieve a private key for signing an XMLObject. The answer above works great and it also possible to retrieve a private key from a keystore as well:

        Properties sigProperties = new Properties();

    sigProperties.put("org.apache.ws.security.crypto.provider","org.apache.ws.security.components.crypto.Merlin");
    sigProperties.put("org.apache.ws.security.crypto.merlin.keystore.type","jks");
    sigProperties.put("org.apache.ws.security.crypto.merlin.keystore.password","keypass");
    sigProperties.put("org.apache.ws.security.crypto.merlin.keystore.alias","keyalias");
    sigProperties.put("org.apache.ws.security.crypto.merlin.keystore.file","keystore.jks");

    Crypto issuerCrypto = CryptoFactory.getInstance(sigProperties);

    String issuerKeyName = (String) sigProperties.get("org.apache.ws.security.crypto.merlin.keystore.alias");

    //See http://ws.apache.org/wss4j/xref/org/apache/ws/security/saml/ext/AssertionWrapper.html 'signAssertion' method
    // prepare to sign the SAML token
    CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
    cryptoType.setAlias(issuerKeyName);
    X509Certificate[] issuerCerts = issuerCrypto.getX509Certificates(cryptoType);
    if (issuerCerts == null) {
        throw new WSSecurityException(
                "No issuer certs were found to sign the SAML Assertion using issuer name: "
                        + issuerKeyName);
    }

    String password = ADSUnitTestUtils.getPrivateKeyPasswordFromAlias(issuerKeyName);

    PrivateKey privateKey = null;
    try {
        privateKey = issuerCrypto.getPrivateKey(issuerKeyName, password);
    } catch (Exception ex) {
        throw new WSSecurityException(ex.getMessage(), ex);
    }


    BasicX509Credential signingCredential = new BasicX509Credential();
    signingCredential.setEntityCertificate(issuerCerts[0]);
    signingCredential.setPrivateKey(privateKey);

    signature.setSigningCredential(signingCredential);

This is more code than the original query requested, but it looks they are trying to get at a BasicX509Credential.

Thanks, Yogesh

like image 23
Yogesh Chawla Avatar answered Oct 19 '22 11:10

Yogesh Chawla