Skip to content
ecdsa.com

JWK thumbprint · OpenSSL

How to compute a JWK thumbprint (RFC 7638) with OpenSSL

The OpenSSL command line has no JWK support, but a JWK thumbprint can still be computed from a PEM public key: extract the key components with openssl pkey and openssl rsa, build the RFC 7638 JSON with printf, and hash it with openssl dgst -sha256. The script reproduces the published thumbprints of the RSA and Ed25519 test keys.

Tested with OpenSSL 3.5.7 (Debian 13, with xxd and bash).

thumbprint.sh — EC, RSA and Ed25519 thumbprints from PEM public keys
b64url() { openssl base64 -A | tr '+/' '-_' | tr -d '='; }
hex_b64url() { xxd -r -p | b64url; }
thumbprint() { openssl dgst -sha256 -binary | b64url; echo; }
# EC P-256: the SPKI DER ends with the point 04 ‖ x ‖ y (32 bytes each)
spki=$(openssl pkey -pubin -in ec.pub.pem -outform DER | xxd -p | tr -d '\n')
x=$(printf '%s' "${spki: -128:64}" | hex_b64url)
y=$(printf '%s' "${spki: -64}" | hex_b64url)
printf '{"crv":"P-256","kty":"EC","x":"%s","y":"%s"}' "$x" "$y" | thumbprint
# RSA: modulus and exponent as unsigned big-endian bytes
n=$(openssl rsa -pubin -in rsa.pub.pem -noout -modulus | cut -d= -f2 | hex_b64url)
e_dec=$(openssl rsa -pubin -in rsa.pub.pem -noout -text | sed -n 's/^Exponent: \([0-9]*\).*/\1/p')
e_hex=$(printf '%x' "$e_dec"); (( ${#e_hex} % 2 )) && e_hex=0$e_hex
e=$(printf '%s' "$e_hex" | hex_b64url)
printf '{"e":"%s","kty":"RSA","n":"%s"}' "$e" "$n" | thumbprint
# Ed25519: the SPKI DER ends with the 32-byte public key
spki=$(openssl pkey -pubin -in okp.pub.pem -outform DER | xxd -p | tr -d '\n')
x=$(printf '%s' "${spki: -64}" | hex_b64url)
printf '{"crv":"Ed25519","kty":"OKP","x":"%s"}' "$x" | thumbprint

How it works

  1. For EC and Ed25519 keys, the DER of the SPKI ends with the raw key: 04 ‖ x ‖ y for P-256 (the last 64 bytes are x and y), the 32-byte key for Ed25519.
  2. For RSA, openssl rsa -pubin -noout -modulus prints n as hex, and -text gives the exponent in decimal, which is converted to an even-length hex string.
  3. printf writes the members in lexicographic order with no whitespace — the canonical RFC 7638 form.
  4. openssl dgst -sha256 -binary hashes the JSON, and the b64url helper turns the digest into the base64url thumbprint.

Gotchas

  • The tail-of-DER trick depends on the curve: for P-384 the coordinates are 48 bytes each, for P-521 66 bytes. Adjust the offsets, or use openssl pkey -text and parse the printed point.
  • printf must not add a newline to the JSON: a trailing \n changes the hash. The echo in thumbprint comes after the digest, only to end the printed line.
  • -modulus output is uppercase hex, which xxd -r -p accepts; base64url of those bytes is the JWK n. Do not strip a leading 00 from the modulus by hand — openssl rsa -modulus already prints it without one.
  • For JWK input there is nothing to extract: OpenSSL cannot read JWK, so for jwks.json files use one of the language recipes or the JWK converter.

Related recipes