How Does BouncyCastle Generate ECDH "Keys"?

micah :

I have an application that is using BouncyCastle as a security provider, but I want to switch to a different one that uses OpenSSL directly (Conscrypt). The issue I'm having is that I'm using ECDH "Keys" from the KeyGenerator provided by BouncyCastle, but don't have similar KeyGenerator in my other library.

To compare the two I am going to decode the points using both methods with the following input-

line breaks added for readability

BADX_GAXp03z_5p05O1-op61KJAl4j9U2sBnAnJ4p_6GSAIyFGU3lM
oC4aIXw_2qlTnplykArgjvwCWw-2g6L44

Using the BouncyCastle method-

public org.bouncycastle.jce.interfaces.ECPublicKey loadECPublicKeyBC(String encodedPublicKey) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException, IOException {
    Base64.Decoder base64Decoder = Base64.getUrlDecoder();
    byte[] decodedPublicKey = base64Decoder.decode(encodedPublicKey);
    KeyFactory keyFactory = KeyFactory.getInstance("ECDH", "BC");

    ECParameterSpec ecParameterSpec = ECUtil.getECParameterSpec(openSSLProvider, "prime256v1");
    ECPoint ecPoint = ECUtil.decodePoint(decodedPublicKey, ecParameterSpec.getCurve());

    ECPublicKeySpec pubSpec = new ECPublicKeySpec(ecPoint, ecParameterSpec);
    org.bouncycastle.jce.interfaces.ECPublicKey ecPublicKey = (org.bouncycastle.jce.interfaces.ECPublicKey)keyFactory.generatePublic(pubSpec);

    return ecPublicKey;
}

The getAlgorithm returned is EC. The getFormat returned is X.509.

The getEncoded value of this is-

[48,-126,1,51,48,-127,-20,6,7,42,-122,72,-50,61,2,1,48,
-127,-32,2,1,1,48,44,6,7,42,-122,72,-50,61,1,1,2,33,0,
-1,-1,-1,-1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,48,68,4,32,-1,-1,-1,-1,0,0,0,1,
0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-4,4,32,90,-58,53,-40,-86,58,-109,-25,-77,-21,-67,85,
118,-104,-122,-68,101,29,6,-80,-52,83,-80,-10,59,-50,60,
62,39,-46,96,75,4,65,4,107,23,-47,-14,-31,44,66,71,-8,-68,
-26,-27,99,-92,64,-14,119,3,125,-127,45,-21,51,-96,-12,
-95,57,69,-40,-104,-62,-106,79,-29,66,-30,-2,26,127,-101,
-114,-25,-21,74,124,15,-98,22,43,-50,51,87,107,49,94,-50,
-53,-74,64,104,55,-65,81,-11,2,33,0,-1,-1,-1,-1,0,0,0,0,-1,
-1,-1,-1,-1,-1,-1,-1,-68,-26,-6,-83,-89,23,-98,-124,-13,
-71,-54,-62,-4,99,37,81,2,1,1,3,66,0,4,0,-41,-4,96,23,-89,
77,-13,-1,-102,116,-28,-19,126,-94,-98,-75,40,-112,37,-30,
63,84,-38,-64,103,2,114,120,-89,-2,-122,72,2,50,20,101,55,
-108,-54,2,-31,-94,23,-61,-3,-86,-107,57,-23,-105,41,0,-82,
8,-17,-64,37,-80,-5,104,58,47,-114]

Just using the BouncyCastle EC algorithm (not ECDH) I get-

