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 bytesn=$(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_hexe=$(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 keyspki=$(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" | thumbprintHow it works
- For EC and Ed25519 keys, the DER of the SPKI ends with the raw key:
04 ‖ x ‖ yfor P-256 (the last 64 bytes are x and y), the 32-byte key for Ed25519. - For RSA,
openssl rsa -pubin -noout -modulusprintsnas hex, and-textgives the exponent in decimal, which is converted to an even-length hex string. printfwrites the members in lexicographic order with no whitespace — the canonical RFC 7638 form.openssl dgst -sha256 -binaryhashes the JSON, and theb64urlhelper 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 -textand parse the printed point. printfmust not add a newline to the JSON: a trailing\nchanges the hash. Theechointhumbprintcomes after the digest, only to end the printed line.-modulusoutput is uppercase hex, whichxxd -r -paccepts; base64url of those bytes is the JWKn. Do not strip a leading00from the modulus by hand —openssl rsa -modulusalready prints it without one.- For JWK input there is nothing to extract: OpenSSL cannot read JWK, so for
jwks.jsonfiles use one of the language recipes or the JWK converter.