JWT Signing using RSASSA-PSS in .NET Core

C#

As of version 5.5, Microsoft’s IdentityModel library now supports the signing of JSON Web Tokens using the RSASSA-PSS (Probabilistic Signature Scheme) digital signature algorithm. This is great news if you’re looking to start building .NET Core systems that implement OpenID’s Financial-grade API and Open Banking, where PS256 should be used for signing.

You can find the full list of support for various .NET targets on GitHub, but the exciting thing is that PS256, PS384, and PS512 are now supported on .NET Core.

RS256 vs. PS256

When deciding between two algorithms such as RS256 (RSASSA-PKCS1-v1_5 using SHA-256) and PS256 (RSASSA-PSS using SHA-256 and MGF1 with SHA-256), we would prefer to use PS256. That’s not to say RSASSA-PKCS1-v1_5 is broken, but rather that RSASSA-PSS simply has desirable features that the other does not, in the form of a probabilistic signature scheme and a security proof.

With RSASSA-PKCS1-v1_5 we’re stuck with a deterministic signature, meaning the same two inputs (e.g. JWTs) would create the same signature. With RSASSA-PSS, we get a probabilistic signature, meaning the same two inputs would create different signatures, thanks to the introduction of some random data, while still being verifiable by the same public key.

A probabilistic signature plus a verifiable security proof makes cryptographers much happier.

I also ran the two .NET implementations through BenchmarkDotNet but struggled to find any noticeable performance differences between them. Signature length is also identical between the two.

PS256 in .NET Core

To get the right APIs we’ll need Microsoft.IdentityModel.JsonWebTokens version 5.5 (or greater). This will also bring in the various Microsoft.IdentityModel libraries that we need:

install-package Microsoft.IdentityModel.JsonWebTokens

We then need an RSA key to sign and validate tokens with. RFC 7518, which defines PS256, enforces a key size of at least 2048 bits. You could load a private key from an X509 certificate, but here we’ll create one in code:

var key = new RsaSecurityKey(RSA.Create(2048));

Token Creation

We can then new up our JWT token handler, a token descriptor, and then use the handler’s CreateToken method to create our JWT. These APIs should be familiar if you have created JWTs before.

var handler = new JsonWebTokenHandler();
var now = DateTime.UtcNow;

var descriptor = new SecurityTokenDescriptor
    {
        Issuer = "me",
        Audience = "you",
        IssuedAt = now,
        NotBefore = now,
        Expires = now.AddMinutes(5),
        Subject = new ClaimsIdentity(new List<Claim> {new Claim("sub", "scott")}),
        SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.RsaSsaPssSha256)
    };

string jwt = handler.CreateToken(descriptor);

Token Validation

To validate our token, we use the same APIs as normal, calling ValidateToken on our token handler. This would be indistinguishable from validating a token signed using RS256:

TokenValidationResult result = handler.ValidateToken(jwt,
    new TokenValidationParameters
    {
        ValidIssuer = "me",
        ValidAudience = "you",
        IssuerSigningKey = new RsaSecurityKey(key.Rsa.ExportParameters(false))
    });

Deterministic vs. Probabilistic

Using the above handlers and descriptors, we can also prove the deterministic behavior of RSASSA-PKCS1-v1_5, and the probabilistic behavior of RSASSA-PSS, by simply calling the CreateToken method twice for each one. The following unit tests prove this:

[Fact]
public void WhenGeneratedWithDeterministicSignatureScheme_ExpectIdenticalJwts()
{
    descriptor.SigningCredentials = new SigningCredentials(key, "RS256");

    var token1 = handler.CreateToken(descriptor);
    var token2 = handler.CreateToken(descriptor);

    Assert.Equal(token1, token2);
}

[Fact]
public void WhenGeneratedWithProbabilisticSignatureScheme_ExpectDifferentJwts()
{
    descriptor.SigningCredentials = new SigningCredentials(key, "PS256");

    var token1 = handler.CreateToken(descriptor);
    var token2 = handler.CreateToken(descriptor);

    Assert.NotEqual(token1, token2);
}

Source Code and Test Runner

You can find the full source from this article on GitHub. It’s a console app showing PS256 JWT creation and validation, and the two unit tests shown above.