13

I have a raw (r,s) format ECDSA NIST P-256 public key. It seems that there is no simple way to load it into an object that implements java.security.interfaces.ECPublicKey.

What is the cleanest way to load a 64 byte public key so that it can be used to check signatures?

user1094206
  • 840
  • 1
  • 11
  • 22
  • What's the format of your EC Public Key? Compressed or uncompressed? – rakeb.mazharul May 26 '15 at 06:02
  • 1
    64 bytes? Obviously not compressed. It would be 33 bytes if it was compressed. – Maarten Bodewes May 27 '15 at 02:07
  • Correct. It is uncompressed. – user1094206 May 29 '15 at 17:06
  • An ECDSA **public key** is a point (on the relevant curve) and an uncompressed point's **components are x and y** which are elements of the underlying field i.e. Fp; r and s are the components of a _signature_ and are elements of Zn where n is not p, although the NIST Fp curves have cofactor 1 so n is the order of the curve which is fairly _near_ p per Hasse. – dave_thompson_085 Jul 17 '18 at 10:40

5 Answers5

14

Java 7 is required for the EC functionality and Java 8 for the Base 64 encoder / decoder, no additional libraries - just plain Java. Note that this will actually display the public key as a named curve when printed out, something most other solutions won't do. If you have an up-to-date runtime, this other answer is more clean.

This answer is going to be tough if we do this using ECPublicKeySpec. So lets cheat a bit and use X509EncodedKeySpec instead:

private static byte[] P256_HEAD = Base64.getDecoder().decode("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE");

/**
 * Converts an uncompressed secp256r1 / P-256 public point to the EC public key it is representing.
 * @param w a 64 byte uncompressed EC point consisting of just a 256-bit X and Y
 * @return an <code>ECPublicKey</code> that the point represents 
 */
public static ECPublicKey generateP256PublicKeyFromFlatW(byte[] w) throws InvalidKeySpecException {
    byte[] encodedKey = new byte[P256_HEAD.length + w.length];
    System.arraycopy(P256_HEAD, 0, encodedKey, 0, P256_HEAD.length);
    System.arraycopy(w, 0, encodedKey, P256_HEAD.length, w.length);
    KeyFactory eckf;
    try {
        eckf = KeyFactory.getInstance("EC");
    } catch (NoSuchAlgorithmException e) {
        throw new IllegalStateException("EC key factory not present in runtime");
    }
    X509EncodedKeySpec ecpks = new X509EncodedKeySpec(encodedKey);
    return (ECPublicKey) eckf.generatePublic(ecpks);
}

Usage:

ECPublicKey key = generateP256PublicKeyFromFlatW(w);
System.out.println(key);

The idea behind this is to create a temporary X509 encoded key, which happily ends with the public point w at the end. The bytes before that contain the ASN.1 DER encoding of the OID of the named curve and structural overhead, ending with byte 04 indicating an uncompressed point. Here is an example what the structure looks like, using value 1 and 2 for the 32-byte X and Y.

The 32-byte X and Y values of the uncompressed point values removed to create the header. This only works because the point is statically sized - it's location at the end is only determined by the size of the curve.

Now all that is required in the function generateP256PublicKeyFromFlatW is to add the received public point w to the header and run it through the decoder implemented for X509EncodedKeySpec.


The above code uses a raw, uncompressed public EC point - just a 32 byte X and Y - without the uncompressed point indicator with value 04. Of course it is easy to support 65 byte compressed points as well:

/**
 * Converts an uncompressed secp256r1 / P-256 public point to the EC public key it is representing.
 * @param w a 64 byte uncompressed EC point starting with <code>04</code>
 * @return an <code>ECPublicKey</code> that the point represents 
 */
public static ECPublicKey generateP256PublicKeyFromUncompressedW(byte[] w) throws InvalidKeySpecException {
    if (w[0] != 0x04) {
        throw new InvalidKeySpecException("w is not an uncompressed key");
    }
    return generateP256PublicKeyFromFlatW(Arrays.copyOfRange(w, 1, w.length));
}

Finally, I generated the constant P256_HEAD head value in base 64 using:

private static byte[] createHeadForNamedCurve(String name, int size)
        throws NoSuchAlgorithmException,
        InvalidAlgorithmParameterException, IOException {
    KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
    ECGenParameterSpec m = new ECGenParameterSpec(name);
    kpg.initialize(m);
    KeyPair kp = kpg.generateKeyPair();
    byte[] encoded = kp.getPublic().getEncoded();
    return Arrays.copyOf(encoded, encoded.length - 2 * (size / Byte.SIZE));
}

called by:

String name = "NIST P-256";
int size = 256;
byte[] head = createHeadForNamedCurve(name, size);
System.out.println(Base64.getEncoder().encodeToString(head));
Maarten Bodewes
  • 80,169
  • 13
  • 121
  • 225
6

What is the cleanest way to load a 64 byte public key so that it can be used to check signatures?

