Skip to content
ecdsa.com

PEM ⇄ JWK · .NET

How to convert a PEM public key to a JWK and back in C#

In C#, ECDsa.ImportFromPem reads a PEM public key and JsonWebKeyConverter (Microsoft.IdentityModel.Tokens) turns it into a JsonWebKey; for the way back, ECParameters built from the JWK's x and y give an ECDsa whose ExportSubjectPublicKeyInfoPem writes the PEM. The run checks the RFC 7515 round trip byte for byte.

Tested with Microsoft.IdentityModel.Tokens 8.23.0 (.NET SDK 10.0.402) and .NET 10.0.402 (System.Security.Cryptography ECDsa, System.Text.Json).

app.cs — PEM to JWK and JWK to PEM (a .NET 10 file-based app)
#:package Microsoft.IdentityModel.Tokens@8.23.0
using System.Security.Cryptography;
using System.Text.Json.Nodes;
using Microsoft.IdentityModel.Tokens;
// PEM -> JWK
using var ecdsa = ECDsa.Create();
ecdsa.ImportFromPem(File.ReadAllText("pub.pem"));
JsonWebKey jwk = JsonWebKeyConverter.ConvertFromECDsaSecurityKey(new ECDsaSecurityKey(ecdsa));
// Only the public members, as a JsonObject (no reflection-based serialization needed)
string json = new JsonObject { ["kty"] = jwk.Kty, ["crv"] = jwk.Crv, ["x"] = jwk.X, ["y"] = jwk.Y }.ToJsonString();
File.WriteAllText("out.jwk.json", json);
Console.WriteLine(json);
// JWK -> PEM: rebuild the key from its parameters, export SPKI as PEM
var input = new JsonWebKey(File.ReadAllText("pub.jwk.json"));
using var key = ECDsa.Create(new ECParameters
{
Curve = input.Crv switch
{
"P-256" => ECCurve.NamedCurves.nistP256,
"P-384" => ECCurve.NamedCurves.nistP384,
"P-521" => ECCurve.NamedCurves.nistP521,
_ => throw new NotSupportedException(input.Crv),
},
Q = new ECPoint { X = Base64UrlEncoder.DecodeBytes(input.X), Y = Base64UrlEncoder.DecodeBytes(input.Y) },
});
string pem = key.ExportSubjectPublicKeyInfoPem() + "\n";
File.WriteAllText("out.pem", pem);
Console.Write(pem);

How it works

  1. ECDsa.ImportFromPem accepts BEGIN PUBLIC KEY, BEGIN EC PRIVATE KEY and PKCS#8 blocks.
  2. JsonWebKeyConverter.ConvertFromECDsaSecurityKey fills Kty, Crv, X and Y; a JsonObject writes just those members.
  3. For JWK to PEM, new JsonWebKey(json) parses the JWK, and ECParameters with the named curve and Q = (X, Y) recreate the key — ECDsa.Create(parameters) checks the point.
  4. ExportSubjectPublicKeyInfoPem() writes the SPKI PEM with 64-character lines.

Gotchas

  • File-based apps (dotnet run app.cs) disable reflection-based JSON: JsonSerializer.Serialize of an anonymous object throws InvalidOperationException: Reflection-based serialization has been disabled for this application. Use JsonObject, as here, or a source-generated JsonSerializerContext.
  • ExportSubjectPublicKeyInfoPem() returns the PEM without a final newline; OpenSSL and most tools write one, so the code adds it to get an identical file.
  • A point that is not on the curve fails in ECDsa.Create with CryptographicException (point is not on curve from OpenSSL on Linux).
  • Serializing the whole JsonWebKey writes many empty members (key_ops, x5c, oth…); select the members you mean to publish.

Related recipes