How to create encrypted JWT in C# using RS256 with RSA private key

邮差的信 提交于 2019-12-02 18:30:00

I know this post is old, but it took me forever to figure this out, so I thought I would share.

To test I created RSA keys using OpenSSL:

openssl genrsa -out privateKey.pem 512
openssl rsa -in privateKey.pem -pubout -out publicKey.pem

You will need the following 2 nuget packages:

  1. https://github.com/dvsekhvalnov/jose-jwt
  2. http://www.bouncycastle.org/csharp/

Test Code

public static void Test()
{
        string publicKey = File.ReadAllText(@"W:\Dev\Temp\rsa_keys\publicKey.pem");
        string privateKey = File.ReadAllText(@"W:\Dev\Temp\rsa_keys\privateKey.pem");

        var claims = new List<Claim>();
        claims.Add(new Claim("claim1", "value1"));
        claims.Add(new Claim("claim2", "value2"));
        claims.Add(new Claim("claim3", "value3"));

        var token = CreateToken(claims, privateKey);
        var payload = DecodeToken(token, publicKey);
    }

Create Token

    public static string CreateToken(List<Claim> claims, string privateRsaKey)
    {
        RSAParameters rsaParams;
        using (var tr = new StringReader(privateRsaKey))
        {
            var pemReader = new PemReader(tr);
            var keyPair = pemReader.ReadObject() as AsymmetricCipherKeyPair;
            if (keyPair == null)
            {
                throw new Exception("Could not read RSA private key");
            } 
            var privateRsaParams = keyPair.Private as RsaPrivateCrtKeyParameters;
            rsaParams = DotNetUtilities.ToRSAParameters(privateRsaParams);
        }
        using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
        {
            rsa.ImportParameters(rsaParams);
            Dictionary<string, object> payload = claims.ToDictionary(k => k.Type, v => (object)v.Value);
            return Jose.JWT.Encode(payload, rsa, Jose.JwsAlgorithm.RS256);
        }
    }

Decode Token

    public static string DecodeToken(string token, string publicRsaKey)
    {
        RSAParameters rsaParams;

        using (var tr = new StringReader(publicRsaKey))
        {
            var pemReader = new PemReader(tr);
            var publicKeyParams = pemReader.ReadObject() as RsaKeyParameters;
            if (publicKeyParams == null)
            {
                throw new Exception("Could not read RSA public key");
            }
            rsaParams = DotNetUtilities.ToRSAParameters(publicKeyParams);
        }
        using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
        {
            rsa.ImportParameters(rsaParams);
            // This will throw if the signature is invalid
            return Jose.JWT.Decode(token, rsa, Jose.JwsAlgorithm.RS256);  
        }
    }

I found https://jwt.io/ a great resource to test your tokens

Enrico

If you want to use a certificate, you can retrieve it by it's thumbprint using this method

private X509Certificate2 GetByThumbprint(string Thumbprint)
{
    var localStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
    localStore.Open(OpenFlags.ReadOnly);
    return localStore.Certificates//.Find(X509FindType.FindByKeyUsage, X509KeyUsageFlags.DigitalSignature, false)
        .Find(X509FindType.FindByThumbprint, Thumbprint, false)
        .OfType<X509Certificate2>().First();
}

and then:

private JwtSecurityToken GenerateJWT()
{
    var securityKey = new Microsoft.IdentityModel.Tokens.X509SecurityKey(GetByThumbprint("YOUR-CERT-THUMBPRINT-HERE"));

    var credentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(securityKey, "RS256");

    var JWTHeader = new JwtHeader(credentials);

    var payload = new JwtPayload
    {
        { "iss", "Issuer-here"},
        { "exp", (Int32)(DateTime.UtcNow.AddHours(1).Subtract(new DateTime(1970, 1, 1))).TotalSeconds},
        { "iat", (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds}
    };

    var token = new JwtSecurityToken(JWTHeader, payload);
    return token;
}

If you use a public certificate and .NET 4.6,for decoding you can use:

string token = "eyJhbGciOiJSUzI1NiIsInR....";
string certificate = "MIICnzCCAYcCBgFd2yEPx....";
var publicKey = new X509Certificate2(Convert.FromBase64String(certificate )).GetRSAPublicKey();
string decoded = JWT.Decode(token, publicKey, JwsAlgorithm.RS256);
  1. RS256 is a Signature Algorithm not an Encryption Algorithm
  2. Encryption is done with the public key
  3. Here is the code to create an encrypted JWT:

    var cert = new X509Certificate2(".\\key.cer");
    var rsa = (RSACryptoServiceProvider) cert.PublicKey.Key;
    
    var payload = new Dictionary<string, object>()
    {
      {"sub", "mr.x@contoso.com"},
      {"exp", 1300819380}
    };
    
    var encryptedToken =
      JWT.Encode(
        payload,
        rsa,
        JweAlgorithm.RSA_OAEP,
        JweEncryption.A256CBC_HS512,
        null);
    

The key to this question is using JWT and Bouncy castle libraries for encoding the token and signing it respectively.

  1. JWT for encoding and decoding JWT tokens
  2. Bouncy Castle supports encryption and decryption, especially RS256 get it here

First, you need to transform the private key to the form of RSA parameters. Then you need to pass the RSA parameters to the RSA algorithm as the private key. Lastly, you use the JWT library to encode and sign the token.

    public string GenerateJWTToken(string rsaPrivateKey)
    {
        var rsaParams = GetRsaParameters(rsaPrivateKey);
        var encoder = GetRS256JWTEncoder(rsaParams);

        // create the payload according to your need
        var payload = new Dictionary<string, object>
        {
            { "iss", ""},
            { "sub", "" },
            // and other key-values 
        };

        // add headers. 'alg' and 'typ' key-values are added automatically.
        var header = new Dictionary<string, object>
        {
            { "{header_key}", "{your_private_key_id}" },
        };

        var token = encoder.Encode(header,payload, new byte[0]);

        return token;
    }

    private static IJwtEncoder GetRS256JWTEncoder(RSAParameters rsaParams)
    {
        var csp = new RSACryptoServiceProvider();
        csp.ImportParameters(rsaParams);

        var algorithm = new RS256Algorithm(csp, csp);
        var serializer = new JsonNetSerializer();
        var urlEncoder = new JwtBase64UrlEncoder();
        var encoder = new JwtEncoder(algorithm, serializer, urlEncoder);

        return encoder;
    }

    private static RSAParameters GetRsaParameters(string rsaPrivateKey)
    {
        var byteArray = Encoding.ASCII.GetBytes(rsaPrivateKey);
        using (var ms = new MemoryStream(byteArray))
        {
            using (var sr = new StreamReader(ms))
            {
                // use Bouncy Castle to convert the private key to RSA parameters
                var pemReader = new PemReader(sr);
                var keyPair = pemReader.ReadObject() as AsymmetricCipherKeyPair;
                return DotNetUtilities.ToRSAParameters(keyPair.Private as RsaPrivateCrtKeyParameters);
            }
        }
    }

ps: the RSA private key should have the following format:

-----BEGIN RSA PRIVATE KEY-----

{base64 formatted value}

-----END RSA PRIVATE KEY-----

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!