How to find the matching curve name from an ECPublicKey
Asked Answered
M

3

3

Currently i'm updating my x.509 certificate library to support ECC. Most of the builders that are implemented take a publicKey and derive the algorithm and such from the key. In RSA this is simple, you check the algorithm of the key and you can verify the bit length. However with ECC the key is based on a curve, and the curve name (of course) needs to be specified in the certificate (as OID).

The issue i'm working on right now is finding a way to come from either a java.security.interfaces.ECPublicKey or a org.bouncycastle.jce.interfaces.ECPublicKey to a curve name. (Both implementations are completely different from each other...)

One way i can think of is getting the key's ECPoint and validate that it is on a given curve. This way i can test all supported curves, this however feels cumbersome at runtime and possibly error prone if there are points overlapping 2 or more curves.

Another way is to get the ECCurve (bc implementation) or the EllipticCurve (jre implentation) and compare the curve details with the supported implementations. This also involves stepping through every known curve.

Does anybody know a better way of finding the curve name based on curve or publicKey details using jre(8/9) and bc only. And what is your feeling about the first solution, how likely would it be to get false hits.

Mackenziemackerel answered 18/4, 2018 at 9:13 Comment(6)
Do you have a particular standard naming convention you are looking for, like 'secpxyz'? After all, a name is just a convenient label for a small set of parameters.Mosher
In the end i need to convert to the specific OID representing the curve for the x.509 certificate. I have a table of those (the ones i can support) based upon their name which is indeed in the format 'secpxyz' and 'brainpoolxyz' and so on. These names seem to be pretty standardized and are also present in let's say ECNamedCurveTable and other classes in jre an bc.Mackenziemackerel
The first method should be adequate, and it would be almost impossible to have a random point on one curve also turn out to be a random point on another curve, at least for curves used in cryptography. It would probably be more efficient to look up the curve order in a map from BigIntegers to curve parameters.Mosher
Bouncy castle has a few classes that contain the encoded curves. You can just retrieve the parameters from the public keys and compare by looping over them. Maybe you can post the encoded public key? If you just need to do this once: print out the curve parameters, and use google to search for one of the parameters such as the order or (even better) prime.Halloran
@MaartenBodewes thanks this was indeed what i eventually ended up doing. The conversion between all the double classes from jce and bc caused a bit of a headache though since both implementations use different parameter names and types. The EC5Util class from BC provides a solution. I've put the solution in the answer below. Now i'll have to run a full UnitTest to see if all curves and names come up well.Mackenziemackerel
Are there any solutions that don't use Bouncy Castle? It would be great to have an example with no additional dependencies.Chadwell
M
1

I think i've found a valid solution using the EC5Util class for the jre type specifications. All of the double class instances with the same name make it a bit messy, however the functions are now accessible and useable.

public static final String deriveCurveName(org.bouncycastle.jce.spec.ECParameterSpec ecParameterSpec) throws GeneralSecurityException{
    for (@SuppressWarnings("rawtypes")
           Enumeration names = ECNamedCurveTable.getNames(); names.hasMoreElements();){
        final String name = (String)names.nextElement();

        final X9ECParameters params = ECNamedCurveTable.getByName(name);

        if (params.getN().equals(ecParameterSpec.getN())
            && params.getH().equals(ecParameterSpec.getH())
            && params.getCurve().equals(ecParameterSpec.getCurve())
            && params.getG().equals(ecParameterSpec.getG())){
            return name;
        }
    }

    throw new GeneralSecurityException("Could not find name for curve");
}

public static final String deriveCurveName(PublicKey publicKey) throws GeneralSecurityException{
    if(publicKey instanceof java.security.interfaces.ECPublicKey){
        final java.security.interfaces.ECPublicKey pk = (java.security.interfaces.ECPublicKey) publicKey;
        final ECParameterSpec params = pk.getParams();
        return deriveCurveName(EC5Util.convertSpec(params, false));
    } else if(publicKey instanceof org.bouncycastle.jce.interfaces.ECPublicKey){
        final org.bouncycastle.jce.interfaces.ECPublicKey pk = (org.bouncycastle.jce.interfaces.ECPublicKey) publicKey;
        return deriveCurveName(pk.getParameters());
    } else throw new IllegalArgumentException("Can only be used with instances of ECPublicKey (either jce or bc implementation)");
}

