using System.Security.Cryptography; using var key = ECDsa.Create();key.ImportFromPem(File.ReadAllText("key.pem")); // PKCS#8 or SEC 1: the public point comes along string pem = key.ExportSubjectPublicKeyInfoPem(); // "BEGIN PUBLIC KEY", no trailing newlineFile.WriteAllText("pub.pem", pem + "\n");Console.WriteLine(pem); // The same public key as a JWK: the point's coordinates, base64url without padding (.NET 9+)ECParameters point = key.ExportParameters(includePrivateParameters: false);string x = System.Buffers.Text.Base64Url.EncodeToString(point.Q.X);string y = System.Buffers.Text.Base64Url.EncodeToString(point.Q.Y);Console.WriteLine($$"""{"kty":"EC","crv":"P-256","x":"{{x}}","y":"{{y}}"}"""); How it works
ImportFromPemacceptsBEGIN PRIVATE KEY(PKCS#8) andBEGIN EC PRIVATE KEY(SEC 1); the run feeds it both files and gets byte-identical output.ExportSubjectPublicKeyInfoPem()(.NET 7+) writes SPKI with the curve OID, wrapped at 64 columns — the run compares it byte for byte withopenssl pkey -pubout.ExportSubjectPublicKeyInfo()returns the same structure as DER bytes, the input for fingerprints and forImportSubjectPublicKeyInfoelsewhere.ExportParameters(false)exposes the public point;Q.XandQ.Yare fixed-width big-endian coordinates, so base64url-encoding them directly gives valid JWKxandyvalues.
Gotchas
ExportSubjectPublicKeyInfoPem()returns the PEM without a final newline. Add one before writing a file, or concatenating PEM files and comparing them with other tools' output fails.ExportParameters(true)includes the private scalarD. Passfalsewhenever the result leaves the process — a JWK built from the wrong call publishes the private key.ImportFromPemwants the PEM text. Given a file path, or a certificate, it fails withNo supported key formats were found; for a certificate useX509CertificateLoaderandGetECDsaPublicKey().ExportRSAPublicKeyPemand the otherExport…Pemmethods write different containers. For ECDSA keys only SPKI (BEGIN PUBLIC KEY) is universally understood.