javacryptographyasn.1diffie-hellmanwolfssl

How to import/export Java EC keys to/from X9.63 encoding with Java 8


I am looking into writing a Java 8 program to do a ECDH exchange with another piece of software using a library (wolfSSL/wolfCrypt) that can only export/import ECC public keys in X9.63 format (their wc_ecc_export_x963() function). I would greatly prefer to do this using the providers that come with Java.

Therefore, I need to find out how to get Java to create a PublicKey object out of the X9.63 encoding of one and create the X9.63 encoded bytes of a PublicKey object.

I've written some test code to try to figure out what format Java is using:

    // Make a key
    ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
    KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
    keyGen.initialize(ecSpec);
    KeyPair pair = keyGen.generateKeyPair();

    // Let's see what Java thinks the encoding is
    System.out.println("Pubkey format: " + pair.getPublic().getFormat());
    System.out.println("Privkey format: " + pair.getPrivate().getFormat());

    // And write out the encoded forms to files so we can poke at them
    // with openssl, etc.
    try (FileOutputStream pubOut = new FileOutputStream("ecpub.der");
         FileOutputStream privOut = new FileOutputStream("ecpriv.der")) {
        pubOut.write(pair.getPublic().getEncoded());
        privOut.write(pair.getPrivate().getEncoded());
    }

This yields the output:

Pubkey format: X.509
Privkey format: PKCS#8

I can then do use openssl to investigate the public key structure:

$ openssl asn1parse -i -in ecpub.der -inform DER
    0:d=0  hl=2 l=  89 cons: SEQUENCE
    2:d=1  hl=2 l=  19 cons:  SEQUENCE
    4:d=2  hl=2 l=   7 prim:   OBJECT            :id-ecPublicKey
   13:d=2  hl=2 l=   8 prim:   OBJECT            :prime256v1
   23:d=1  hl=2 l=  66 prim:  BIT STRING

but that is opaque to me (I don't know ASN.1) and I have no idea what encoding format that is or even how to find out what encoding format it is.

And here's a hexdump of that Java representation:

$ hexdump -C ecpub.der
00000000  30 59 30 13 06 07 2a 86  48 ce 3d 02 01 06 08 2a  |0Y0...*.H.=....*|
00000010  86 48 ce 3d 03 01 07 03  42 00 04 a3 c4 5c 5d aa  |.H.=....B....\].|
00000020  93 70 8b 65 47 9b f9 83  17 01 37 23 30 d2 0c 6a  |.p.eG.....7#0..j|
00000030  c7 93 6e d4 70 b1 5b bf  8e 65 4f 96 70 7e e8 97  |..n.p.[..eO.p~..|
00000040  30 a2 6e e4 1f 50 bb 21  4f a6 7a 01 bd 96 a4 2f  |0.n..P.!O.z..../|
00000050  8b cd 0d d0 d2 4a 63 d1  68 d0 7b                 |.....Jc.h.{|
0000005b

UPDATE

Here's the hexdump of what I get out of wc_ecc_export_x963(). I'll include the C source code to my test program as well. openssl asn1parse chokes on the file, FWIW.

$ hexdump -C wolf.x963
00000000  04 f1 55 1b 03 d5 91 ed  03 d5 44 f9 09 b2 1e 59  |..U.......D....Y|
00000010  c7 4d ef 1a e9 de 51 16  4e b9 4d 8c 1d 10 73 d4  |.M....Q.N.M...s.|
00000020  9e 09 24 78 5a 03 c4 45  bf 0c 83 22 69 d8 52 ed  |..$xZ..E..."i.R.|
00000030  90 04 00 0c ea 38 95 a9  e5 da 96 d2 ae c4 5c 3a  |.....8........\:|
00000040  c8                                                |.|
00000041

And for reference, here's the program that produced that wolf.x963 file (note -- I haven't programmed in C in many years):

#include <stdio.h>
#include <wolfssl/options.h>
#include <wolfssl/wolfcrypt/error-crypt.h>
#include <wolfssl/wolfcrypt/settings.h>
#include <wolfssl/wolfcrypt/random.h>
#include <wolfssl/wolfcrypt/ecc.h>

int main() {
  // Make a key object ready for use.
  ecc_key key;
  wc_ecc_init(&key);

  // Make a random number generator object ready for use
  RNG rng;
  wc_InitRng(&rng);

  // Make a "32-byte" key.  According to wolfSSL,
  // this will use the SECP256R1 curve since that's
  // what they map to a request for a 32-byte key.
  wc_ecc_make_key(&rng, 32, &key);

  byte encoded[1024];
  word32 encodedLen = 0;
  int error;

  // According to the API docs, on entry encodedLen should
  // be a number equal to or larger than what the output
  // will be.  If it is not, the function will return BUFFER_E
  // and set encodedLen to how many bytes will be needed to
  // hold the exported data.
  error = wc_ecc_export_x963(&key, encoded, &encodedLen);
  printf("Error code = %d\n", error);
  if (error == BUFFER_E) {
    error = wc_ecc_export_x963(&key, encoded, &encodedLen);
    printf("Error code again = %d\n", error);
  }

  // Print out the byte values so that I can make sure
  // that I didn't somehow corrupt the data writing it
  // out as I am very rusty at C.
  //int i;
  //for (i = 0; i < encodedLen; i++) {
  //  printf("enc[%d] = %x\n", i, encoded[i]);
  //}

  FILE *outFile;
  outFile = fopen("wolf.x963", "wb");
  fwrite(encoded, encodedLen, 1, outFile);
  fclose(outFile);

  wc_ecc_free(&key);
  wc_FreeRng(&rng);

  return 0;
}

And its output:

 ./SaveEccKey
Error code = -132
Error code again = 0

Solution

  • What you seem to get from the wc_ecc_export_x963 function is simply the uncompressed point representation.

    Points on the curve of 256 bits would consist of two coordinates with that length. If these are represented as fixed length big endian unsigned integers then they would take ceil(256 / 8) = 32 bytes each, for a total of 64 bytes. These are prefixed with the uncompressed point indicator 04 of one byte giving you 65 bytes. As you can see in Java the length of the BIT STRING is 66 bytes. BIT STRINGs however contain a padding indicator, usually set to 00 indicating that the number of bits is a multiple of 8. In other words, the BIT STRING in the ASN.1 definition is simply the uncompressed point you get from wc_ecc_export_x963.

    So what you can do is to put everything before the 04 at position encoded.length - 133 before your point and then use the X.509 decoding facilities (KeyFactory) of Java. This I would however consider hacking; it would not be compatible with any other key size. You can however also use this answer I've provided quite some time ago.