Skip to content
ecdsa.com

PEM ⇄ JWK · Java

How to convert a PEM public key to a JWK and back in Java

In Java, the JDK reads and writes the SPKI inside a PEM public key, and Nimbus JOSE + JWT converts between JCA keys and JWKs: ECKey.Builder turns an ECPublicKey into a JWK, and JWK.parse(…).toECKey().toECPublicKey() turns a JWK back into a key whose getEncoded() is the SPKI DER. The run checks the RFC 7515 round trip byte for byte.

Tested with com.nimbusds:nimbus-jose-jwt 10.10 (OpenJDK 25.0.4.1 (Temurin)).

Convert.java — PEM to JWK and JWK to PEM with Nimbus JOSE + JWT
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.JWK;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyFactory;
import java.security.interfaces.ECPublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class Convert {
public static void main(String[] args) throws Exception {
// PEM -> JWK: the JDK reads the SPKI, Nimbus builds the JWK
String pem = Files.readString(Path.of("pub.pem"));
byte[] spki = Base64.getMimeDecoder().decode(pem.replaceAll("-----[^-]+-----", ""));
ECPublicKey key = (ECPublicKey) KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(spki));
ECKey jwk = new ECKey.Builder(Curve.forECParameterSpec(key.getParams()), key).build();
Files.writeString(Path.of("out.jwk.json"), jwk.toJSONString());
System.out.println(jwk.toJSONString());
// JWK -> PEM: toECPublicKey() gives a JCA key; getEncoded() is its SPKI DER
ECKey parsed = JWK.parse(Files.readString(Path.of("pub.jwk.json"))).toECKey();
String body = Base64.getMimeEncoder(64, "\n".getBytes()).encodeToString(parsed.toECPublicKey().getEncoded());
String out = "-----BEGIN PUBLIC KEY-----\n" + body + "\n-----END PUBLIC KEY-----\n";
Files.writeString(Path.of("out.pem"), out);
System.out.print(out);
}
}

How it works

  1. X509EncodedKeySpec with KeyFactory.getInstance("EC") reads the decoded PEM body as an ECPublicKey.
  2. Curve.forECParameterSpec(key.getParams()) finds the JWK curve name (P-256) for the key; new ECKey.Builder(curve, key).build() makes the JWK, and toJSONString() writes it.
  3. JWK.parse(json).toECKey().toECPublicKey() validates the JWK and returns a JCA key.
  4. getEncoded() of a public key is its SPKI DER; the MIME encoder with 64-character lines and the PEM armour give the original file.

Gotchas

  • The JDK has no PEM API: the armour is removed and added by hand. Base64.getMimeDecoder() tolerates the line breaks; the plain decoder fails with Illegal base64 character a.
  • ECKey.Builder has no private key unless .privateKey(…) is called, so the JWK here is public. For a key pair, publish jwk.toPublicJWK() — it drops d.
  • Curve.forECParameterSpec returns null for curves Nimbus has no JOSE name for, and the builder then throws NullPointerException: The curve must not be null; check the result when keys come from outside.
  • JWK.parseFromPEMEncodedObjects reads PEM directly, but only with Bouncy Castle on the class path; the JDK-only route above avoids that dependency.

Related recipes