1

Is there a Java library/example to read an openssh format ecdsa public key to a JCE PublicKey in Java? I want to use EC for JWT .

The format I'm trying to read is as per authorized_keys, or Github API (e.g. https://api.github.com/users/davidcarboni/keys): ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK8hPtB72/sfYgNw1WTska2DNOJFx+QhUxuV6OLINSD2ty+6gxcM8yZrvMqWdMePGRb2cGh8L/0bGOk+64IQ/pM=

I've found this answer, which is fine for RSA and DSS: Using public key from authorized_keys with Java security, and this discussion of the openssh format for ECDSA: https://security.stackexchange.com/questions/129910/ecdsa-why-do-ssh-keygen-and-java-generated-public-keys-have-different-sizes

However I'm getting lost trying to adapt the RSS/DSA code for ECDSA - I'm not sure how to set up an ECPublicKeySpec. It needs ECPoint, EllipticCurve, ECParameterSpec, ECField. The openssh format only contains two integers, which makes sense for ECPoint, but I don't know how to set up the rest.

I've been poking around a bunch of libraries, including jsch, sshj, ssh-tools and good old Bouncycastle. The closest I have is:

com.jcraft.jsch.KeyPair load = com.jcraft.jsch.KeyPair.load(jsch, null, bytes[openSshKey]);

Which loads the key fine, but doesn't get me to a JCE PublicKey - just a byte[] getPublicKeyBlob() method.

Am I missing something obvious?

David Carboni
  • 1,556
  • 23
  • 24
  • I've made some progress in setting up `ECPublicKeySpec` by using [`com.nimbusds.jose.jwk.ECKey`](https://static.javadoc.io/com.nimbusds/nimbus-jose-jwt/2.21/com/nimbusds/jose/jwk/ECKey.Curve.html). This provides a `ECKey.Curve.P_256.toECParameterSpec()` call, which sorts out most of the mystery parameters. That just leaves the [`ECPoint`](https://docs.oracle.com/javase/7/docs/api/java/security/spec/ECPoint.html), so it's looking more likely the RSA/DSA example could be adapted. – David Carboni Jun 29 '17 at 10:02
  • So, well, interesting. I'm adapting the RSA/DSA example and am getting the values "ecdsa-sha2-nistp256", "nistp256" and then just one BigInt (rather than two). I'm wondering if that's the "public part" only given it's a public key? I guess it's time to read [RFC 5656, section 3.1](https://tools.ietf.org/html/rfc5656#section-3.1). – David Carboni Jun 29 '17 at 10:40

2 Answers2

1

I've found a way to do this using Bouncycastle (but would like to find a JCE way).

Adapting the code from Using public key from authorized_keys with Java security, and refering to RFC 5656, section 3.1, the following block added to decodePublicKey will parse the single BigInt value Q, which is "the public key encoded from an elliptic curve point":

if (type.startsWith("ecdsa-sha2-") &&
            (type.endsWith("nistp256") || type.endsWith("nistp384") || type.endsWith("nistp521"))) {

        // Based on RFC 5656, section 3.1 (https://www.rfc-editor.org/rfc/rfc5656#section-3.1)

        // The string [identifier] is the identifier of the elliptic curve
        // domain parameters.  The format of this string is specified in
        // Section 6.1 (https://www.rfc-editor.org/rfc/rfc5656#section-6.1).
        // Information on the REQUIRED and RECOMMENDED sets of
        // elliptic curve domain parameters for use with this algorithm can be
        // found in Section 10 (https://www.rfc-editor.org/rfc/rfc5656#section-10).
        String identifier = decodeType();
        if (!type.endsWith(identifier)) {
            throw new IllegalArgumentException("Invalid identifier " + identifier + " for key type " + type + ".");
        }

        // Q is the public key encoded from an elliptic curve point into an
        // octet string as defined in Section 2.3.3 of [SEC1];
        // (https://www.rfc-editor.org/rfc/rfc5656#ref-SEC1)
        // point compression MAY be used.
        BigInteger q = decodeBigInt();

        ECPublicKey keyBC = getKeyBC(q, identifier);
        return keyBC;
    }

The solution I've found for getting from Q to an ECPublicKey is the following, using the Bouncycastle API (credit to Generate ECPublicKey from ECPrivateKey for providing the starting point):

