JWT Signing using ECDSA in .NET Core

C#

Recently, as part of messing around with an identity provider, I was given the following private/public key pair and told to sign a JSON Web Token (JWT) with them using ES256:

Private: c711e5080f2b58260fe19741a7913e8301c1128ec8e80b8009406e5047e6e1ef
Public: 04e33993f0210a4973a94c26667007d1b56fe886e8b3c2afdd66aa9e4937478ad20acfbdc666e3cec3510ce85d40365fc2045e5adb7e675198cf57c6638efa1bdb

Okay, sounds simple enough. 5 days and a lot of swearing later, I finally got it working. Now I’m going to write it down so that I don’t have to go through it again.

.NET Core

In .NET Core, to sign a JWT using an Elliptic Curve Digital Signature Algorithm (ECDSA) we need to get ourselves an instance of ECDsaSecurityKey. The constructor for this takes in an instance of ECDsa, which in turn we have to pass in an instance of ECParameters if we want to load in our own key and not have it generate one for us. So, let’s make a start!

Converting from Hexadecimal String to Byte Array

First, we need to turn our keys into something usable, since none of the APIs in .NET for ECDSA (that I could find) accept a hexadecimal string or even string. After a bit of digging it turns out there’s a few ways to get a byte array from a hexadecimal string. In fact, there’s a rather popular Stack Overflow question which even includes a performance analysis of the answers.

Let’s take the simplest one:

private static byte[] FromHexString(string hex) {
    var numberChars = hex.Length;
    var hexAsBytes = new byte[numberChars / 2];
    for (var i = 0; i < numberChars; i += 2)
        hexAsBytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);

return hexAsBytes; }

Byte Array to ECDsa

We need some way to get our private key into ECDsa via ECParameters. This involves extracting the d, x, and y parameters of our key (just x & y for our public key). The simplest way I could find to do this is to use Bouncy Castle, so let’s load that in now:

install-package Portable.BouncyCastle

For our private key, we can now convert our byte array into a Bouncy Castle big integer and use that with one of their elliptical curve structures that will parse our x & y parameters for us. From this data, we can then initialize an instance of ECDsa.

In this example I’m using ECDSA using P-256 curve and SHA-256 hash algorithm (aka ES256) to sign our JWT. This means I’ll be using the NIST P-256 curve (aka secp256r1, or OID 1.2.840.10045.3.1.7, or in bytes 2A8648CE3D030107). .NET supports the NIST and brainpool curves.

If you’re looking for curves used with blockchains such as secp256k1, you’re going to need to look into open source alternatives.

private static ECDsa LoadPrivateKey(byte[] key) {
    var privKeyInt = new Org.BouncyCastle.Math.BigInteger(+1, key);
    var parameters = SecNamedCurves.GetByName("secp256r1");
    var ecPoint = parameters.G.Multiply(privKeyInt);
    var privKeyX = ecPoint.Normalize().XCoord.ToBigInteger().ToByteArrayUnsigned();
    var privKeyY = ecPoint.Normalize().YCoord.ToBigInteger().ToByteArrayUnsigned();

return ECDsa.Create(new ECParameters { Curve = ECCurve.NamedCurves.nistP256, D = privKeyInt.ToByteArrayUnsigned(), Q = new ECPoint { X = privKeyX, Y = privKeyY } }); }

For our public key we can do things manually, since we know the first byte is the tag (04) and then the rest of the key is the x & y parameters which are equal in length.

private static ECDsa LoadPublicKey(byte[] key) {
    var pubKeyX = key.Skip(1).Take(32).ToArray();
    var pubKeyY = key.Skip(33).ToArray();

return ECDsa.Create(new ECParameters { Curve = ECCurve.NamedCurves.nistP256, Q = new ECPoint { X = pubKeyX, Y = pubKeyY } }); }

Signing JWT using ECDSA

Now that we have our keys loaded in we can now sign and verify JSON Web Tokens using ECDSA. First, we’ll need the usual library:

install-package System.IdentityModel.Tokens.Jwt

For creating tokens we’ll use the previously mentioned ECDsaSecurityKey class for our SigningCredentials:

private static string CreateSignedJwt(ECDsa eCDsa) {
    var now = DateTime.UtcNow;
    var tokenHandler = new JwtSecurityTokenHandler();

var jwtToken = tokenHandler.CreateJwtSecurityToken( issuer: "me", audience: "you", subject: null, notBefore: now, expires: now.AddMinutes(30), issuedAt: now, signingCredentials: new SigningCredentials( new ECDsaSecurityKey(eCDsa), SecurityAlgorithms.EcdsaSha256));
return tokenHandler.WriteToken(jwtToken); }

And then verify it using something similar:

private static bool VerifySignedJwt(ECDsa eCDsa, string token) {
    var tokenHandler = new JwtSecurityTokenHandler();

var claimsPrincipal = tokenHandler.ValidateToken(token, new TokenValidationParameters { ValidIssuer = "me", ValidAudience = "you", IssuerSigningKey = new ECDsaSecurityKey(eCDsa) }, out var parsedToken);
return claimsPrincipal.Identity.IsAuthenticated; }

Run It

static void Main(string[] args) {
    const string privateKey = "c711e5080f2b58260fe19741a7913e8301c1128ec8e80b8009406e5047e6e1ef";
    const string publicKey = "04e33993f0210a4973a94c26667007d1b56fe886e8b3c2afdd66aa9e4937478ad20acfbdc666e3cec3510ce85d40365fc2045e5adb7e675198cf57c6638efa1bdb";

var privateECDsa = LoadPrivateKey(FromHexString(privateKey)); var publicECDsa = LoadPublicKey(FromHexString(publicKey));
var jwt = CreateSignedJwt(privateECDsa); var isValid = VerifySignedJwt(publicECDsa, jwt);
Console.WriteLine(isValid ? "Valid!" : "Not Valid..."); }

Can you do this better?

If you can figure out a simpler way, then please let me know. I had a hard time hacking the above together, and it’s clear to me I need to improve some of my crypto knowledge as a result.

You can find the full solution on GitHub. Pull requests using better solutions will be accepted, just add any alternative approaches as a new method.

Scott Brady

Scott Brady

Scott Brady is the Identity & Access Control Lead at Rock Solid Knowledge, focusing on authorization & authentication protocols such as OAuth and OpenID Connect.

  • ETH: 0xC32481796340ff8A42832B12c6a687129977FE52
  • BTC: 3PAvLKXJKL7HGceDzyZycZC4DcV7TqYpjp
  • Paypal: paypal.me/scottbrady91

I'm currently trialling donations/tips as a replacement for Google AdSense

Keep Up To Date

Follow me on Twitter to keep up to date with the latest articles and announcements.