4

I've generated an elliptic curve private/public key pair using OpenSSL. The private and public keys are PEM encoded. I've figured out how to load the public key thanks to this. However, I can't figure out how to load the private key, as the above message just ends up with an InvalidKeySpecException: key spec not recognized.

I then found this, but it also ends up with an "encoded key spec not recognised". How can I load my private key?

private PrivateKey loadPrivateKey(String location) {
    try {
        // Strip the guarding strings
        byte[] bytes = stripGuardLines(location);

        return KeyFactory.getInstance("ECDH").generatePrivate(new PKCS8EncodedKeySpec(bytes));
    } catch (FileNotFoundException e) {
        LoggerFactory.getLogger("Nectar").error("Failed to find Private KEY: " + location);
        System.exit(1);
    } catch (IOException e) {
        LoggerFactory.getLogger("Nectar").error("IOException while loading Private Key!");
        e.printStackTrace();
        System.exit(1);
    } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
        e.printStackTrace();
        System.exit(1);
    }

    return null;
}

private byte[] stripGuardLines(String location) throws IOException {
    BufferedReader r = new BufferedReader(new FileReader(location));

    String line;
    StringBuilder sb = new StringBuilder();
    while((line = r.readLine()) != null) {
        if(line.contains("EC PRIVATE KEY")) { //Check if guard line
            continue;
        }
        sb.append(line);
    }
    // Guard lines stripped, now decode base64

    return Base64.getDecoder().decode(sb.toString());
}

Here is the private key file:

-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDD2MFRv6BpJU6/zDI2yBfVbe0oeU1nFAoYMedDGtcdwHyWNJSeiYRBA
pVNzMxPSBLWgBwYFK4EEACKhZANiAAQBttEp/qUGnDlmL+o6KZnVs+RoBnEBEGho
PxSUu1Xfj77QQqfuqHOCRzWXseQA1aZB/h6VQEiFovugtG1G3HaMxxrqLLxb10g2
BMaRcAfZyeqc3O0Ui8XXb1esn0gOrCU=
-----END EC PRIVATE KEY-----
Community
  • 1
  • 1
jython234
  • 315
  • 2
  • 9
  • I *think* you've just got the most minimal encoding instead of a PKCS#8 compliant structure. Create your own PKCS#8 from Java and compare with the online ASN.1 decoder (encode to base 64 first). – Maarten Bodewes Jan 30 '17 at 17:44
  • Yep, just confirmed, this is the X9.62 structure that is *inside* the PKCS#8 structure. – Maarten Bodewes Jan 30 '17 at 17:55
  • http://stackoverflow.com/questions/22963581/reading-elliptic-curve-private-key-from-file-with-bouncycastle/41947163 now improved – dave_thompson_085 Jan 30 '17 at 23:24
  • @dave_thompson_085 You can directly [link to your answer](http://stackoverflow.com/a/41947163/589259) by hitting the share button below it. Or close questions as duplicate (invoking dual gold badge powers, beware). – Maarten Bodewes Jan 31 '17 at 14:51
  • 1
    @MaartenBodewes: I didn't link my answer specifically because it is not the only good one (now) and probably not the best. Personally I use 'openssl to pkcs8 then JCE' or in cases with a cert 'openssl to pkcs12'. I answered about BC only because that point in the Q hadn't been addressed. :-) – dave_thompson_085 Feb 02 '17 at 01:53

1 Answers1

5

Your code outputs only the key itself. It is missing the algorithm specifiers required for PKCS#8 encoded private keys. Fortunately the key itself always has the same size when encoded (it doesn't use any ASN.1 integers, which may have different sizes as far as I can see).

That means that you can simply concatenate the header from another key:

// static header you can put in front
byte[] header = Hex.decode("30 81bf 020100 301006072a8648ce3d020106052b81040022 0481a7");
// your key from the PEM above
byte[] fromPEM = Base64.decode("MIGkAgEBBDD2MFRv6BpJU6/zDI2yBfVbe0oeU1nFAoYMedDGtcdwHyWNJSeiYRBApVNzMxPSBLWgBwYFK4EEACKhZANiAAQBttEp/qUGnDlmL+o6KZnVs+RoBnEBEGhoPxSUu1Xfj77QQqfuqHOCRzWXseQA1aZB/h6VQEiFovugtG1G3HaMxxrqLLxb10g2BMaRcAfZyeqc3O0Ui8XXb1esn0gOrCU=");
byte[] bytes = Arrays.concatenate(header, fromPEM);
PrivateKey ecPrivate = KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(bytes));

this code is both Bouncy Castle compatible as compatible with the default implementation in the Java JRE from Oracle.


Note that you might as well use Oracle compatible code to create your key pair as well:

KeyPairGenerator kpGen = KeyPairGenerator.getInstance("EC");
kpGen.initialize(new ECGenParameterSpec("secp384r1"));
KeyPair ecKP = kpGen.generateKeyPair();
Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • Kind of obvious, but since the header contains length encodings and an OID of the named curve, this will only work for secp384r1 (or P-384, same thing) curves. – Maarten Bodewes Jan 30 '17 at 19:10
  • @dave_thompson_085 Probably better that way, would vote up answer. My option is more of a hack around the issue (didn't have time to muck around with `openssl` at work :P). – Maarten Bodewes Jan 30 '17 at 21:18
  • 1
    (ninja) Or let OpenSSL convert the PEM: `openssl pkey pkcs8` (or especially in 0.9.8 or lower `openssl pkcs8 -topk8 -nocrypt pkcs8`) then strip and base64-decode that. Or convert to DER at the same time with `openssl pkcs8 -topk8 -nocrypt -outform der pkcs8.der` and use that directly -- but _not_ `pkey -outform der` which outputs legacy/raw! – dave_thompson_085 Jan 30 '17 at 21:29
  • 1
    Thanks, I ended up converting the .PEM from the "old format" to the "new format" using ```openssl pkey -in [old] -out [new]``` – jython234 Jan 31 '17 at 23:16