divyanayan awasthi
divyanayan awasthi

Reputation: 950

Generate JWT token with ECDSA private key

Trying to generate signed JWT token with EDCSA private jkey but getting errored out

Generate public and private key using link

https://techdocs.akamai.com/iot-token-access-control/docs/generate-jwt-ecdsa-keys

 private String doGenerateToken(Map<String, Object> claims, String subject)
      throws NoSuchAlgorithmException, InvalidKeySpecException {
    Security.addProvider(new BouncyCastleProvider());
    String EC_PRIVATE_KEY_STR = "-----BEGIN EC PRIVATE KEY-----\n"
        + "MHQCAQEEIBuSmY4MFZ938j0sno1nOICb0ScfIebC1O7DXkvf6UDMoAcGBSuBBAAK\n"
        + "oUQDQgAELAWORZuUv+lpO34bVoYHv6T3Gey+GtuHFB+TH1+l0tRKfKELHcmHlDOK\n"
        + "ebiIegDVhHd6jYx2yT1nOBddjDHCVw==\n"
        + "-----END EC PRIVATE KEY-----\n";
    final KeyFactory keyPairGenerator = KeyFactory.getInstance("EC"); // EC is ECDSA in Java
    ECPrivateKey EC_PRIVATE_KEY = (ECPrivateKey) keyPairGenerator.generatePrivate(
        new PKCS8EncodedKeySpec(
            Base64.decodeBase64(removeEncapsulationBoundaries(EC_PRIVATE_KEY_STR))));

    var currentDateTime = new Date(System.currentTimeMillis());
    final String jwt = Jwts.builder()
        .setHeaderParam("kid", "any")
        .signWith(SignatureAlgorithm.ES256, EC_PRIVATE_KEY)
        .compact();
    return jwt;
  }

Exception :

Exception in thread "main" java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : version mismatch: (supported:     00, parsed:     01

Upvotes: 1

Views: 1745

Answers (1)

dave_thompson_085
dave_thompson_085

Reputation: 38771

Java PKCS8EncodedKeySpec requires a key in PKCS8 format (and specifically PKCS8-clear); that's why the name says PKCS8. That's not a PKCS8-format key so it fails.

To read the key:

If you're generating the key with OpenSSL, as per the website you link (though on the previous page), the easiest way is to convert with OpenSSL:

$ cat ecprivate-sec1
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIBuSmY4MFZ938j0sno1nOICb0ScfIebC1O7DXkvf6UDMoAcGBSuBBAAK
oUQDQgAELAWORZuUv+lpO34bVoYHv6T3Gey+GtuHFB+TH1+l0tRKfKELHcmHlDOK
ebiIegDVhHd6jYx2yT1nOBddjDHCVw==
-----END EC PRIVATE KEY-----
$ openssl pkey <ecprivate-sec1 >ecprivate-pkcs8
$ cat ecprivate-pkcs8
-----BEGIN PRIVATE KEY-----
MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgG5KZjgwVn3fyPSyejWc4
gJvRJx8h5sLU7sNeS9/pQMyhRANCAAQsBY5Fm5S/6Wk7fhtWhge/pPcZ7L4a24cU
H5MfX6XS1Ep8oQsdyYeUM4p5uIh6ANWEd3qNjHbJPWc4F12MMcJX
-----END PRIVATE KEY-----

If you have an ancient (0.9.x) version of OpenSSL, instead do openssl pkcs8 -topk8 -nocrypt.

That value, with the BEGIN/END lines removed and de-base64'ed, will be accepted by Java.

Or OpenSSL can generate this format directly; instead of ecparam -name secp256k1 -genkey -noout ... use:

openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:secp256k1 >ecprivate-pkcs8
# but see below

(Also you can use either ec -pubout or pkey -pubout to get the public key, and the format OpenSSL uses for public key is, after de-PEM-ing, the 'X.509' format Java wants.)

Alternatively, if BouncyCastle is available including bcpkix, that can handle the 'traditional' SEC1/rfc5915 format in PEM directly. With your above EC_PRIVATE_KEY_STR value just do:

PrivateKey privkey = new JcaPEMKeyConverter().getKeyPair (
    (PEMKeyPair) new PEMParser(new StringReader(EC_PRIVATE_KEY_STR)).readObject() 
    ).getPrivate();

(you can declare, and cast to, ECPrivateKey if you want but don't need to).

But that's still wrong.

I don't if anyone did quality control on that website, but JWS ES256 uses ECDSA with P-256, the curve also known as secp256r1 and prime256v1, NOT secp256k1 which is an entirely different and wrong curve. Instead when generating with OpenSSL you should specify either P-256 or prime256v1.

The example they give using command-line openssl to sign doesn't catch this, because OpenSSL is a general purpose tool used for many things not just JOSE/JWS, and for some (other) things secp256k1 is correct. More surprising to me, jjwt -- at least the slightly old version I have -- also doesn't catch this. Nevertheless the result does not comply to the standard and will not be reliably accepted by other systems.

Upvotes: 2

Related Questions