0
votes

Hoping to get some help here...

I am trying to generate a shared secret from uncompressed client public key (0x04 |32 byte point X| 32 byte point Y) and server private key (openSSL generated base64) but getting an error while converting server EC private key into EC key object. I would need these to generate shared secret

I am using - 1. Curve is brainpoolP256r1 2. server private key is generated using openSSL (hardcoded for now in code) 3. client public is shared in uncompressed (format 0x04 |32 byte point X| 32 byte point Y).

//Step 1 : Converting Client EC Public key (ephemeral) into actual EC Public key object (X509) 

byte[] clientECPublicKeybytes = DatatypeConverter.parseHexBinary("045894609CCECF9A92533F630DE713A958E96C97CCB8F5ABB5A688A238DEED6DC2D9D0C94EBFB7D526BA6A61764175B99CB6011E2047F9F067293F57F5");

 ECParameterSpec ecParameterSpec = ECNamedCurveTable.getParameterSpec("brainpoolP256r1");

  ECCurve curve = ecParameterSpec.getCurve();  ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(curve.decodePoint(clientECPublicKeybytes), ecParameterSpec);


 KeyFactory kf = KeyFactory.getInstance("EC"); 


ECPublicKey ecClientPublicKey = (ECPublicKey)kf.generatePublic(pubKeySpec); 


 //Step 2 : Converting server EC private key ASN.1 to ECPrivateKey object  

byte[] serverECCSkBytes = DatatypeConverter.parseBase64Binary("MHgCAQEEIA27nM1klj9pVxOzJrO4aBLFvXTtOJnf+vMhiv3HA+3noAsGCSskAwMCCAEBB6FEA0IABG1erLtLyTbC5yN8gVY4a0JPO5eefKftWMbSQij2Ks5TaNNuj/tqigFqsk1g/l2UBBkIx2KdpeiY8nVddwMpzho=");

  KeyFactory keyFactory= KeyFactory.getInstance("EC","BC");

  PKCS8EncodedKeySpec skSpec = new PKCS8EncodedKeySpec(serverECCSkBytes);

  ECPrivateKey ecServerPrivateKey = (ECPrivateKey)keyFactory.generatePrivate(skSpec);

Error

2018-02-26 14:31:12.818[0;39m [31mERROR[0;39m [35m18096[0;39m [2m---[0;39m [2m[nio-8080-exec-1][0;39m [36mo.a.c.c.C.[.[.[/].[dispatcherServlet]   [0;39m [2m:[0;39m Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.security.spec.InvalidKeySpecException: java.lang.IllegalArgumentException: wrong version for private key info] with root cause

java.security.spec.InvalidKeySpecException: java.lang.IllegalArgumentException: wrong version for private key info
    at org.bouncycastle.jce.provider.JDKKeyFactory$EC.engineGeneratePrivate(Unknown Source) ~[bcprov-jdk15-140.jar:1.40.0]
    at java.security.KeyFactory.generatePrivate(KeyFactory.java:366) ~[na:1.8.0_77]
1

1 Answers

3
votes

TLDR: that private key isn't PKCS8

OpenSSL supports four different PEM private key formats per algorithm (and about a dozen total); only one of these is PKCS8 unencrypted, the format required by Java PKCS8EncodedKeySpec, and you used a different one which Java doesn't support. Compare to How can I convert a .p12 to a .pem containing an unencrypted PKCS#1 private key block? which is for RSA; for EC the two 'traditional' formats are defined by SEC1 from https://www.secg.org (vs PKCS1 for RSA), while the PKCS8 formats (for all algorithms not just EC and RSA) are defined by PKCS8. OpenSSL is able to deal with multiple formats mostly because PEM files have a very important BEGIN line (and less important END line) that specifies the data type; your file before you removed it had type EC PRIVATE KEY meaning SEC1 not type PRIVATE KEY meaning PKCS8 unencrypted as used by Java.

You have two (or more) options:

  • convert the OpenSSL key to PKCS8 unencrypted PEM with openssl pkcs8 -topk8 -nocrypt or just openssl pkey (in 1.0.0 up) -- or generate it as PKCS8 in the first place with genpkey or possibly req -newkey (both in 1.0.0 up) instead of ecparam -genkey. Use that blob de-base64-ed in PKCS8EncodedKeySpec

  • if in addition to bcprov you have or get bcpkix, that handles many (maybe all?) OpenSSL formats that plain Java does not. Rebuild, or just revert to, the original PEM format (which can be in memory) and you can parse with PEMReader and then convert with JcaPEMKeyConverter (or just a KeyFactory).

Both of those are covered by How to Load RSA Private Key From File for RSA, which can easily be modified for EC.

  • alternatively, since you've already done part of the PEM parsing, you could handle the rest yourself in the same way PEMParser does, simplified for private only:
import java.security.*;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
...
static void SO48996981BCparseECprivate () throws Exception {
    byte[] server_sec1 = DatatypeConverter.parseBase64Binary("MHgCAQEEIA27nM1klj9pVxOzJrO4aBLFvXTtOJnf+vMhiv3HA+3noAsGCSskAwMCCAEBB6FEA0IABG1erLtLyTbC5yN8gVY4a0JPO5eefKftWMbSQij2Ks5TaNNuj/tqigFqsk1g/l2UBBkIx2KdpeiY8nVddwMpzho=");
    ASN1Sequence seq = ASN1Sequence.getInstance(server_sec1);
    org.bouncycastle.asn1.sec.ECPrivateKey pKey = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(seq);
    AlgorithmIdentifier algId = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, pKey.getParameters());
    byte[] server_pkcs8 = new PrivateKeyInfo(algId, pKey).getEncoded();
    KeyFactory fact = KeyFactory.getInstance ("EC","BC");
    PrivateKey pkey = fact.generatePrivate (new PKCS8EncodedKeySpec(server_pkcs8));
    // for test only:
    System.out.println (pkey.getClass().getName() + " " + pkey.getAlgorithm());
}
  • finally, you don't really need Java for this. OpenSSL can do ECDH key agreement/derivation, either in library (called from code you write) or commandline openssl pkeyutl -derive -- although for the latter you need the keys in PEM files: the privatekey in any PEM format OpenSSL supports (which you already have) but the peer publickey in X.509 SubjectPublicKeyInfo format, which on the information given you may not and may have to build.