Search code examples
javabouncycastlepublic-keysecret-keyecdh

Javascript - Java ECDH


In my scenario there are Alice and Bob who have agreed on which curve to use.

  • Alice generates the public key and the private key
  • Alice sends the public key to Bob
  • Bob generates his keys and generates the session key ( or secret key, or shared key ) based on the Alice public key he received.

My problem is that Alice's public key is actually a point, so it has the xy format.

I need to convert the x,y coordinates bytes into a ECPublicKey.

This is the source code I'm using

    // outerPublicKey is the raw bytes from x,y coordinates in hex format
    KeyFactory kf = KeyFactory.getInstance("EC");

    PublicKey remoteAlicePub = kf.generatePublic(new X509EncodedKeySpec(outerPublicKey));

    KeyPairGenerator bobKeyGen = KeyPairGenerator.getInstance("ECDH", "BC");
    bobKeyGen.initialize(new ECGenParameterSpec(properties.getCurveName()), new SecureRandom());

    KeyPair bobPair = bobKeyGen.generateKeyPair();
    ECPublicKey bobPub = (ECPublicKey)bobPair.getPublic();
    ECPrivateKey bobPvt = (ECPrivateKey)bobPair.getPrivate();

    byte[] bobPubEncoded = bobPub.getEncoded();
    byte[] bobPvtEncoded = bobPvt.getEncoded();

    KeyAgreement bobKeyAgree = KeyAgreement.getInstance("ECDH");
    bobKeyAgree.init(bobPvt);
    bobKeyAgree.doPhase(remoteAlicePub, true);

    return DatatypeConverter.printHexBinary(bobKeyAgree.generateSecret());

the problem is:

    new X509EncodedKeySpec(outerPublicKey);

How can I create a public key from the xy coordinates of the point? because outerPublicKey is a raw byte array of x,y coordinates, what kind of format should i use?


Solution

  • I solved in this way ( Java Server Side )

    // arrive a string like this 04456cb4ba8ee9263311485baa8562c27991f7ff22d59f3d8245b9a05661d159911b632a6f8a7a080d82f4ca77e4d12bb201b89c8ec93f61d5b4dd22df42e1b482
    Map<String, Object> result = new HashMap<>();
        try {
    
            // set provider
            Security.addProvider(new BouncyCastleProvider());
    
            // transform from hex to ECPublicKey
            byte[] ecRawExternalPublicKey = HexFormat.of().parseHex(clientPublicKey.getPublicKey());
            ECPublicKey ecExternalPublicKey = null;
            KeyFactory externalKeyFactor = null;
    
            ECNamedCurveParameterSpec ecExternalNamedCurveParameterSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
            ECCurve curve = ecExternalNamedCurveParameterSpec.getCurve();
            EllipticCurve ellipticCurve = EC5Util.convertCurve(curve, ecExternalNamedCurveParameterSpec.getSeed());
            java.security.spec.ECPoint ecPoint = ECPointUtil.decodePoint(ellipticCurve, ecRawExternalPublicKey);
            java.security.spec.ECParameterSpec ecParameterSpec = EC5Util.convertSpec(ellipticCurve, ecExternalNamedCurveParameterSpec);
            java.security.spec.ECPublicKeySpec externalPublicKeySpec = new java.security.spec.ECPublicKeySpec(ecPoint, ecParameterSpec);
    
            externalKeyFactor = java.security.KeyFactory.getInstance("EC");
            // this is externalPubicKey
            ecExternalPublicKey = (ECPublicKey) externalKeyFactor.generatePublic(externalPublicKeySpec);
    
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDH", "BC");
            keyGen.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom());
    
            KeyPair pair = keyGen.generateKeyPair();
            ECPublicKey pub = (ECPublicKey)pair.getPublic();
            ECPrivateKey pvt = (ECPrivateKey)pair.getPrivate();
    
            byte[] pubEncoded = pub.getEncoded();
            byte[] pvtEncoded = pvt.getEncoded();
    
            KeyAgreement keyAgree = KeyAgreement.getInstance("ECDH");
            keyAgree.init(pvt);
            keyAgree.doPhase(ecExternalPublicKey, true);
    
            System.out.println("sharedKey: "+ this.bytesToHex( keyAgree.generateSecret() ));
    
            // internal public key
            return "04"+ pub.getW().getAffineX().toString(16) + pub.getW().getAffineY().toString(16)
    
        }
        catch (Exception e ){
            e.printStackTrace();
            return null;
        }
    

    Javascript ( Client Side )

    ecdhHandShake() {
        let _this = this;
    
        this.keyGeneration()
        .then( k => {
            ajax({
                url: "http://localhost:5050/test/ecdh/handshake",
                headers: {
                    "Content-Type": "application/json"
                },
                body: {
                    publickey: this.buf2Hex(this.publicKey)
                },
                method: 'POST',
                crossDomain: true,
                responseType: 'json'
            })
            .subscribe(
                payload => {
                    const publicKey = _this.hex2Arr(payload.response.publicKey);
                    _this.serverPublicKey = _this.hex2Arr(payload.response.publicKey);
    
                    _this.importServerKey()
                    .then(sharedSecret => {
                        const sharedSecretHex = this.buf2Hex(sharedSecret);
                        console.log("shared key: "+ sharedSecretHex);
                    })
                    .catch( e => {
                        console.log(e);
                    })
                },
                error => {
                    console.log(error);
                },
                () => console.log('done')
            )
            ;
        })
        .catch( e => {
            console.log(e);
        })
        ;
    }
    
    
    keyGeneration() {       
        
        let _this = this;
        return window.crypto.subtle.generateKey(
            {
                name: "ECDH",
                namedCurve: "P-256", // the curve name
            },
            true, // <== Here if you want it to be exportable !!
            ["deriveKey", "deriveBits"] // usage
        )
        .then(key => {
            _this.keys = key;
            return window.crypto.subtle.exportKey(
                "raw", //can be "jwk" (public or private), "raw" (public only), "spki" (public only), or "pkcs8" (private only)
                _this.keys.publicKey
            )
            .then(rawPublicKey => {
                _this.publicKey = rawPublicKey;
                return rawPublicKey;
            })
            .catch(function(err){
                console.error(err);
            })
            ;
        })
        .catch(function(err){
            console.error(err);
        })
        ;
    
    }
    
    importServerKey() {
    
        return window.crypto.subtle.importKey(
            'raw',
            this.serverPublicKey,
            {
                name: 'ECDH',
                namedCurve: 'P-256'
            },
            true,
            []
        )
        .then(aliceKeyImported => {
            return window.crypto.subtle.deriveBits(
                {
                    name: 'ECDH',
                    namedCurve: 'P-256',
                    public: aliceKeyImported
                },
                this.keys.privateKey,
                256)
        })
        .catch( e => {
            console.log(e);
        })
    }
    
    
    hex2Arr( str ) {
        if (!str) {
            return new Uint8Array();
        }
        const arr = [];
        for (let i = 0, len = str.length; i < len; i+=2) {
            arr.push(parseInt(str.substr(i, 2), 16));
        }
        return new Uint8Array(arr);
    }
    
    buf2Hex( buf ) {
        return Array.from(new Uint8Array(buf))
            .map(x => ('00' + x.toString(16)).slice(-2))
            .join('')
    }