pythoncryptographyelliptic-curvediffie-hellman

Generate a Java compatible Diffie-Hellman using Python


I am trying to rewrite the following code in Python. The original code is written in Javascript using the sjcl library.

// Inputs
var serverPubX = "WIUBDotrk02Rk/apL11jQPbmX0quyaYz2EIkGUlVf7s=";
var serverPubY = "diZ2CbfSUy5Kr82OIfd4Ajusq2K+/kjGZ7ymcqVwn2k=";

// The code
var serverPubXBits = sjcl.codec.base64.toBits(serverPubX);
var serverPubYBits = sjcl.codec.base64.toBits(serverPubY);
var serverPubKeyPointBits = serverPubXBits.concat(serverPubYBits);

var serverPubKey = new sjcl.ecc.elGamal.publicKey(
    sjcl.ecc.curves.c256, serverPubKeyPointBits);

var clientKeys = sjcl.ecc.elGamal.generateKeys(256, 1);

// What I need:
var sharedKey = clientKeys.sec.dhJavaEc(serverPubKey);

My main problem is the dhJavaEc function. According to the sjcl documentation, it's a Java compatible Diffie-Hellman function. But I couldn't find anything equivalent in the pycryptodome library.

I checked the dhJavaEc code this is what it does:

// Looks like it converts the server key to Jacobian and then multiply it by client key
serverPubKey.J.toJac().mult(clientKeys.sec.I, serverPubKey.J).toAffine().x.toBits()

// serverPubKey.J is the X and Y keys concatenated:
sjcl.codec.base64.fromBits(serverPubKey.J.toBits())
"WIUBDotrk02Rk/apL11jQPbmX0quyaYz2EIkGUlVf7t2JnYJt9JTLkqvzY4h93gCO6yrYr7+SMZnvKZypXCfaQ=="

// In my example, clientKeys.sec.I is this:
sjcl.codec.base64.fromBits(clientKeys.sec.I.toBits())
"zIhDVlFUpWQiRP+bjyEIhSLq8rcB8+XInXGhm6JGcVI="

// And the calculated key is:
sjcl.codec.base64.fromBits(sharedKey)
"ZBin/RV1qnfKoIuel+5fzv1y8rn3UZkMPO3pXva3VzQ="

How can I generate a "sharedKey" equivalent using Python?


Solution

  • PyCryptodome does not seem to support ECDH at the moment, see Future plans. An alternative is the Cryptography library, see Elliptic Curve Key Exchange algorithm.

    The library expects the private key as int and the public key in uncompressed format as bytes object. The uncompressed format consists of the concatenated x and y coordinates preceded by a 0x04 byte.

    sjcl.ecc.curves.c256 defines secp256r1 (aka prime256v1 aka NIST P-256).

    Then a possible implementation in Python with Cryptography is:

    from cryptography.hazmat.backends import default_backend
    from cryptography.hazmat.primitives.asymmetric import ec
    import base64
    
    curve = ec.SECP256R1()
    
    clientKey = 'zIhDVlFUpWQiRP+bjyEIhSLq8rcB8+XInXGhm6JGcVI='
    privateKeyInt = int.from_bytes(base64.b64decode(clientKey), byteorder='big', signed=False)
    privateKey = ec.derive_private_key(privateKeyInt, curve, default_backend())
    
    serverPubKey = 'WIUBDotrk02Rk/apL11jQPbmX0quyaYz2EIkGUlVf7t2JnYJt9JTLkqvzY4h93gCO6yrYr7+SMZnvKZypXCfaQ=='
    publicKeyUncomp = b'\x04' + base64.b64decode(serverPubKey)
    publicKey = ec.EllipticCurvePublicKey.from_encoded_point(curve, publicKeyUncomp)
    
    sharedSecret = privateKey.exchange(ec.ECDH(), publicKey)
    print(base64.b64encode(sharedSecret).decode('utf8')) # ZBin/RV1qnfKoIuel+5fzv1y8rn3UZkMPO3pXva3VzQ= 
    

    which produces the same shared secret as the JavaScript code.