public static final String deriveCurveName(PrivateKey privateKey) throws GeneralSecurityException{
    if(privateKey instanceof java.security.interfaces.ECPrivateKey){
        final java.security.interfaces.ECPrivateKey pk = (java.security.interfaces.ECPrivateKey) privateKey;
        final ECParameterSpec params = pk.getParams();
        return deriveCurveName(EC5Util.convertSpec(params, false));
    } else if(privateKey instanceof org.bouncycastle.jce.interfaces.ECPrivateKey){
        final org.bouncycastle.jce.interfaces.ECPrivateKey pk = (org.bouncycastle.jce.interfaces.ECPrivateKey) privateKey;
        return deriveCurveName(pk.getParameters());
    } else throw new IllegalArgumentException("Can only be used with instances of ECPrivateKey (either jce or bc implementation)");
}
Mackenziemackerel answered 20/4, 2018 at 9:3 Comment(1)
After some checks i ended up adding the CustomNamedCurves class and loop through those as well (this can be done in exactly the same way as looping through the ECNamedCurveTable, both are BC standard).Mackenziemackerel
R
6

From your description it appears what you really need is the OID, not the name. If so, that's easier, since the curve OID is present in the "X.509" encoding of an EC public key, which is actually the SubjectPublicKeyInfo structure from X.509 (replicated in PKIX, see rfc5280 #4.1 and rfc3279 #2.3.5 but skip the parts about explicit parameters, everybody uses the namedCurve=OID option) which is the encoding for JCA public keys, for both Sun/Oracle/OpenJDK and BC implementations (and all algorithms not just ECC). BC additionally provides good support to parse this structure:

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;

    KeyPairGenerator gen = KeyPairGenerator.getInstance("EC");
    gen.initialize(new ECGenParameterSpec("secp256r1"));
    ECPublicKey jcekey = (ECPublicKey) gen.generateKeyPair().getPublic();
    //KeyFactory fact = KeyFactory.getInstance("EC", "BC");
    //org.bouncycastle.jce.interfaces.ECPublicKey bckey = (org.bouncycastle.jce.interfaces.ECPublicKey)fact.generatePublic(new X509EncodedKeySpec(jcekey.getEncoded()));
    
    // with Bouncy
    byte[] enc = jcekey.getEncoded(); //enc = bckey.getEncoded();
    SubjectPublicKeyInfo spki = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(enc));
    AlgorithmIdentifier algid = spki.getAlgorithm();
    if( algid.getAlgorithm().equals(X9ObjectIdentifiers.id_ecPublicKey)){
        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) algid.getParameters();
        System.out.println (oid.toString()); // curve OID, use as needed
    }else System.out.println ("not EC?");

and for completeness even without Bouncy it's not hard if you don't use the largest curves and you're willing to cheat (which Java increasingly discourages):

import sun.security.util.DerInputStream;
import sun.security.util.ObjectIdentifier;

    final ObjectIdentifier x9_id_ec = new ObjectIdentifier("1.2.840.10045.2.1");
    int off = (4+2)+enc[(4+1)];
    if( enc[0]==0x30 && enc[1]>0 && enc[2]==0x30 && enc[4]==6 
        && new ObjectIdentifier(new DerInputStream(enc,4,off-4)).equals((Object)x9_id_ec)
        && enc[off] == 6 ){
        byte[] oidenc = Arrays.copyOfRange(enc,off,off+2+enc[off+1]);
        // that's the DER-encoded OID of the curve
        ObjectIdentifier oid = new ObjectIdentifier(new DerInputStream(oidenc));
        System.out.println (oid.toString()); // and the display form
    }else System.out.println ("not EC or too big?");

I'd also note if you're building a certificate, PublicKey.getEncoded() is already the entire subjectPublicKeyInfo field, which is the only place you need to put the curve OID and except for self-signed the only place you put this key's algorithm OID.

