Skip to content
ecdsa.com

JWK thumbprint · .NET

How to compute a JWK thumbprint (RFC 7638) in C#

In C#, Microsoft.IdentityModel.Tokens computes a JWK thumbprint with JsonWebKey.ComputeJwkThumbprint() — SHA-256 over the RFC 7638 canonical form — and JsonWebKeyConverter turns ECDsa and RSA keys loaded from PEM into a JsonWebKey first. The run reproduces the published RFC 7638 thumbprint and the EC thumbprint every other recipe of this task computes.

Tested with Microsoft.IdentityModel.Tokens 8.23.0 (.NET SDK 10.0.402).

app.cs — thumbprints from JWK JSON and from PEM keys (a .NET 10 file-based app)
#:package Microsoft.IdentityModel.Tokens@8.23.0
using System.Security.Cryptography;
using System.Text;
using Microsoft.IdentityModel.Tokens;
static string Thumbprint(JsonWebKey jwk) => Base64UrlEncoder.Encode(jwk.ComputeJwkThumbprint()); // SHA-256, RFC 7638
// From a JWK: extra members such as kid, use and alg are ignored
foreach (var file in new[] { "ec.jwk.json", "rsa.jwk.json" })
{
Console.WriteLine($"{file}: {Thumbprint(new JsonWebKey(File.ReadAllText(file)))}");
}
// From a PEM public key: convert the key to a JsonWebKey first
using var ecdsa = ECDsa.Create();
ecdsa.ImportFromPem(File.ReadAllText("ec.pub.pem"));
Console.WriteLine($"ec.pub.pem: {Thumbprint(JsonWebKeyConverter.ConvertFromECDsaSecurityKey(new ECDsaSecurityKey(ecdsa)))}");
using var rsa = RSA.Create();
rsa.ImportFromPem(File.ReadAllText("rsa.pub.pem"));
Console.WriteLine($"rsa.pub.pem: {Thumbprint(JsonWebKeyConverter.ConvertFromRSASecurityKey(new RsaSecurityKey(rsa)))}");
// Ed25519 (OKP): ComputeJwkThumbprint rejects it (IDX10706), so hash the RFC 8037 members directly
var okp = new JsonWebKey(File.ReadAllText("okp.jwk.json"));
byte[] canonical = Encoding.UTF8.GetBytes($$"""{"crv":"{{okp.Crv}}","kty":"{{okp.Kty}}","x":"{{okp.X}}"}""");
Console.WriteLine($"okp.jwk.json: {Base64UrlEncoder.Encode(SHA256.HashData(canonical))}");

How it works

  1. new JsonWebKey(json) parses a JWK; members other than the required ones are kept but ignored by the thumbprint.
  2. ComputeJwkThumbprint() returns the raw SHA-256 digest; Base64UrlEncoder.Encode turns it into the thumbprint string.
  3. For PEM keys, ECDsa.ImportFromPem or RSA.ImportFromPem loads the key, and JsonWebKeyConverter.ConvertFromECDsaSecurityKey or ConvertFromRSASecurityKey builds the JsonWebKey.
  4. CanComputeJwkThumbprint() tells in advance whether a key type is supported; for Ed25519 it is false, so the last lines hash the RFC 8037 members crv, kty, x directly with SHA256.HashData.

Gotchas

  • OKP keys (Ed25519) are not supported: ComputeJwkThumbprint throws IDX10706: Cannot create a JWK thumbprint, 'Kty' must be one of the following: 'EC, RSA, oct, AKP'. The raw string literal above builds their canonical form; it is safe because every member value is base64url or a fixed name.
  • Convert.ToBase64String is standard base64, with +, / and padding; thumbprints need Base64UrlEncoder.Encode.
  • X509Certificate2.Thumbprint is the SHA-1 hash of the whole certificate — a different value with a similar name. A JWK thumbprint covers only the public key.
  • The kid a JsonWebKey carries is whatever the JSON said; IdentityModel does not derive it from the thumbprint unless you set KeyId = Base64UrlEncoder.Encode(jwk.ComputeJwkThumbprint()).

Related recipes