[48,-126,1,51,48,-127,-20,6,7,42,-122,72,
-50,61,2,1,48,-127,-32,2,1,1,48,44,6,7,42,
-122,72,-50,61,1,1,2,33,0,-1,-1,-1,-1,0,0,
0,1,0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,48,68,4,32,-1,-1,-1,-1,
0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-4,4,32,90,-58,53,-40,
-86,58,-109,-25,-77,-21,-67,85,118,-104,-122,
-68,101,29,6,-80,-52,83,-80,-10,59,-50,60,62,
39,-46,96,75,4,65,4,107,23,-47,-14,-31,44,66,
71,-8,-68,-26,-27,99,-92,64,-14,119,3,125,
-127,45,-21,51,-96,-12,-95,57,69,-40,-104,-62,
-106,79,-29,66,-30,-2,26,127,-101,-114,-25,
-21,74,124,15,-98,22,43,-50,51,87,107,49,94,
-50,-53,-74,64,104,55,-65,81,-11,2,33,0,-1,-1,
-1,-1,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-68,-26,
-6,-83,-89,23,-98,-124,-13,-71,-54,-62,-4,99,
37,81,2,1,1,3,66,0,4,0,-41,-4,96,23,-89,77,
-13,-1,-102,116,-28,-19,126,-94,-98,-75,40,
-112,37,-30,63,84,-38,-64,103,2,114,120,-89,
-2,-122,72,2,50,20,101,55,-108,-54,2,-31,-94,
23,-61,-3,-86,-107,57,-23,-105,41,0,-82,8,-17,
-64,37,-80,-5,104,58,47,-114]

Now with the Conscrypt method-

public ECPublicKey loadECPublicKey(String encodedPublicKey) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException, IOException {
    Base64.Decoder base64Decoder = Base64.getUrlDecoder();
    byte[] decodedPublicKey = base64Decoder.decode(encodedPublicKey);
    KeyFactory keyFactory = KeyFactory.getInstance("EC", "Conscrypt");

    ECParameterSpec ecParameterSpec = ECUtil.getECParameterSpec(openSSLProvider, "prime256v1");
    ECPoint ecPoint = ECUtil.decodePoint(decodedPublicKey, ecParameterSpec.getCurve());

    ECPublicKeySpec pubSpec = new ECPublicKeySpec(ecPoint, ecParameterSpec);
    ECPublicKey ecPublicKey = (ECPublicKey)keyFactory.generatePublic(pubSpec);

    return ecPublicKey;
}

The getAlgorithm returned is EC. The getFormat returned is X.509. The getEncoded value of this is-

[48,89,48,19,6,7,42,-122,72,-50,61,2,1,6,8,42,
-122,72,-50,61,3,1,7,3,66,0,4,0,-41,-4,96,23,
-89,77,-13,-1,-102,116,-28,-19,126,-94,-98,-75,
40,-112,37,-30,63,84,-38,-64,103,2,114,120,-89,
-2,-122,72,2,50,20,101,55,-108,-54,2,-31,-94,23,
-61,-3,-86,-107,57,-23,-105,41,0,-82,8,-17,-64,
37,-80,-5,104,58,47,-114]

Ignoring the discrepancies between both EC generated keys. What does BouncyCastle do in the ECDH KeyGenerator?

DH being a KeyAgreement, I assume it's generating an EC key and running it through a DH KeyAgreement- but what is it initializing as the private key when nothing is specified in the KeyGenerator spec?

Also. Why when I use the EC algorithm with both providers do I get different results for the same algorithm when both are using the prime256v1 spec? I would assume these would at least be equal.

Edit:

The ECUtil comes from sun.security.util.ECUtil.

For any of the classes where both BC and the Java Security Library share a common name in my examples (e.g. ECPoint)- it's always the Java Security Library. Only when the class is prefixed with the bouncycastle path (e.g. org.bouncycastle.jce.interfaces.ECPublicKey) is it a BC class. openSSLProvider is an instance of OpenSSLProvider from the Conscrypt library.

That project can be found here.

https://github.com/google/conscrypt

And the pom to install is here-

<dependency>
    <groupId>org.conscrypt</groupId>
    <artifactId>conscrypt-openjdk-uber</artifactId>
    <version>2.1.0</version>
</dependency>

///

import org.conscrypt.OpenSSLProvider;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import sun.security.util.ECUtil;

Security.addProvider(new BouncyCastleProvider());
Security.addProvider(new OpenSSLProvider());

ECUtil.getECParameterSpec(new OpenSSLProvider, "prime256v1");

Edit Edit:

