2

I am new to the EC-encryption and have some struggle with it. I am using Java 8 and the BouncyCatle provider. The Question I have now is: when I generate an EC-KeyPair with the folloing code:

    ECGenParameterSpec spec = new ECGenParameterSpec("secp521r1");
    KeyPairGenerator kpg = KeyPairGenerator.getInstance("ECDH", BouncyCastleProvider.PROVIDER_NAME);
    kpg.initialize(spec, new SecureRandom());
    return kpg.generateKeyPair();

and try to get the byte array of the public key to send it to another person, the encoded key is 158 bytes long and in the X.509 format. But I expected the X9.62 format and a keysize between 65 and 66 bytes. Why is the public key this large and how can I encode it with the expected keysize? (I expected the keysize because I expect the key to be 521 bits long)

President James K. Polk
  • 40,516
  • 21
  • 95
  • 125
Pilikio
  • 302
  • 4
  • 13

2 Answers2

5

An ECC publickey is semantically a point on a curve; if the curve you name is implied, a point in X9.62 format is either 67 octets (Java bytes) if compressed or 133 octets if uncompressed, never any other length.

If you mean java.security.PublicKey.getEncoded() that is always in what Java calls "X.509" encoding which is actually the ASN.1 structure SubjectPublicKeyInfo (SPKI) defined in X.509 and more conveniently available in rfc5280 sec 4.1, encoded as DER. An ECC publickey on that curve in this format is is 90 or 158 octets, exactly, for uncompressed or compressed, and the Java providers (at least currently) produce the uncompressed form (although they can parse compressed).

It sounds like you may want the X9.62 compressed format, which as I said is 67 bytes (not 65 or 66). If so, you can't control point compression in the standard Java API, but the BouncyCastle implementation classes do support it, given you have key objects created by the BC provider. First cast keypair.getPublicKey() to (corr) org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey (before 1.47 was org.bouncycastle.jce.provider.JCEECPublicKey) and then getQ() returns an org.bouncycastle.math.ec.ECPoint which has an (overloaded) getEncoded(boolean compressed) which produces what you apparently want.


For your additional but not (yet?) official question, to re-create a PublicKey object from an encoded point (compressed or not), you have two or three options depending how you count:

  • construct an ASN.1/DER-encoded SubjectPublicKeyInfo structure (which Java calls "X.509" format) for this curve and point, put it in X509EncodedKeySpec, and run that through an appropriate KeyFactory. Either the standard SunEC provider (assuming j7+, and not a RedHat-crippled version) or the BC provider can be used. Constructing ASN.1 encodings like SPKI by hand is difficult in general but not bad in this specific case; or given you have BC you can use its ASN.1 functionality

  • call the BC routines directly to do what the EC KeyFactory would do for the above input

Example code for creating a point and then using it all three ways:

// as needed in addition to standard java.security and javax.xml 
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.sec.SECObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x9.X962Parameters;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jcajce.provider.config.ProviderConfiguration;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;

    KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
    kpg.initialize(new ECGenParameterSpec("secp521r1"));
    org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey ku = 
            (org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey)kpg.generateKeyPair().getPublic();
    byte[] encodedpoint = ku.getQ().getEncoded(true/*compressed*/);
    
    { // construct SPKI by hand, this curve only
        byte[] hdr = DatatypeConverter.parseHexBinary("3058301006072a8648ce3d020106052b81040023034400");
        // could also write out byte[] hdr = {0x30,0x58,0x30,0x10... but items with 0x80 set need casts
        if( 0x44 /*hdr[0x15]*/ -1 != encodedpoint.length ) throw new Exception ("BAD COMPRESSED POINT FOR secp521r1!");
        byte[] spki = Arrays.copyOf(hdr,90); System.arraycopy(encodedpoint,0, spki,0x17, 0x43);
        PublicKey k2 = KeyFactory.getInstance("EC" /*,provider?*/).generatePublic(new X509EncodedKeySpec(spki));
        Signature.getInstance("ECDSA").initVerify(k2); // sanity check
    }
    { // construct SPKI with BC
        AlgorithmIdentifier algid = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey,SECObjectIdentifiers.secp521r1);
        ASN1EncodableVector vec = new ASN1EncodableVector();
        vec.add(algid); vec.add(new DERBitString(encodedpoint));
        byte[] spki = new DERSequence(vec).getEncoded();
        PublicKey k2 = KeyFactory.getInstance("EC" /*,provider*/).generatePublic(new X509EncodedKeySpec(spki));
        Signature.getInstance("ECDSA").initVerify(k2); // sanity check
    }
    { // call BC directly
        ProviderConfiguration configuration = BouncyCastleProvider.CONFIGURATION;
        X962Parameters params = X962Parameters.getInstance(org.bouncycastle.asn1.sec.SECObjectIdentifiers.secp521r1);
        ECCurve curve = EC5Util.getCurve(configuration, params);
        /*ECParameterSpec ecSpec = EC5Util.convertToSpec(params, curve);*/
        ECPoint point = curve.decodePoint(encodedpoint).normalize();
        ECPublicKeyParameters kparams = new ECPublicKeyParameters(point, ECUtil.getDomainParameters(configuration, params));
        PublicKey k2 = new BCECPublicKey ("EC"/* or "ECDH" etc*/, kparams, configuration);
        Signature.getInstance("ECDSA").initVerify(k2); // sanity check
    }