Riddick answered 21/4, 2018 at 2:6 Comment(4)
Hi Dave, since i've written a DER/ASN.1/X.509 library where this question is part of. The first thing i did was to check the content of the "encoded" bytes of the key. However every one i tested was using the explicit scheme, and did not contain the curve name OID. I tested with secp256k1 and curve25519. Also if you input the java x.509 encoded public key into the certificate, it does not validate in windows. Besides that i also use the name for serialization and deserialization of the keys, but since i did not tell you couldn't know ;-)Mackenziemackerel
i've ran your code sample as well and when using the way you generate the ECPublicKey (using the JCE format), the OID is contained in the encoded format. Since you are using the X509EncodedKeySpec from the JCE key, for the BC implementation it still contains the OID. However keys generated with BC or by their curve details will not contain Curve OID's so the code above will not work on those keys.Mackenziemackerel
Keys generated by the BC provider using (JCE) ECGenParameterSpec or (BC) ECNamedCurveGenParameterSpec (or the int overload) do encode with OID. Yes, if you manually/explicitly provide all the parameters EXCEPT the name (why?) then you don't have OID.Riddick
it all depends where you get your keys from. If an external party provides you with their publickey, you are not in control of what was generated. So a solution that assumes that the OID is in the publickey is not goïng to help. None of the key samples i had included the OID.Mackenziemackerel
M
1

I think i've found a valid solution using the EC5Util class for the jre type specifications. All of the double class instances with the same name make it a bit messy, however the functions are now accessible and useable.

public static final String deriveCurveName(org.bouncycastle.jce.spec.ECParameterSpec ecParameterSpec) throws GeneralSecurityException{
    for (@SuppressWarnings("rawtypes")
           Enumeration names = ECNamedCurveTable.getNames(); names.hasMoreElements();){
        final String name = (String)names.nextElement();

        final X9ECParameters params = ECNamedCurveTable.getByName(name);

        if (params.getN().equals(ecParameterSpec.getN())
            && params.getH().equals(ecParameterSpec.getH())
            && params.getCurve().equals(ecParameterSpec.getCurve())
            && params.getG().equals(ecParameterSpec.getG())){
            return name;
        }
    }

    throw new GeneralSecurityException("Could not find name for curve");
}

public static final String deriveCurveName(PublicKey publicKey) throws GeneralSecurityException{
    if(publicKey instanceof java.security.interfaces.ECPublicKey){
        final java.security.interfaces.ECPublicKey pk = (java.security.interfaces.ECPublicKey) publicKey;
        final ECParameterSpec params = pk.getParams();
        return deriveCurveName(EC5Util.convertSpec(params, false));
    } else if(publicKey instanceof org.bouncycastle.jce.interfaces.ECPublicKey){
        final org.bouncycastle.jce.interfaces.ECPublicKey pk = (org.bouncycastle.jce.interfaces.ECPublicKey) publicKey;
        return deriveCurveName(pk.getParameters());
    } else throw new IllegalArgumentException("Can only be used with instances of ECPublicKey (either jce or bc implementation)");
}

public static final String deriveCurveName(PrivateKey privateKey) throws GeneralSecurityException{
    if(privateKey instanceof java.security.interfaces.ECPrivateKey){
        final java.security.interfaces.ECPrivateKey pk = (java.security.interfaces.ECPrivateKey) privateKey;
        final ECParameterSpec params = pk.getParams();
        return deriveCurveName(EC5Util.convertSpec(params, false));
    } else if(privateKey instanceof org.bouncycastle.jce.interfaces.ECPrivateKey){
        final org.bouncycastle.jce.interfaces.ECPrivateKey pk = (org.bouncycastle.jce.interfaces.ECPrivateKey) privateKey;
        return deriveCurveName(pk.getParameters());
    } else throw new IllegalArgumentException("Can only be used with instances of ECPrivateKey (either jce or bc implementation)");
}
Mackenziemackerel answered 20/4, 2018 at 9:3 Comment(1)
After some checks i ended up adding the CustomNamedCurves class and loop through those as well (this can be done in exactly the same way as looping through the ECNamedCurveTable, both are BC standard).Mackenziemackerel
A
1

Actually (at least for keys generated by SunEC provider) OID can be extracted much easier than other answers suggest:

import java.security.Key;
import java.security.AlgorithmParameters;
import java.security.interfaces.ECKey;
import java.security.spec.ECGenParameterSpec;

public static String curveOid(Key key) throws Exception {
    AlgorithmParameters params = AlgorithmParameters.getInstance("EC");
    params.init(((ECKey) key).getParams());
    return params.getParameterSpec(ECGenParameterSpec.class).getName();
}
Archerfish answered 24/12, 2021 at 14:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.