Search code examples
javacryptographybouncycastle

How to make a Bouncy Castle ECPublicKey


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://stackoverflow.com/a/29355749/5453873 but the code there uses java.security... instead of org.bouncycastle... and ECPublicKey is an interface in org.bouncycastle... not an instantiable class.


Solution

  • 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.