Skip to content
ecdsa.com

PEM ⇄ JWK · Kotlin

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

In Kotlin, the JDK reads the SPKI inside a PEM public key and JJWT converts between JCA keys and JWKs: Jwks.builder().key(publicKey).build() makes the JWK and Jwks.json serializes it; Jwks.parser() reads a JWK, and toKey().encoded is the SPKI DER for the PEM. The run checks the RFC 7515 round trip byte for byte.

Tested with io.jsonwebtoken:jjwt 0.13.0 (Kotlin 2.4.21, jjwt-gson with Gson 2.14.0) and OpenJDK 25.0.4.1 (Eclipse Temurin 25.0.4.1+1).

Convert.kt — PEM to JWK and JWK to PEM with JJWT
import io.jsonwebtoken.security.Jwks
import io.jsonwebtoken.security.PublicJwk
import java.io.File
import java.security.KeyFactory
import java.security.interfaces.ECPublicKey
import java.security.spec.X509EncodedKeySpec
import java.util.Base64
fun main() {
// PEM -> JWK: the JDK reads the SPKI, JJWT builds and serializes the JWK
val der = Base64.getMimeDecoder().decode(File("pub.pem").readText().replace(Regex("-----[^-]+-----"), ""))
val key = KeyFactory.getInstance("EC").generatePublic(X509EncodedKeySpec(der)) as ECPublicKey
val json = Jwks.json(Jwks.builder().key(key).build())
File("out.jwk.json").writeText(json)
println(json)
// JWK -> PEM: parse the JWK, getEncoded() of its key is the SPKI DER
val jwk = Jwks.parser().build().parse(File("pub.jwk.json").readText()) as PublicJwk<*>
val body = Base64.getMimeEncoder(64, "\n".toByteArray()).encodeToString(jwk.toKey().encoded)
val pem = "-----BEGIN PUBLIC KEY-----\n$body\n-----END PUBLIC KEY-----\n"
File("out.pem").writeText(pem)
print(pem)
}

How it works

  1. The PEM armour is removed and the body decoded with the MIME decoder; X509EncodedKeySpec reads the SPKI as an ECPublicKey.
  2. Jwks.builder().key(key).build() returns an EcPublicJwk; Jwks.json(jwk) writes it as JSON.
  3. Jwks.parser().build().parse(json) validates the JWK (curve, point) and returns a PublicJwk for a public key.
  4. toKey().encoded is the SPKI DER; the MIME encoder with 64-character lines rebuilds the PEM.

Gotchas

  • Jwks.json accepts only a PublicJwk, so a private JWK cannot be serialized with it by accident; toString() of a private JWK prints <redacted> for secret members. Other JSON serializers handed a private Jwk get no such protection.
  • Jwks.builder().key(…) needs the key's static type: with ECPublicKey (as here) the EC overload applies; with a plain PublicKey, Kotlin needs explicit type arguments.
  • Parsing a JWK needs jjwt-impl and a JSON module (jjwt-gson or jjwt-jackson) at runtime, like every other JJWT operation.
  • The JDK's KeyFactory cannot read BEGIN EC PRIVATE KEY or certificate PEM files; this route is for BEGIN PUBLIC KEY blocks.

Related recipes