Complete minimum reproducible example-

Edit Edit Edit:

Example now includes loading the public key manually and not with the KeyFactory.

When loading the BC public key manually, it's encoded value matches the encoded value of the Conscrypt public key when loading through the Key Factory...

import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.provider.JCEECPublicKey;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.conscrypt.OpenSSLProvider;
import sun.security.util.ECUtil;

import java.io.IOException;
import java.security.*;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.util.*;

public class Main {

    public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, IOException {
        Security.addProvider(new BouncyCastleProvider());
        Security.addProvider(new OpenSSLProvider());

        String pubKey = "BADX_GAXp03z_5p05O1-op61KJAl4j9U2sBnAnJ4p_6GSAIyFGU3lMoC4aIXw_2qlTnplykArgjvwCWw-2g6L44";

        ECPublicKey publicKey = (ECPublicKey)loadPublicKey(pubKey, "Conscrypt");
        org.bouncycastle.jce.interfaces.ECPublicKey publicKeyBC = (org.bouncycastle.jce.interfaces.ECPublicKey)loadPublicKey(pubKey, "BC");
        org.bouncycastle.jce.interfaces.ECPublicKey publicKeyBC2 = (org.bouncycastle.jce.interfaces.ECPublicKey) loadPublicKeyManually(pubKey);

        System.out.println(Arrays.toString(publicKey.getEncoded()));
        System.out.println(Arrays.toString(publicKeyBC.getEncoded()));
        System.out.println(Arrays.toString(publicKeyBC2.getEncoded()));
    }

    public static PublicKey loadPublicKey(String encodedPublicKey, String provider) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException, IOException {
        Base64.Decoder base64Decoder = Base64.getUrlDecoder();
        byte[] decodedPublicKey = base64Decoder.decode(encodedPublicKey);
        KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);

        ECParameterSpec ecParameterSpec = ECUtil.getECParameterSpec(new OpenSSLProvider(), "prime256v1");
        ECPoint ecPoint = ECUtil.decodePoint(decodedPublicKey, ecParameterSpec.getCurve());

        ECPublicKeySpec pubSpec = new ECPublicKeySpec(ecPoint, ecParameterSpec);
        ECPublicKey ecPublicKey = (ECPublicKey)keyFactory.generatePublic(pubSpec);

        return ecPublicKey;
    }

    public static PublicKey loadPublicKeyManually(String encodedPublicKey) {
        Base64.Decoder base64Decoder = Base64.getUrlDecoder();
        byte[] decodedPublicKey = base64Decoder.decode(encodedPublicKey);

        ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec("prime256v1");

        org.bouncycastle.jce.spec.ECPublicKeySpec ecPublicKeySpec = new org.bouncycastle.jce.spec.ECPublicKeySpec(
                parameterSpec.getCurve().decodePoint(decodedPublicKey),
                parameterSpec
        );
        org.bouncycastle.jce.interfaces.ECPublicKey ecPublicKey = new JCEECPublicKey(
                "EC",
                ecPublicKeySpec
        );

        return ecPublicKey;
    }
}
President James Moveon Polk :

The two public key representations are essentially equivalent. Both are instances of a DER-encoded SubjectPublicKeyInfo structure described in RFC 5280. This structure contains, not just the public key, but also some metadata describing the algorithmic context of the public key. For one of the forms the metadata simply states that the context is the "prime256v1" curve. For the other, all the parameters of this curve are provided instead. The public key itself occurs as the last part of both forms, and as you can see they are identical.

What you have are 3 different representations in total of the same public key. The base-64 encoded string just contains a type 4 (uncompressed) elliptic curve point encoded according to SEC 1 section 2.3.3. You have found an undocumented and unsupported API in the sun.security.util.ECUtil class to turn this into a PublicKey.

I'm not completely certain what your question is about the private key. For DH schemes including ECDH, the private key is simply an integer chosen "securely" from the range of the underlying group order. The public key is then computed by multiplying (in an elliptic curve sense) this integer by the base point of the curve. The result public key is also a point on the curve.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=121240&siteId=1