EdDSA for JWT Signing in .NET Core

Scott Brady
Scott Brady
C#

Edwards-curve Digital Signing Algorithm (EdDSA) is the new hotness in digital signing algorithms. From what I’ve seen, it’s the current recommendation from the cryptography community and generally preferred over your typical Elliptic Curve Digital Signature Algorithm (ECDSA).

I’ve had a few chances to play with EdDSA as part of my work with FIDO2 and PASETO, so I’m going to solidify that by writing up my high-level understanding of EdDSA, how to use EdDSA in .NET with Bouncy Castle, and how to sign a JWT with EdDSA using ScottBrady.IdentityModel.

Edwards-curve Digital Signing Algorithm (EdDSA)

EdDSA is a form of elliptic curve cryptography that takes advantage of twisted Edwards curves. It is a variant of Schnorr’s signature system (rather than DSA).

Using EdDSA has a few advantages over ECDSA, mostly due to it being easier to implement and, therefore, more secure. An excellent example of this is its use of random values.

EdDSA only uses random values during private key creation, and the signatures it creates are deterministic, meaning the same private key combined with the same data will produce the same signature every time. Meanwhile, ECDSA uses a random nonce (no more than once) that is generated per signature. Failure to only ever use a nonce value once makes the private key easily recoverable, and this has been seen in the wild with both Sony’s Playstation 3 and Bitcoin. With the Playstation 3, the private key was recovered due to a static nonce, and with Bitcoin, we saw Android users affected due to a bug in the Java SecureRandom class on Android.

If random values are required for the security of a probabilistic signature, then you should prefer a deterministic signature that does not.

Twisted Edwards Curves

When talking about EdDSA, we’re usually talking about Ed25519 and Ed448 (especially for JOSE):

  • Ed25519: a 255-bit curve Curve25519(32-byte private key, 32-byte public key, 64-byte signature). Signing uses SHA-512. Provides 128-bit security
  • Ed448: a 448-bit curve Curve448-Goldilocks (57-byte private key, 57-byte public key, 114-byte signature). Signing uses SHAKE256. Provides 224-bit security

To put things into perspective, Ed25519 offers similar security to ECDSA using NIST P-256 or RSA using a 3000-bit key.

EdDSA, Ed25519, Ed448, and a truckload of variants such as Ed25519ph and Ed25519ctx are defined in RFC 8032. To learn more about EdDSA and these variants, I recommend checking out David Wong’s article “EdDSA, Ed25519, Ed25519-IETF, Ed25519ph, Ed25519ctx, HashEdDSA, PureEdDSA, WTF?”. Otherwise, check out ed25519.cr.yp.to, which lists the benefits of using EdDSA (some are debatable).

Throughout the rest of this article, I’m going to focus on Ed25519.

EdDSA using Bouncy Castle (.NET)

.NET does not currently support EdDSA out of the box due to Windows not yet supporting it. However, it can be found in Bouncy Castle, of which there is a great cross-platform implementation of Bouncy Castle maintained by Claire Novotny called Portable.BouncyCastle. You can install this from nuget:

install-package Portable.BouncyCastle

Creating a Private and Public Key

To create a private key suitable for use, I found it easiest to use the Ed25519KeyPairGenerator and then casting the resulting AsymmetricKeyParameters into their respective Ed25519 variants if necessary.

var keyPairGenerator = new Ed25519KeyPairGenerator();
keyPairGenerator.Init(new Ed25519KeyGenerationParameters(new SecureRandom()));
var keyPair = keyPairGenerator.GenerateKeyPair();

var privateKey = (Ed25519PrivateKeyParameters) keyPair.Private;
var publicKey = (Ed25519PublicKeyParameters) keyPair.Public;

If you’ve ever used the other key pair generators in Bouncy Castle, you’ll appreciate how easy it is to generate an EdDSA key.

Signing Data

To sign a message using Ed25519, you can use an Ed25519Signer. This is your typical Bouncy Castle signer, where Init sets if the signer can create signature (in this case, yes) and the key you want to use (our private key). You then give the signer the data you want to sign by calling BlockUpdate and generate the signature using GenerateSignature.

var message = Encoding.UTF8.GetBytes("Bob Loblaw");