ECPublicKey getKeyBC(BigInteger q, String identifier) {
    // https://stackoverflow.com/questions/42639620/generate-ecpublickey-from-ecprivatekey
    try {
        // This only works with the Bouncycastle library:
        Security.addProvider(new BouncyCastleProvider());
        // http://www.bouncycastle.org/wiki/pages/viewpage.action?pageId=362269#SupportedCurves(ECDSAandECGOST)-NIST(aliasesforSECcurves)
        String name = identifier.replace("nist", "sec") + "r1";
        KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", "BC");
        ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec(name);
        ECPoint point = ecSpec.getCurve().decodePoint(q.toByteArray());
        ECPublicKeySpec pubSpec = new ECPublicKeySpec(point, ecSpec);
        ECPublicKey publicKey = (ECPublicKey) keyFactory.generatePublic(pubSpec);
        return publicKey;
    } catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchProviderException e) {
        throw new RuntimeException(e);
    }
}

That gets you from an openssh format elliptic curve public key (ssh-keygen -t ecdsa -b [256|384|521]) to a JCE ECPublicKey.

Community
  • 1
  • 1
David Carboni
  • 1,556
  • 23
  • 24
  • I've posted a new question about whether there's a JCE way to do this: https://stackoverflow.com/questions/44829426/can-i-create-a-jca-ecpublickey-from-a-q-value-from-an-openssh-public-key-and-ecp – David Carboni Jun 29 '17 at 15:33
1

For completeness, here's the code I've gone with. It's nearly-pure JCE, with a sprinkling of Bouncycastle inside helper methods (this updates the example code in Using public key from authorized_keys with Java security):

...
        } else if (type.startsWith("ecdsa-sha2-") &&
                (type.endsWith("nistp256") || type.endsWith("nistp384") || type.endsWith("nistp521"))) {
            // Based on RFC 5656, section 3.1 (https://tools.ietf.org/html/rfc5656#section-3.1)
            String identifier = decodeType();
            BigInteger q = decodeBigInt();
            ECPoint ecPoint = getECPoint(q, identifier);
            ECParameterSpec ecParameterSpec = getECParameterSpec(identifier);
            ECPublicKeySpec spec = new ECPublicKeySpec(ecPoint, ecParameterSpec);
            return KeyFactory.getInstance("EC").generatePublic(spec);
        } ...

/**
 * Provides a means to get from a parsed Q value to the X and Y point values.
 * that can be used to create and ECPoint compatible with ECPublicKeySpec.
 *
 * @param q          According to RFC 5656:
 *                   "Q is the public key encoded from an elliptic curve point into an octet string"
 * @param identifier According to RFC 5656:
 *                   "The string [identifier] is the identifier of the elliptic curve domain parameters."
 * @return An ECPoint suitable for creating a JCE ECPublicKeySpec.
 */
ECPoint getECPoint(BigInteger q, String identifier) {
    String name = identifier.replace("nist", "sec") + "r1";
    ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec(name);
    org.bouncycastle.math.ec.ECPoint point = ecSpec.getCurve().decodePoint(q.toByteArray());
    BigInteger x = point.getAffineXCoord().toBigInteger();
    BigInteger y = point.getAffineYCoord().toBigInteger();
    System.out.println("BC x = " + x);
    System.out.println("BC y = " + y);
    return new ECPoint(x, y);
}

/**
 * Gets the curve parameters for the given key type identifier.
 *
 * @param identifier According to RFC 5656:
 *                   "The string [identifier] is the identifier of the elliptic curve domain parameters."
 * @return An ECParameterSpec suitable for creating a JCE ECPublicKeySpec.
 */
ECParameterSpec getECParameterSpec(String identifier) {
    try {
        // http://www.bouncycastle.org/wiki/pages/viewpage.action?pageId=362269#SupportedCurves(ECDSAandECGOST)-NIST(aliasesforSECcurves)
        String name = identifier.replace("nist", "sec") + "r1";
        AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
        parameters.init(new ECGenParameterSpec(name));
        return parameters.getParameterSpec(ECParameterSpec.class);
    } catch (InvalidParameterSpecException | NoSuchAlgorithmException e) {
        throw new IllegalArgumentException("Unable to get parameter spec for identifier " + identifier, e);
    }
}
David Carboni
  • 1,556
  • 23
  • 24
  • is there anyway I could convince you to share your project on github? I'm worried that my seemingly identical question will be flagged as a repeat of yours and have a limited understanding of cryptography. Thanks - Chris – ChrisHaze Dec 23 '17 at 15:30
  • Happily that code is visible on Github, at least for now: https://github.com/rosgovuk/discharges-jwt/blob/master/src/main/java/uk/gov/ros/discharges/security/OpenSshPublicKey.java I'll take a backup just in case, feel free to ask if it does disappear. – David Carboni Feb 06 '18 at 22:10