How to make a Bouncy Castle ECPublicKey
Asked Answered
R

4

8

I know the curve name (secp256k1) and the X and Y coordinates of the EC public key.

How do I make a org.bouncycastle.jce.interfaces.ECPublicKey out of them?

I've read https://mcmap.net/q/1323187/-how-does-one-convert-a-public-ec-code-point-and-curve-name-into-a-publickey but the code there uses java.security... instead of org.bouncycastle... and ECPublicKey is an interface in org.bouncycastle... not an instantiable class.

Reflux answered 19/10, 2015 at 15:44 Comment(3)
Do you really need to use the org.bouncycastle.* classes? I would expect you will be better off trying to use the JCE API with BC just as a provider. Alternatively, if you don't mind the direct dependency on BC then things might be simpler if you just use the lightweight (i.e. non-JCE) API directly.Bulbul
@PeterDettman there's a non-JCE API for bouncycastle? I'm using BC's ECIES - does this mean I should stick to the JCE API?Reflux
Please don't forget to indicate the language / runtime (Java) as well as additional tags such as cryptography. Not that many people follow bouncycastle.Kr
K
11

Generating ECPublicKey using Bouncy Castle

This generates the EC public key as used in the JCE/JCA. The Bouncy Castle provider can directly use these software keys. Otherwise Bouncy is just used to generate the parameters required to generate the public key.

package nl.owlstead.stackoverflow;

import static java.nio.charset.StandardCharsets.US_ASCII;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.Security;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;

import javax.crypto.Cipher;

import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.util.encoders.Hex;

public class ECPublicKeyFactory {

    public static void main(String[] args) throws Exception {

        String name = "secp256r1";

        Security.addProvider(new BouncyCastleProvider());

        // === NOT PART OF THE CODE, JUST GETTING TEST VECTOR ===
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
        ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec(name);
        kpg.initialize(ecGenParameterSpec);
        ECPublicKey key = (ECPublicKey) kpg.generateKeyPair().getPublic();
        byte[] x = key.getW().getAffineX().toByteArray();
        byte[] y = key.getW().getAffineY().toByteArray();

        // === here the magic happens ===
        KeyFactory eckf = KeyFactory.getInstance("EC");
        ECPoint point = new ECPoint(new BigInteger(1, x), new BigInteger(1, y));
        ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec(name);
        ECParameterSpec spec = new ECNamedCurveSpec(name, parameterSpec.getCurve(), parameterSpec.getG(), parameterSpec.getN(), parameterSpec.getH(), parameterSpec.getSeed());
        ECPublicKey ecPublicKey = (ECPublicKey) eckf.generatePublic(new ECPublicKeySpec(point, spec));
        System.out.println(ecPublicKey.getClass().getName());

        // === test 123 ===
        Cipher ecies = Cipher.getInstance("ECIESwithAES", "BC");
        ecies.init(Cipher.ENCRYPT_MODE, ecPublicKey);
        byte[] ct = ecies.doFinal("owlstead".getBytes(US_ASCII));
        System.out.println(Hex.toHexString(ct));
    }
}

Generating Bouncy Castle ECPublicKeyParameters

Initially I thought that a Bouncy Castle specific key was required, so the following code generates the EC public key as used in the Bouncy Castle lightweight API.

package nl.owlstead.stackoverflow;

import java.math.BigInteger;
import java.security.KeyPairGenerator;
import java.security.Security;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.params.ECNamedDomainParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;

public class BC_EC_KeyCreator {

    public static void main(String[] args) throws Exception {

        String name = "secp256r1";

        // === NOT PART OF THE CODE, JUST GETTING TEST VECTOR ===
        Security.addProvider(new BouncyCastleProvider());
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
        kpg.initialize(new ECGenParameterSpec(name));
        ECPublicKey key = (ECPublicKey) kpg.generateKeyPair().getPublic();
        byte[] x = key.getW().getAffineX().toByteArray();
        byte[] y = key.getW().getAffineY().toByteArray();

        // assumes that x and y are (unsigned) big endian encoded
        BigInteger xbi = new BigInteger(1, x);
        BigInteger ybi = new BigInteger(1, y);
        X9ECParameters x9 = ECNamedCurveTable.getByName(name);
        ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID(name);
        ECCurve curve = x9.getCurve();
        ECPoint point = curve.createPoint(xbi, ybi);
        ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid,
                x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed());
        ECPublicKeyParameters pubKey = new ECPublicKeyParameters(point, dParams);
        System.out.println(pubKey);