Related Loading raw 64-byte long ECDSA public key in Java which is for P256 uncompressed.

Community
  • 1
  • 1
dave_thompson_085
  • 34,712
  • 6
  • 50
  • 70
  • Thank you very much! I just had to change it to an BCECPublicKey because I couldn't cast it. But now I have a new Question, how do I get from the Q byte[] to the PublicKey? – Pilikio Jul 15 '18 at 16:37
  • @Pilikio: wow! when I retested I found the same, and eventually tracked down that I had initially tested on a JVM that I forgot I had set up some time ago with an older version of Bouncy to check something -- and Bouncy has changed this between versions; edited. Re your new question, you should add it to your question because Stack policy is that the question should be in the question and the answer should be in the answer, and comments are not for discussion and can be deleted at any time. But see edit anyway :) – dave_thompson_085 Jul 17 '18 at 10:50
  • doesn't matter anymore, I found out a solution after one and a half day of frustration that the class names of BouncyCastle and Java are the same but incompatible to each other... – Pilikio Jul 18 '18 at 13:00
  • Is the point in X9.62 format 67 octets in size with "compression" bit or without? Because I have a key that is compressed but it has 68 octets length and I get the error while trying to create ECPoint on given curve from it. – Michał Krzywański May 31 '19 at 15:18
  • 1
    @michalk: _for P-521 aka secp521r1 only_ compressed is 1+66=67 and uncompressed is 1+2x66=133. (And hybrid, which no one uses, it the same as uncompressed.) You can tell which it is from the first octet: 02 or 03 is compressed, 04 is uncompressed. – dave_thompson_085 Jun 01 '19 at 05:55
0

The code (modified from BouncyCastle) below can work with any public key (not only secp521r1)


import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x9.X962Parameters;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.x9.X9ECPoint;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.math.ec.ECCurve;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.Security;
import java.security.spec.ECParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class TestCompressionEncoded {

    static X962Parameters getDomainParametersFromName(ECParameterSpec ecSpec, boolean compress) {
        X962Parameters x962Param;
        if (ecSpec instanceof ECNamedCurveSpec) {
            ASN1ObjectIdentifier var3 = ECUtil.getNamedCurveOid(((ECNamedCurveSpec)ecSpec).getName());
            if (var3 == null) {
                var3 = new ASN1ObjectIdentifier(((ECNamedCurveSpec)ecSpec).getName());
            }

            x962Param = new X962Parameters(var3);
        } else if (ecSpec == null) {
            x962Param = new X962Parameters(DERNull.INSTANCE);
        } else {
            ECCurve var5 = EC5Util.convertCurve(ecSpec.getCurve());
            X9ECParameters var4 = new X9ECParameters(var5, new X9ECPoint(EC5Util.convertPoint(var5, ecSpec.getGenerator()), compress), ecSpec.getOrder(), BigInteger.valueOf((long)ecSpec.getCofactor()), ecSpec.getCurve().getSeed());
            x962Param = new X962Parameters(var4);
        }
        return x962Param;
    }

    static byte[] encodeKeyWithCompression(BCECPublicKey x) throws Exception {
        AlgorithmIdentifier var1 = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, getDomainParametersFromName(x.getParams(), true));
        byte[] var2 = x.getQ().getEncoded(true);
        return KeyUtil.getEncodedSubjectPublicKeyInfo(var1, var2);
    }

    public static void main(String...args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());
        String publicKey = "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAELPqrW2JAXKTbjfh9M3X3b85Uje7T0r2gu7qKPmmyagGFnfckwVFpKg10+S2ttJYVUB4q+kPpnJg/YHV5xMnSLA==";
        KeyFactory fact = KeyFactory.getInstance("ECDSA", "BC");
        BCECPublicKey bcePubKey = (BCECPublicKey) fact.generatePublic(new X509EncodedKeySpec( Base64.getDecoder().decode(publicKey)));

        System.out.println("Uncompressed encoded value: " + publicKey);
        System.out.println("Compressed encoded value: " + Base64.getEncoder().encodeToString(encodeKeyWithCompression(bcePubKey)));
    }
}

The output (for prime256v1)

Uncompressed encoded value: MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAELPqrW2JAXKTbjfh9M3X3b85Uje7T0r2gu7qKPmmyagGFnfckwVFpKg10+S2ttJYVUB4q+kPpnJg/YHV5xMnSLA==
Compressed encoded value: MDYwEAYHKoZIzj0CAQYFK4EEAAoDIgACLPqrW2JAXKTbjfh9M3X3b85Uje7T0r2gu7qKPmmyagE=

hyo1411
  • 81
  • 2
  • getting encoded key spec not recognized: failed to construct sequence from byte[]: Extra data detected in stream passing public key: 043d076b9e470cb23148f12eebd5c5d00c3cb1a2a19c688f0f10176a96fddef2e833449c88b9f87d7a15d1afc42eda3bd3dc91567cb195ea401e4e86d115aee3f1 – Umar Ata Dec 16 '21 at 08:50