2

I need to generate openssl keypair in java which simulate the below:

openssl ecparam -name prime256v1 -genkey -noout -out prime256v1.key

openssl pkcs8 -topk8 -in prime256v1.key -out prime256v1-priv.pem -nocrypt

openssl ec -in prime256v1-priv.pem -pubout -out prime256v1-pub.pem

My java program is as below:

public static void main(String args[]) throws Exception{

    Security.addProvider(new BouncyCastleProvider());
    KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC");
    ECGenParameterSpec spec = new ECGenParameterSpec("secp256r1");
    g.initialize(spec);
    KeyPair keyPair = g.generateKeyPair();

    byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
    String publicKeyContent = Base64.encode(publicKeyBytes);
    String publicKeyFormatted = "-----BEGIN PUBLIC KEY-----" + System.lineSeparator();
    for (final String row:
            Splitter
                    .fixedLength(64)
                    .split(publicKeyContent)
            )
    {
        publicKeyFormatted += row + System.lineSeparator();
    }
    publicKeyFormatted += "-----END PUBLIC KEY-----";
    BufferedWriter writer = new BufferedWriter(new FileWriter("publickey.pem"));
    writer.write(publicKeyFormatted);
    writer.close();

    byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
    String privateKeyContent = Base64.encode(privateKeyBytes);
    String privateKeyFormatted = "-----BEGIN PRIVATE KEY-----" + System.lineSeparator();
    for (final String row:
            Splitter
                    .fixedLength(64)
                    .split(privateKeyContent)
            )
    {
        privateKeyFormatted += row + System.lineSeparator();
    }
    privateKeyFormatted += "-----END PRIVATE KEY-----";
    BufferedWriter writer2 = new BufferedWriter(new FileWriter("privatekey.pem"));
    writer2.write(privateKeyFormatted);
    writer2.close();
}

The above code works but the private key generated seem to be longer than the one generated via the command line utility I have mentioned at the top.

Privatekey with command line:

-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgGuyf3+/6+rnDKw0D
WbxVyggwNL0jlTVAzGm6cpl3ji2hRANCAAQ7zLtxLLvl6LJHJAlYAZr4hAc09fZn
bAniYIeKVqVBdKIvb5R445PFiUDFcfyneeX/resPXJHMEm/vAxfQeMqL
-----END PRIVATE KEY-----

Privatekey with java:

-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgYFPrkmxnwjVBgpUV
B02/luLD1rt9
UWZHj62YdhwYQESgCgYIKoZIzj0DAQehRANCAATZp7Jl8KXXApA
hvv9qeQtX5LbHQkrCdx3DfkUC
GgCUMSJWKxs7yJPNKtFZnFUTFZfyEF76fdEzky
zIon5H04MX
-----END PRIVATE KEY-----

Even if I remove the 2 extra lines here, even then, this seems to be a bigger key.

Publickey with Command line:

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEO8y7cSy75eiyRyQJWAGa+IQHNPX2
Z2wJ4mCHilalQXSiL2+UeOOTxYlAxXH8p3nl/63rD1yRzBJv7wMX0HjKiw==
-----END PUBLIC KEY-----

Public key with Java:

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2aeyZfCl1wKQIb7/ankLV+S2x0JK
wncdw35FAhoA
lDEiVisbO8iTzSrRWZxVExWX8hBe+n3RM5MsyKJ+R9ODFw==
-----END PUBLIC KEY-----

So, my first question is about the private key length. It seems longer. My second question is it seems I am not splitting the generated key bytes properly. There are certainly more lines than expected. How to correct it?

T Anna
  • 874
  • 5
  • 21
  • 52

1 Answers1

2

... the private key length ... seems longer

It is, or to be precise the structure representing/containing the private key is longer. Java includes the optional -- and unnecessary (redundant) in a PKCS8 -- 'parameters' field of the algorithm-specific data, which is ECPrivateKey defined in SEC1 appendix C.4, while OpenSSL does not. This will be ignored when read back in. The actual key value within both structures is the correct size.

I am not splitting the generated key bytes properly

Rather, splitting the (base64) characters that encode the bytes (of the key structure).

Look at the output from your Base64.encode before the Splitter. I bet you will find it already contains newlines after each 76 base64 characters, consistent with the MIME standards (RFC 1521 et seq) which some people think are more common (or more important?) or at least newer than PEM. (Although XML and JWX are even newer and now quite common, and don't insert linebreaks at all.) As a result your Splitter takes:

  • the first 64 chars from the first line
  • the remaining 12 chars from the first line, a newline, and 51 chars from the second line
  • the remaining 25 chars from the second line, a newline, and (up to) 38 chars from the third line
  • etc.

Although OpenSSL writes PEM files with body linebreaks every 64 chars (except the last line), per the PEM standard (RFC 1421), it has always been able to read files with any multiple of 4 up to 76 chars, consistent with MIME. Recent versions since 1.1.0 in 2016, now fairly widely adopted, can read lines up to several hundred chars. Thus if your files are to be read by (anything using) the OpenSSL library, you could just write the split-at-76 version without any further change except ensuring there is a linebreak terminating the last line. Other software may vary; if you need to be safe, or strictly compliant, first remove the linebreaks from your Base64.encode output and then add them back at the correct spacing of 64. See the recently published respecification.

PS: if you use Java to put this key in a PKCS12 keystore (which requires you have/get/create a certificate for it), openssl commandline can read that directly, and convert (1) the privatekey to PEM, (2) the certificate to PEM from which you can extract the publickey in PEM.

Community
  • 1
  • 1
dave_thompson_085
  • 34,712
  • 6
  • 50
  • 70
  • I have raised another question. Would you be able to take a look please? https://stackoverflow.com/questions/61589895/unable-to-read-bouncycastle-generated-privatekey-in-java – T Anna May 04 '20 at 10:18