        // some additional encoding tricks
        byte[] compressed = point.getEncoded(true);
        System.out.println(Hex.toHexString(compressed));
        byte[] uncompressed = point.getEncoded(false);
        System.out.println(Hex.toHexString(uncompressed));
    }
}

This was mostly tricky because I didn't want to include any JCE specific code, and X9ECParameters is not a subclass of ECDomainParameters. So I used a conversion to ECNamedDomainParameters copied from elsewhere in the code base of Bouncy Castle.

Kr answered 22/10, 2015 at 21:35 Comment(3)
Thanks for your work. With bcpkix-jdk15on-152.jar and bcprov-jdk15on-152.jar org.bouncycastle.jce.ECNameCurveTable does not have the methods getByName nor getOID, so the code won't compile.Reflux
Also an org.bouncycastle.crypto.params.ECPublicKeyParameters isn't an org.bouncycastle.jce.interfaces.ECPublicKey, which is what I think I need to call init on the "ECIESwithAES" javax.crypto.Cipher.Reflux
Thanks for your work - I don't yet have enough rep to upvote your answer, but I will when I do. I've found a simpler method that achieves what I need.Reflux
R
4

In the code which follows, encoded contains 0x04 followed by 32 bytes of X, then 32 bytes of Y.

Alternatively, it can contain 0x02 or 0x03 (dependent on the sign of Y) followed by 32 bytes of X.

public static ECPublicKey decodeKey(byte[] encoded) throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException{
    ECNamedCurveParameterSpec params = ECNamedCurveTable.getParameterSpec("secp256k1");
    KeyFactory fact = KeyFactory.getInstance("ECDSA", "BC");
    ECCurve curve = params.getCurve();
    java.security.spec.EllipticCurve ellipticCurve = EC5Util.convertCurve(curve, params.getSeed());
    java.security.spec.ECPoint point = ECPointUtil.decodePoint(ellipticCurve, encoded);
    java.security.spec.ECParameterSpec params2 =EC5Util.convertSpec(ellipticCurve, params);
    java.security.spec.ECPublicKeySpec keySpec = new java.security.spec.ECPublicKeySpec(point,params2);
    return (ECPublicKey) fact.generatePublic(keySpec);
}
Reflux answered 26/10, 2015 at 13:58 Comment(1)
@MaartenBodewes I've accepted your answer, as it's more generally useful.Reflux
G
4

A shorter variation on Thomas' answer, which does not use a KeyFactory would be:

public static ECPublicKey decodeKey(byte[] encoded) {
  ECNamedCurveParameterSpec params = ECNamedCurveTable.getParameterSpec("secp256k1");
  ECPublicKeySpec keySpec = new ECPublicKeySpec(params.getCurve().decodePoint(encoded), params);
  return new BCECPublicKey("ECDSA", keySpec, BouncyCastleProvider.CONFIGURATION);
}

I assume that encoded was obtained by BCECPublicKey.getQ().getEncoded(bool), i.e. it is a byte 0x2-0x4 followed by one or two affine coordinates of the public point on secp256k1.

Godard answered 22/1, 2021 at 18:40 Comment(0)
L
1

ECUtil Already provided

ECPublicKeyParameters bcecPublicKey =(ECPublicKeyParameters) ECUtil.generatePublicKeyParameter(kp.getPublic());
ECPrivateKeyParameters bcecPrivateKey = (ECPrivateKeyParameters) ECUtil.generatePrivateKeyParameter(kp.getPrivate());
Lutero answered 25/6, 2018 at 6:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.