bcgit / bc-java

Bouncy Castle Java Distribution (Mirror)
https://www.bouncycastle.org/java.html
MIT License
2.29k stars 1.13k forks source link

DH->Curve25519->ServerKeyExchange->Bouncy Castle is not working #251

Closed plmn22 closed 6 years ago

plmn22 commented 6 years ago

I need to create a shared secret for the DH (Diffie–Hellman Key Exchange), using my private key and a public key that I receive from Apache Server. The code is written in Java + Bouncy Castle 1.57.

I have attached a screen shot from OpenSSL:

I have used openSSL in order to connect to a server, that implement, Curve25519. I have taken the public key, that have returned in the response and use it, as byte array, in the following code:

// **** START OF BOUNCY CASTLE CODE

byte[] publicKey = new byte[]{(byte)0xF1, (byte)0x6D, (byte)0x48, (byte)0x25, (byte)0x0C, (byte)0xE2, (byte)0xA2, (byte)0xA4, (byte)0xFD, (byte)0x4D, (byte)0x9B, (byte)0x08, (byte)0x57, (byte)0x7B, (byte)0x2D, (byte)0x3F, (byte)0x92, (byte)0xC6, (byte)0x4D, (byte)0x09, (byte)0x3C, (byte)0xD9, (byte)0x68, (byte)0xE6, (byte)0xC7, (byte)0x32, (byte)0x5E, (byte)0x40, (byte)0x30, (byte)0xB7, (byte)0xF2, (byte)0x06 };

ECParameterSpec ecP = ECNamedCurveTable.getParameterSpec(this.namedCarved);

ECPublicKeySpec pubKey = new ECPublicKeySpec(ecP.getCurve().decodePoint(publicKey), ecP);

KeyFactory kf = KeyFactory.getInstance("ECDH", "BC"); return kf.generatePublic(pubKey);

// **** END OF BOUNCY CASTLE CODE

The problem it that the function ecP.getCurve().decodePoint(publicKey) throws an exception: "java.lang.IllegalArgumentException: Invalid point encoding 0xF1"

peterdettman commented 6 years ago

This is a point format and/or curve form mismatch. Skip to the last paragraph for the easy solution, or read on if you need to make things work via the provider code above.

X25519 uses the Montgomery curve "Curve25519", and specifies the public key format as the (exactly) 32-byte X coordinate (little-endian).

On the other hand, when you get an implementation of "Curve25519" (or any curve) from ECNamedCurveTable, it will be for a short-Weierstrass (SW) curve, and the expected public key format is from the SEC standards, so that it includes a format byte at the start, followed by the 32-byte X coordinate, and possibly the Y coordinate, both in big-endian order.

This can be made to work by converting the input as follows:

Sample code:

    static BigInteger P = BigInteger.ONE.shiftLeft(255).subtract(BigInteger.valueOf(19));
    static BigInteger D = BigInteger.valueOf(3).modInverse(P).multiply(BigInteger.valueOf(486662)).mod(P);

    byte[] convertInput(byte[] montPubKey)
    {
        BigInteger Xm = new BigInteger(1, Arrays.reverse(montPubKey));
        BigInteger Xw = Xm.add(D).mod(P);

        byte[] weierPubKey = BigIntegers.asUnsignedByteArray(33, Xw);
        weierPubKey[0] = 0x02;

        return weierPubKey;
    }

If you want to also send a public key in X25519 format, you'll need to do a similar conversion (Xm = Xw - D mod P) from the point encoding you get from the Weierstrass curve.

However I should point out that we have just committed a proper implementation of X25519 (https://github.com/bcgit/bc-java/commit/1f559bba32d601ddc76e1b306c566ba20d23b4ea). We have more work to do on trying to present that in the provider and through the usual interfaces, but if you just want to do ECDH with X25519, you could use that class directly (copy it for now, or wait for the next release - or beta).

plmn22 commented 6 years ago

Thanks for your answer. It was very accurate and also the explanation was good.

But, I have another question:

Now i need to generate X25519 public key. I'm generating that public key, using BC library. When i check the public key that i get from BC, then i can see that it is 64 bytes. According to your explanation I think that BC output it in short-Weierstrass format, while i need it in Montgomery curve format.

How can i convert it to 32 bytes?

This is my java code:

X9ECParameters ecP = CustomNamedCurves.getByName("Curve25519"); this.ECCPointCompressed = true;

ECParameterSpec ecSpec=new ECParameterSpec(ecP.getCurve(), ecP.getG(),ecP.getN(), ecP.getH(), ecP.getSeed());

KeyPairGenerator kpgen; kpgen = KeyPairGenerator.getInstance("ECDH", "BC"); kpgen.initialize(ecSpec, new SecureRandom()); pairA = kpgen.generateKeyPair(); ECPublicKey eckey = (ECPublicKey)pairA.getPublic(); var public_key = eckey.getQ().getEncoded(true);

The "public_key" returned here is 64 bytes!

Please Advice! Thanks, Asi Fefer.

peterdettman commented 6 years ago

If you need to do the full X25519 ECDH, then I am going to strongly recommend that you use the classes added here: https://github.com/bcgit/bc-java/commit/1f559bba32d601ddc76e1b306c566ba20d23b4ea . It should be clear how to use it by referring to the X25519Test.testECDH method.

The way that private keys are generated and/or used in X25519 have some subtle differences compared to JCE providers' behaviour for generic SW curves. Also, X25519 permits public keys on the twist of the curve, which will instead cause exceptions in SW implementations. So it's not clear to me that it can be made to work in the general case, without stepping outside of JCE and doing parts of the operations yourself.

In which case, you may as well use the new code, which is a direct implementation of X25519, so requires no complicated adapters, and performs quite a bit faster besides. If you need any advice on using these classes would you please post further questions to the dev-crypto mailing list (http://bouncycastle.org/mailing_lists.html).

hamirshekhawat commented 3 years ago

@peterdettman I found this issue while trying to figure out how to create compatible keys between pointycastle and bouncycastle. Following is the java code

        kpg = KeyPairGenerator.getInstance("EC", "BC");
        X9ECParameters ecP = CustomNamedCurves.getByName("Curve25519");
        ECParameterSpec ecSpec = EC5Util.convertToSpec(ecP);
        kpg.initialize(ecSpec);

        final KeyPair kp = kpg.genKeyPair();

Is it possible to do the same in PointyCastle? If not can you suggest a way to do this using OpenSSL or Libsodium?

peterdettman commented 3 years ago

@hamirshekhawat I assume you should be using KeyPairGenerator.getInstance("X25519", "BC"). I don't think PointyCastle has support for X25519 though.

hamirshekhawat commented 3 years ago

Thanks @peterdettman for confirmation.