The cleanest I could muster ! Should work with other curves too..

NOTE: Limited to the SunJCE provider or Android API 26+ (there may be more providers with this functionality, I am unaware of them at the moment.

public static ECPublicKey rawToEncodedECPublicKey(String curveName, byte[] rawBytes) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidParameterSpecException {
    KeyFactory kf = KeyFactory.getInstance("EC");
    byte[] x = Arrays.copyOfRange(rawBytes, 0, rawBytes.length/2);
    byte[] y = Arrays.copyOfRange(rawBytes, rawBytes.length/2, rawBytes.length);
    ECPoint w = new ECPoint(new BigInteger(1,x), new BigInteger(1,y));
    return (ECPublicKey) kf.generatePublic(new ECPublicKeySpec(w, ecParameterSpecForCurve(curveName)));
}

public static ECParameterSpec ecParameterSpecForCurve(String curveName) throws NoSuchAlgorithmException, InvalidParameterSpecException {
    AlgorithmParameters params = AlgorithmParameters.getInstance("EC");
    params.init(new ECGenParameterSpec(curveName));
    return params.getParameterSpec(ECParameterSpec.class);
}
datKiDfromNY
  • 191
  • 1
  • 2
  • 1
    Interesting trick for generating the parameters. I presume this also allows you to keep using the named parameters rather than the explicit ones? Definitely less hacked than my answer anyway. Any prerequisites for this answer? – Maarten Bodewes Jul 01 '19 at 11:16
  • It's less hacked when it's available... parametrizing your answer to support other curves is a nice middle ground. – datKiDfromNY Aug 02 '19 at 13:47
5

Java does make cryptography very long winded.

The procedure to create a public key from a given EC point:

  1. Construct an ECPoint object from your given co-ordinates.
  2. Construct an ECParameterSpec object from information of your curve.
  3. Construct an ECPublicKeySpec object from your ECPoint and your ECParameterSpec object.
  4. Invoke KeyFactory.generatePublic() with your ECPublicKeySpec object to retrieve a PublicKey object.
  5. Cast the PublicKey into an ECPublicKey as necessary.

Example below:

// Setup for P-256 curve params

BigInteger p256_p = new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16);

BigInteger p256_a = new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16);
BigInteger p256_b = new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16);
byte[] p256_seed = {
                        (byte) 0xc4, (byte) 0x9d, (byte) 0x36, (byte) 0x08, 
                        (byte) 0x86, (byte) 0xe7, (byte) 0x04, (byte) 0x93, 
                        (byte) 0x6a, (byte) 0x66, (byte) 0x78, (byte) 0xe1, 
                        (byte) 0x13, (byte) 0x9d, (byte) 0x26, (byte) 0xb7, 
                        (byte) 0x81, (byte) 0x9f, (byte) 0x7e, (byte) 0x90
                    };

BigInteger p256_xg = new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16);
BigInteger p256_yg = new BigInteger("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16);

BigInteger p256_n = new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16);

// Construct prime field
ECFieldFp p256_field = new ECFieldFp(p256_p);

// Construct curve from parameters
EllipticCurve p256 = new EllipticCurve(p256_field, p256_a, p256_b, p256_seed);

// Construct base point for curve
ECPoint p256_base = new ECPoint(p256_xg, p256_yg);

// Construct curve parameter specifications object
ECParameterSpec p256spec = new ECParameterSpec(p256, p256_base, p256_n, 1); // Co-factor 1 for prime curves

// ------------------------------------------------------------- //

// Construct EC point from "raw" public key
ECPoint point = new ECPoint(r, s); // r, s is of type BigInteger

// Create a EC public key specification object from point and curve
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, p256spec);

// Retrieve EC KeyFactory
KeyFactory ECFactory = KeyFactory.getInstance("EC");

// Generate public key via KeyFactory
PublicKey pubKey = ECFactory.generatePublic(pubKeySpec);
ECPublicKey ECPubKey = (ECPublicKey) pubKey;

It may be helpful to generate the ECParameterSpec once (perhaps in a static initializer block) for performance reasons.

Note: There is probably a much simpler way to generate the ECParameterSpec object (via named curves for example) but so far I've only found that ECGenParameterSpec has this feature. Let me know in comments if there is a less painful approach.


To save yourself the pain of doing the above, encode your EC key under X.509, which will fully describe the key and make loading it much much easier.

In java, with the ECPublicKey, all you need to do is call ECPublicKey.getEncoded() and pass/save the byte array to where you need the key next. The X.509 encoded key can then be reconstructed via:

// Retrieve EC KeyFactory
KeyFactory ECFactory = KeyFactory.getInstance("EC");

// Generate public key via KeyFactory
PublicKey pubKey = ECFactory.generatePublic(new X509EncodedKeySpec(data));
ECPublicKey ECPubKey = (ECPublicKey) pubKey;

where "data" is the encoded byte array.

