Search code examples
javaccryptographybouncycastlembedtls

Cannot read public key from MbedTLS in Java BouncyCastle ECDH


I am trying to do an ECDH between embedded device running mbedTLS and Java using BouncyCastle. When I compare the produced key lengths I get a 66 bytes key made by mbedTLS and 65 bytes made by BC. Attaching pseudocode:

KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
kpg.initialize(256);
KeyPair localKp = kpg.generateKey();

ASN1Sequence sequence = DERSequence.getInstance(localKp.getPublic().getEncoded());
DERBitString subjectPublicKey = (DERBitString) sequence.getObjectAt(1);
byte[] encodedLocalPublicKey = subjectPublicKey.getBytes();
// encodedLocalPublicKey.length -> 65

MbedTLS:

mbedtls_ecdh_context ecdh;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_entropy_context entropy;
const char pers[] = "ecdh";

mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_entropy_init(&entropy);
mbedtls_ecdh_init(&ecdh);

int ret = 0;
if((ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy,
                               (const unsigned char *) pers,
                               sizeof pers )) != 0) {
    mbedtls_printf( " failed\n  ! mbedtls_ctr_drbg_seed returned %d\n", ret );
}

ret = mbedtls_ecp_group_load(&ecdh.grp, MBEDTLS_ECP_DP_SECP256R1);
if (ret != 0) {
    mbedtls_printf( " failed\n  ! mbedtls_ecp_group_load returned %d\n", ret );
}

size_t olen;
unsigned char buf[1024];
ret = mbedtls_ecdh_make_public(&ecdh, &olen, buf, sizeof(buf), mbedtls_ctr_drbg_random, &ctr_drbg);
// ret is 0, olen is 66

When I load the MbedTLS key into Java it throws java.lang.IllegalArgumentException: Invalid point encoding 0x41 :

byte[] publicKeyBytes = ... FROM MbedTLS
log.info("Public key length: {}", publicKeyBytes.length); // Shows 66
ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("P-256");
ECPoint point = ecSpec.getCurve().decodePoint(publicKeyBytes); // This line throws

I tried to do ECDH Java between Java and MbedTLS between MbedTLS. Both of the tests succeeded but they somehow cannot exchange cross platforms.

What am I doing wrong? Sorry for maybe obvious problem but I am trying get handle of it. I will appreciate any help.

Thank you.


Solution

  • From the API documentation for mbedtls_ecdh_make_public:

    This function generates a public key and exports it as a TLS ClientKeyExchange payload.

    So this is a TLS-specific function. From e.g. RFC 8442, we see that the encoding for an elliptic curve point is:

    struct {
        opaque point <1..2^8-1>;
    } ECPoint;
    

    That requires some familiarity with the TLS presentation language, but in the end it amounts to: TLS adds an extra (unsigned) byte to the start of the usual point encoding, which will contain the length of the remainder.

    In your example, you can see that mbedtls outputs 66 bytes. The first byte contains the value 65, which is the length of the rest of the output, and those 65 bytes at the end are in the same format that BC is working with. You will need to extract the last 65 bytes, or ignore the first byte in some way.

    P.S. The way you are generating the KeyPair in Java is very fragile, since you specify only the size and not the exact curve. BC does default to P-256 for 256 bits, but it's not required behaviour and other providers (or some future BC version) might choose some other curve. Better to explicitly generate a P-256 KeyPair. I would also suggest clearer code to get the public key encoding in the Java version:

    byte[] spkiEnc = localKp.getPublic().getEncoded();
    SubjectPublicKeyInfo spki = SubjectPublicKeyInfo.getInstance(spkiEnc);
    byte[] encodedLocalPublicKey = spki.getPublicKeyData().getOctets();