Andrey Yaskulsky
Andrey Yaskulsky

Reputation: 2516

Can't get read DSA keys from .pem files

I use openssl to generate public and private DSA keys and run the following commands:

openssl dsaparam -out dsaparam.pem 1024
openssl gendsa -out dsaprivkey.pem dsaparam.pem
openssl req -new -x509 -key dsaprivkey.pem -out dsacert.pem

And use the following two methods to load these keys:

public static PrivateKey loadPrivateKey() throws Exception {
    String privateKeyPEM = FileUtils.readFileToString(new File("/Keys/dsaprivkey.pem"), StandardCharsets.UTF_8);

    // strip of header, footer, newlines, whitespaces
    privateKeyPEM = privateKeyPEM
            .replace("-----BEGIN DSA PRIVATE KEY-----", "")
            .replace("-----END DSA PRIVATE KEY-----", "")
            .replaceAll("\\s", "");

    // decode to get the binary DER representation
    byte[] privateKeyDER = Base64.getDecoder().decode(privateKeyPEM);

    KeyFactory keyFactory = KeyFactory.getInstance("DSA");
    PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyDER));
    return privateKey;
}

public static PublicKey loadPublicKey() throws Exception {
    String publicKeyPEM = FileUtils.readFileToString(new File("/Keys/dsacert.pem"), StandardCharsets.UTF_8);

    // strip of header, footer, newlines, whitespaces
    publicKeyPEM = publicKeyPEM
            .replace("-----BEGIN CERTIFICATE-----", "")
            .replace("-----END CERTIFICATE-----", "")
            .replaceAll("\\s", "");

    // decode to get the binary DER representation
    byte[] publicKeyDER = Base64.getDecoder().decode(publicKeyPEM);

    KeyFactory keyFactory = KeyFactory.getInstance("DSA");
    PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyDER));
    return publicKey;
}

However, I can't get read neither public nor private key.

When I try to read public key, I get:

java.security.spec.InvalidKeySpecException: Inappropriate key specification: IOException: ObjectIdentifier() -- data isn't an object ID (tag = -96)

And when I try to read private key, I get:

Exception in thread "main" java.security.spec.InvalidKeySpecException: Inappropriate key specification: IOException : algid parse error, not a sequence

If someone faced the same situation, I will really appreciate any help with regards to this problem

Upvotes: 2

Views: 1758

Answers (1)

dave_thompson_085
dave_thompson_085

Reputation: 38771

You aren't generating private and public key, you're generating private key and (X.509) certificate. A certificate contains a public key but is not the same as a public key.

To read an X.509 certificate in Java, use CertificateFactory (usually directly from a file) (see javadoc online) not KeyFactory (on a key-dependent *Spec class). Unlike KeyFactory(s), CertificateFactory can handle either PEM or DER input, so you don't need to do the strip-BEGIN/END and de-base64 yourself (unless you want to).

For the private key you have a problem similar to but not the same as the one jww commented about. For a public key file (which you don't have) OpenSSL does use by default the SubjectPublicKeyInfo format that Java calls X509EncodedKeySpec and OpenSSL internally calls PUBKEY. However for a private key file OpenSSL uses both its own 'legacy' formats and the standardized PKCS8 format, but Java's PKCS8EncodedKeySpec supports only the second of these, your commands are using the first, and converting between them in code is not trivial. You have three or maybe four choices:

  • use openssl to convert to PKCS8-unencrypted: openssl pkcs8 -topk8 -nocrypt -in dsaprivkey.pem -out dsaprivfixed.pem # or in 1.0.0 up openssl pkey -in dsaprivkey.pem -out dsaprivfixed.pem

    Now you can read this with the posted code except the lines to remove are -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- without DSA. As a small improvement, if you use getMimeDecoder you don't need to strip the whitespace yourself.

  • generate PKCS8 (unencrypted) to start with, in openssl 1.0.0 up: # you can generate the key within req instead of separately: openssl dsaparam -out params size openssl req -new -newkey dsa:params -x509 -nodes -keyout private.pem \ -out cert.pem # -nodes really means "don't encrypt key" for hysterical raisins # or in config file set encrypt_rsa_key=no (yes even for other algos!) # or omit it and set encrypt_key=no (more sensible) # # or you can use (new) genpkey instead of (old) gendsa: openssl dsaparam -out params size openssl genpkey -paramfile params -out private.pem openssl req -new -x509 -key private.pem -out cert.pem

    Now you're in the same situation as above.

  • use the PEM functionality in BouncyCastle to read the legacy format.
    See Reading elliptic curve private key from file with BouncyCastle
    and How to Load RSA Private Key From File

  • read the legacy format (as you're doing now) and rebuild the PKCS8 ASN.1 encoding for it and then use KeyFactory on the PKCS8. This is a good deal more complicated and I don't recommend it. I do have a worked example for RSA (which is simpler) somewhere but I'm having trouble finding it; will add later if possible.

Upvotes: 3

Related Questions