Skip to content
ecdsa.com

JWK thumbprint · Kotlin

How to compute a JWK thumbprint (RFC 7638) in Kotlin

In Kotlin, JJWT computes a JWK thumbprint with Jwk.thumbprint(), for JWKs parsed with Jwks.parser() and for JCA public keys wrapped with Jwks.builder(); toURI() gives the RFC 9278 form. The run reproduces the published RFC 7638 and RFC 8037 thumbprints for EC, RSA and Ed25519 keys.

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).

Thumbprint.kt — RFC 7638 thumbprints from JWK files and PEM keys with JJWT
import io.jsonwebtoken.security.Jwks
import java.io.File
import java.security.KeyFactory
import java.security.PrivateKey
import java.security.PublicKey
import java.security.spec.X509EncodedKeySpec
import java.util.Base64
fun readPublicKey(file: String, algorithm: String): PublicKey {
val der = Base64.getMimeDecoder().decode(File(file).readText().replace(Regex("-----[^-]+-----"), ""))
return KeyFactory.getInstance(algorithm).generatePublic(X509EncodedKeySpec(der))
}
fun main() {
// From a JWK: thumbprint() is RFC 7638 with SHA-256; toString() gives the base64url form
for (file in listOf("ec.jwk.json", "rsa.jwk.json", "okp.jwk.json")) {
val jwk = Jwks.parser().build().parse(File(file).readText())
println("$file: ${jwk.thumbprint()}")
}
// From a JCA public key, e.g. one read from PEM
for ((file, algorithm) in listOf("ec.pub.pem" to "EC", "rsa.pub.pem" to "RSA", "okp.pub.pem" to "Ed25519")) {
val jwk = Jwks.builder().key<PublicKey, PrivateKey>(readPublicKey(file, algorithm)).build()
println("$file: ${jwk.thumbprint()}")
}
println(Jwks.builder().key<PublicKey, PrivateKey>(readPublicKey("ec.pub.pem", "EC")).build().thumbprint().toURI()) // RFC 9278
}

How it works

  1. Jwks.parser().build().parse(json) reads any JWK type; members other than the required ones do not affect the thumbprint.
  2. jwk.thumbprint() uses SHA-256; its toString() is the base64url value and toURI() the urn:ietf:params:oauth:jwk-thumbprint:sha-256:… URI.
  3. For a PEM key, the JDK's KeyFactory (EC, RSA or Ed25519) reads the SPKI, and Jwks.builder().key(publicKey).build() wraps it.
  4. thumbprint(Jwks.HASH.SHA384) selects another hash algorithm.

Gotchas

  • From Kotlin, Jwks.builder().key(publicKey) with a plain PublicKey does not compile (cannot infer type for type parameter 'B'): the generic overload needs both type arguments, key<PublicKey, PrivateKey>(…). With an ECPublicKey or RSAPublicKey static type, the specific overloads apply.
  • JJWT can set the kid from the thumbprint while building: Jwks.builder().key(k).idFromThumbprint().build().
  • Like signing, this needs jjwt-impl and a JSON module on the runtime class path; parsing a JWK without them fails with UnknownClassException.
  • A thumbprint of a private JWK equals that of its public part, but only jwk.toPublicJwk() belongs in a published key set.

Related recipes