initramfs
  • 7,625
  • 2
  • 32
  • 56
  • @MaartenBodewes Well it did take longer than I expected to actually get all the constants used in the curve, grabbing them from two or three different documents. I'm still surprised java doesn't have a straightforward way to obtain the ECParameterSpec object directly from named curves, seems like a very useful function if it existed. – initramfs May 27 '15 at 18:49
  • 1
    You're definitely right about that. Happy to support a feature request :) – Maarten Bodewes May 27 '15 at 18:59
  • Thanks for your solution. Where did you get the constants? In particular the p256_seed? – user1094206 May 29 '15 at 17:13
  • 1
    @user1094206 They're all available online in publicly published documents. I got most of the information from [this](https://www.nsa.gov/ia/_files/ecdsa.pdf) document here but used some supporting documents for the other constants. – initramfs May 29 '15 at 17:15
5

That worked for me with the help of Bouncycastle:

ECParameterSpec ecParameterSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
ECNamedCurveSpec params = new ECNamedCurveSpec("secp256r1", spec.getCurve(), spec.getG(), spec.getN());
ECPoint publicPoint =  ECPointUtil.decodePoint(params.getCurve(), publicKeyByteArray);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(publicPoint, params);
PublicKey publicKey =  keyFactory.generatePublic(pubKeySpec);
Hollerweger
  • 901
  • 1
  • 12
  • 29
3

The EC Public Key is a point that consists of x and y co-ordinate. I wrote the following code segment once to convert EC x, y point to publicKey object. Hope this will help you. For your information:

rawPubKey = 04 + x co-ordinate + y co-ordinate (Hex String)

curveName = P-256 (String)

Example EC Public Key Point for P-256:

rawPubKey = 04 6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296 4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5

BC Provider: You need Bouncy Castle provider. I used bcprov-jdk15on-149.jar, but you can download the latest version from here.

/**
 * This method converts the uncompressed raw EC public key into java.security.interfaces.ECPublicKey
 * @param rawPubKey 
 * @param curveName
 * @return java.security.interfaces.ECPublicKey
 */
public ECPublicKey ucPublicKeyToPublicKey(String rawPubKey, String curveName) {
    byte[] rawPublicKey = Helper.toByte(rawPubKey); 
    ECPublicKey ecPublicKey = null;
    KeyFactory kf = null;
    
    ECNamedCurveParameterSpec ecNamedCurveParameterSpec = ECNamedCurveTable.getParameterSpec(curveName);
    ECCurve curve = ecNamedCurveParameterSpec.getCurve();
    EllipticCurve ellipticCurve = EC5Util.convertCurve(curve, ecNamedCurveParameterSpec.getSeed());
    java.security.spec.ECPoint ecPoint = ECPointUtil.decodePoint(ellipticCurve, rawPublicKey);
    ECParameterSpec ecParameterSpec = EC5Util.convertSpec(ellipticCurve, ecNamedCurveParameterSpec);
    java.security.spec.ECPublicKeySpec publicKeySpec = new java.security.spec.ECPublicKeySpec(ecPoint, ecParameterSpec);
    
    kf = java.security.KeyFactory.getInstance("EC");
    
    try {
        ecPublicKey = (ECPublicKey) kf.generatePublic(publicKeySpec);
    } catch (Exception e) {
        System.out.println("Caught Exception public key: " + e.toString());
    }
    
    return ecPublicKey;
}

EDIT: Here is toByte() method:

public static byte[] toByte(String hex) {
        if (hex == null)
            return null;
        hex = hex.replaceAll("\\s", "");
        byte[] buffer = null;
        if (hex.length() % 2 != 0) {
            hex = "0" + hex;
        }
        int len = hex.length() / 2;
        buffer = new byte[len];
        for (int i = 0; i < len; i++) {
            buffer[i] = (byte) Integer.parseInt(
                    hex.substring(i * 2, i * 2 + 2), 16);
        }
        return buffer;
    }

But you can use your own implementation. Here is another one:

import javax.xml.bind.DatatypeConverter;
public static byte[] toByte(String hex) {{
    return DatatypeConverter.parseHexBinary(hex);
}
Community
  • 1
  • 1
rakeb.mazharul
  • 5,423
  • 3
  • 18
  • 39
  • Please at least tell people what libraries you are using and what helper functions like `Helper.toByte` do (and why they are needed in the first place). – Maarten Bodewes May 27 '15 at 01:23
  • @MaartenBodewes I edited my answer. Ty. I thought OP can use his own way (helper or builtin library) to convert a `hex string` into `byte array`. :) – rakeb.mazharul May 27 '15 at 03:50
  • OK, but that's not a well named method - I could not see that it was hex, although I had my suspicions. `ECNamedCurveTable` seems to be a Bouncy Calstle defined class, right? – Maarten Bodewes May 27 '15 at 07:36
  • Thanks! I'd like to avoid using bouncycastle for various reasons. – user1094206 May 29 '15 at 17:16