0

I need help as to how to correctly construct the Java Private.pem. I have an ECDSA pem file created in java and one in python. I have correct implementation of the python one, but the java one isn't correct.

KeyPair pair = GenerateKeys();
PrivateKey priv = pair.getPrivate();
PublicKey pub = pair.getPublic();

// Convert to Bytes then Hex for new account params
byte[] bytePriv = priv.getEncoded();
byte[] bytePub = pub.getEncoded();

// save keys
SaveKeyToFile(bytePriv, "private", "private");

// Calls this function
public static void SaveKeyToFile(byte[] Key, String filename, String keyType) throws IOException, NoSuchAlgorithmException, NoSuchAlgorithmException, InvalidKeySpecException {
    StringWriter stringWriter = new StringWriter();
    PemWriter pemWriter = new PemWriter(stringWriter);
    PemObjectGenerator pemObject = new PemObject("EC " + keyType.toUpperCase() + " KEY", Key);


    pemWriter.flush();
    pemWriter.close();
    String privateKeyString = stringWriter.toString();
    FileUtils.writeStringToFile(new File(filename + ".pem"), privateKeyString);
}

For the ASN1 parse with OpenSSL of both Java and Python (Doesn't include hexdump because of its length):

JAVA
0:d=0  hl=3 l= 141 cons: SEQUENCE          
3:d=1  hl=2 l=   1 prim: INTEGER           :00
6:d=1  hl=2 l=  16 cons: SEQUENCE          
8:d=2  hl=2 l=   7 prim: OBJECT            :id-ecPublicKey
17:d=2  hl=2 l=   5 prim: OBJECT           :secp256k1
24:d=1  hl=2 l= 118 prim: OCTET STRING     [HEXDUMP] 

PYTHON
0:d=0  hl=2 l= 116 cons: SEQUENCE          
2:d=1  hl=2 l=   1 prim: INTEGER           :01
5:d=1  hl=2 l=  32 prim: OCTET STRING      [HEXDUMP] 
39:d=1  hl=2 l=   7 cons: cont [ 0 ]        
41:d=2  hl=2 l=   5 prim: OBJECT           :secp256k1
48:d=1  hl=2 l=  68 cons: cont [ 1 ]        
50:d=2  hl=2 l=  66 prim: BIT STRING    

As for the output PEM:

Java:
-----BEGIN EC PRIVATE KEY-----
MIGNAgEAMBAGByqGSM49AgEGBSuBBAAKBHYwdAIBAQQgiEc2TOzXj4mrUisuv+0p
xZ/z+Z/VyIvWug17zjNOPIKgBwYFK4EEAAqhRANCAATWi28vsiZdfqbqodDZc1uz
UFwNcu90l2ezayH0L4ZYB+MfReMCBFG/P6kn12GLj3DipqmvRucgmOlFVmggm+nH
-----END EC PRIVATE KEY-----

Python:
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEINTaEQvUvtOQlanp0lWv0KBcSs8IltKYH26OkoNxLQc5oAcGBSuBBAAK
oUQDQgAEcp3sseSpAXzIzWCwswYnsVnIZ0EEtkXl/CJWChQHilWLwWsxGq11/g/6
38UfZbsjE5TSf6zT8VXvTZE++u9c+A==
-----END EC PRIVATE KEY-----

Help would be very much appreciated!

dtseng
  • 15
  • 1
  • 6
  • JCE `PrivateKey.getEncoded()` returns unencrypted PKCS8 encoding; the correct PEM type for this is `BEGIN/END PRIVATE KEY` (without specific algorithm like `EC`) and then OpenSSL and things compatible with OpenSSL will read it. If you need OpenSSL's 'traditional' algorithm-specific form that's harder but mostly a dupe; I have to go now but can fill that in later if needed. PS: EC keys/keyparis are _usable_ for ECDSA but not limited to it. A _cert_ OTOH can be limited by KeyUsage. – dave_thompson_085 Dec 12 '17 at 18:24
  • Thanks for the info. The java pem output has to fit this spec: https://tools.ietf.org/html/rfc5915. It's supposed to have EC PRIVATE KEY. Both keys contain the secp256k1 curve. The hex dump is longer on the java one and the integer is 0 rather than 1. Not quite sure how to get into the details to modify it to be correct. I'm currently experimenting with MiscPEMGenerator to see if it will help me arrive at the correct output. – dtseng Dec 13 '17 at 09:30
  • did you really intend the Koblitz curve? Usually people want secp256r1 (note the 'r') since that's the one that corresponds to NIST P-256 – lockcmpxchg8b Dec 13 '17 at 16:47
  • @lockcmpxchg8b: nope. X.509=PKIX SPKI for publickey is no version, AlgId plus BIT STRING wrapping the algo-specific part; PKCS8 unencrypted for privatekey is version=0 then AlgId plus OCTET STRING wrapping the algo-specific part. This matches and is PKCS8 privatekey, even though ECC privatekey reuses the OID X9.62 assigned for ECC publickey. Also secp256r1 is the NIST curve commonly used for SSL/TLS (and previously in SuiteB), but secp256k1 is the curve used for Bitcoin, which has become fairly popular and famous recently. – dave_thompson_085 Dec 14 '17 at 00:18
  • I'm stunned they reused the OID. But I'm very glad to learn this. I'll delete the relevant comment. – lockcmpxchg8b Dec 14 '17 at 02:32
  • @lockcmpxchg8b Curve secp256k1 was intended. – dtseng Jan 09 '18 at 10:04

1 Answers1

2

(Sorry for the delay.)

Okay, you do need the algorithm-specific form but JCE PrivateKey.getEncoded() returns the PKCS8 (handles all algorithms) encoding, which is mostly the same Q as How can I convert a .p12 to a .pem containing an unencrypted PKCS#1 private key block? except you want ECC rather than RSA and (more important) you are using Java with BouncyCastle.

So, your options are:

  • write the PKCS8 encoding, either in PEM or directly in binary (DER), and use openssl ec or in 1.1.0 openssl pkey -traditional to convert it to the algorithm-specific PEM form, which is SEC1 (rfc5915 is basically a republication of SEC1).

  • write the PKCS8 encoding (either PEM or DER) and use openssl asn1parse -strparse to extract the algorithm-specific part, or determine the offset of the algorithm-specific part and extract it directly. A 256-bit ECC keypair DER encodes with mostly single-octet lengths, but the OID identifying the (named) curve may vary in size for different curves; as your example shows, it is 24+2 for secp256k1, so you can just give Arrays.copyOfRange(Key,26,Key.length) to your current logic.

    For ECC keypairs larger than 256-bits, the DER encoding may need to vary, and near the borderline could depend on whether the public value in the keypair is in uncompressed or compressed form, which you have no practical way to control (JCE doesn't provide for options within an encoding). This case would require handling at least some of the DER manually, in which case I would go to the next option instead.

  • BC, which you're already using, exposes the ability to manipulate PKCS8 structures and you can use that to extract the algorithm-specific SEC1 encoding, something like this:

    import org.bouncycastle.asn1.ASN1Object;
    import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
    import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; // as defined by PKCS8
    ...
    PrivateKey k = /* whatever */;
    PemWriter w = new PemWriter (/* underlying writer */);
    PrivateKeyInfo i = PrivateKeyInfo.getInstance(ASN1Sequence.getInstance(k.getEncoded()));
    if( ! i.getPrivateKeyAlgorithm().getAlgorithm().equals(X9ObjectIdentifiers.id_ecPublicKey) ){
        throw new Exception ("not EC key");
    }
    ASN1Object o = (ASN1Object) i.parsePrivateKey(); 
    w.writeObject (new PemObject ("EC PRIVATE KEY", o.getEncoded("DER")));  
    // DER may already be the default but safer to (re)specify it 
    w.close();

dave_thompson_085
  • 34,712
  • 6
  • 50
  • 70
  • Thank you for the assistance. I'm going to have to attempt with option 3 (BC) since I'm constrained to solving this problem in Java. – dtseng Dec 18 '17 at 10:36
  • Thanks @dave_thompson_085. How do I go about reading and loading this type of key back to a `PrivateKey` instance? – dtseng Dec 18 '17 at 13:50
  • @dtseng: with BC, see my answer to https://stackoverflow.com/questions/22963581/reading-elliptic-curve-private-key-from-file-with-bouncycastle . Without BC and constrained to Java you'd need to (re)construct the PKCS8 yourself, which is much more than fits in a comment. – dave_thompson_085 Dec 19 '17 at 06:45