#:package Microsoft.IdentityModel.Tokens@8.23.0using 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 ignoredforeach (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 firstusing 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 directlyvar 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
new JsonWebKey(json)parses a JWK; members other than the required ones are kept but ignored by the thumbprint.ComputeJwkThumbprint()returns the raw SHA-256 digest;Base64UrlEncoder.Encodeturns it into the thumbprint string.- For PEM keys,
ECDsa.ImportFromPemorRSA.ImportFromPemloads the key, andJsonWebKeyConverter.ConvertFromECDsaSecurityKeyorConvertFromRSASecurityKeybuilds the JsonWebKey. CanComputeJwkThumbprint()tells in advance whether a key type is supported; for Ed25519 it isfalse, so the last lines hash the RFC 8037 memberscrv,kty,xdirectly withSHA256.HashData.
Gotchas
- OKP keys (Ed25519) are not supported:
ComputeJwkThumbprintthrowsIDX10706: 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.ToBase64Stringis standard base64, with+,/and padding; thumbprints needBase64UrlEncoder.Encode.X509Certificate2.Thumbprintis the SHA-1 hash of the whole certificate — a different value with a similar name. A JWK thumbprint covers only the public key.- The
kida JsonWebKey carries is whatever the JSON said; IdentityModel does not derive it from the thumbprint unless you setKeyId = Base64UrlEncoder.Encode(jwk.ComputeJwkThumbprint()).