var signer = new Ed25519Signer();
signer.Init(true, privateKey);
signer.BlockUpdate(message, 0, message.Length);

byte[] signature = signer.GenerateSignature();

Validating a Signature

To verify a signature, we again use the Ed25519Signer, using much the same methods, but this time passing the public key to the Init method and calling VerifySignature.

var message = Encoding.UTF8.GetBytes("Bob Loblaw");

var validator = new Ed25519Signer();
validator.Init(false, publicKey);
validator.BlockUpdate(message, 0, message.Length);

bool isValidSignature = validator.VerifySignature(signature);

EdDSA for JWT Signing

EdDSA can also be used to sign a JSON Web Token (JWT). Support for EdDSA was added to JOSE back in 2017, however, it only just seems to be gaining popularity.

With EdDSA, both Ed25519 and Ed448 use an alg value of EdDSA. This is due to security concerns around key mix up. Instead of using the alg header and the signature you receive to figure out the curve, the RFC says you should instead use the key material itself (that you hold separate from the JWT).

So, if we look at a JSON Web Key (JWK), this would mean that an Ed25519 key would have an alg value of "EdDSA" and a crv value of "Ed25519", and an Ed448 key would also have an alg value of "EdDSA" but a crv value of Ed448.

Example Ed25519 JWK

The following is an example EdDSA public key for the Ed25519 curve A private key would use the parameter "d".

{
  "kty": "OKP",
  "alg": "EdDSA",
  "crv": "Ed25519",
  "x": "60mR98SQlHUSeLeIu7TeJBTLRG10qlcDLU4AJjQdqMQ"
}

Signing JWTs with EdDSA in .NET Core

I’ve wrapped the above Bouncy Castle code in the relevant ICryptoProvider and SignatureProvider required to add custom algorithm support to Microsoft.IdentityModel.Tokens. This can be found in my library (GitHub link) which is available on nuget:

install-package ScottBrady.IdentityModel

We can then sign and validate tokens using the usual JsonWebTokenHandler, using the new SecurityKey type of EdDsaSecurityKey:

// private/public key generation
var keyPairGenerator = new Ed25519KeyPairGenerator();
keyPairGenerator.Init(new Ed25519KeyGenerationParameters(new SecureRandom()));
var keyPair = keyPairGenerator.GenerateKeyPair();

var privateKey = (Ed25519PrivateKeyParameters) keyPair.Private;
var publicKey = (Ed25519PublicKeyParameters) keyPair.Public;

var handler = new JsonWebTokenHandler();
            
// create JWT
var token = handler.CreateToken(new SecurityTokenDescriptor
{
    Issuer = "me",
    Audience = "you",
    Subject = new ClaimsIdentity(new[] {new Claim("sub", "123")}),
    
    // using JOSE algorithm "EdDSA"
    SigningCredentials = new SigningCredentials(
		new EdDsaSecurityKey(privateKey), ExtendedSecurityAlgorithms.EdDsa)
});

// validate JWT
var result = handler.ValidateToken(token, new TokenValidationParameters
{
    ValidIssuer = "me",
    ValidAudience = "you",
    IssuerSigningKey = new EdDsaSecurityKey(publicKey)
});

Running the above C# code results in a JWT signed using EdDSA:

eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJhdWQiOiJ5b3UiLCJpc3MiOiJtZSIsImV4cCI6MTU5MDk0NjI1My4wLCJpYXQiOjE1OTA5NDI2NTMsIm5iZiI6MTU5MDk0MjY1M30.JmRSPGh6DlmbEb3F5bAY6Nz8xnO1GbLufxIM_wnsO0XteFRJciMveNYIJ7-vW6CZNrGX9j-r-O66rg5yc29yAA

Which, when decoded, follows the same pattern as any other standard JWT created in C#. Only the algorithm and signature are different.

{
  "alg": "EdDSA",
  "typ": "JWT"
}
{
  "sub": "123",
  "aud": "you",
  "iss": "me",
  "exp": 1590946253,
  "iat": 1590942653,
  "nbf": 1590942653
}

This is compatible with the existing EdDSA JWT implementation found in Nimbus JOSE + JWT and the test vectors found in RFC 8037.

Source Code

You can find a simple console app showing the above code running on GitHub. Or, dive straight into the internals and take a look at ScottBrady.IdentityModel.