Using openssh public key (ecdsa-sha2-nistp256) wit

2019-03-03 03:13发布

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?

2条回答
爷的心禁止访问
2楼-- · 2019-03-03 03:51

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://tools.ietf.org/html/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://tools.ietf.org/html/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://tools.ietf.org/html/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://tools.ietf.org/html/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.

查看更多
混吃等死
3楼-- · 2019-03-03 04:06

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);
    }
}
查看更多
登录 后发表回答