How to sign a JWT using RS256 with RSA private key - c#

I am using the jose-jwt library and want to create a signed JWT in C# using the RS256 algorithm for encryption. I have no experience with cryptography, so please excuse my ignorance. I see the following example in the docs:
var payload = new Dictionary<string, object>()
{
{ "sub", "mr.x#contoso.com" },
{ "exp", 1300819380 }
};
var privateKey=new X509Certificate2("my-key.p12", "password", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet).PrivateKey as RSACryptoServiceProvider;
string token=Jose.JWT.Encode(payload, privateKey, JwsAlgorithm.RS256);
which shows the use of a p12 file, but how do I use an RSA key file of the form below? I am looking at the docs for X509Certificate2, but I see no option for RSA private keys. It appears to only accept PKCS7, which I understand to be public keys.
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp
wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5
1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh
3tx4VgMtrQ+WEgCjhoTwo23KMBAuJGSYnRmoBZM3lMfTKevIkAidPExvYCdm5dYq3XToLkkLv5L2
pIIVOFMDG+KESnAFV7l2c+cnzRMW0+b6f8mR1CJzZuxVLL6Q02fvLi55/mbSYxECQQDeAw6fiIQX
GukBI4eMZZt4nscy2o12KyYner3VpoeE+Np2q+Z3pvAMd/aNzQ/W9WaI+NRfcxUJrmfPwIGm63il
AkEAxCL5HQb2bQr4ByorcMWm/hEP2MZzROV73yF41hPsRC9m66KrheO9HPTJuo3/9s5p+sqGxOlF
L0NDt4SkosjgGwJAFklyR1uZ/wPJjj611cdBcztlPdqoxssQGnh85BzCj/u3WqBpE2vjvyyvyI5k
X6zk7S0ljKtt2jny2+00VsBerQJBAJGC1Mg5Oydo5NwD6BiROrPxGo2bpTbu/fhrT8ebHkTz2epl
U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ
37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0=
-----END RSA PRIVATE KEY-----
Finally, what is the difference between the two options listed in the docs, and how do I choose between the two?
-------------------------- OPTION 1 --------------------------
RS-* and PS-* family
CLR:
RS256, RS384, RS512 and PS256, PS384, PS512 signatures require
RSACryptoServiceProvider (usually private) key of corresponding
length. CSP need to be forced to use Microsoft Enhanced RSA and AES
Cryptographic Provider. Which usually can be done be re-importing
RSAParameters. See http://clrsecurity.codeplex.com/discussions/243156
for details.
-------------------------- OPTION 2 --------------------------
CORECLR: RS256, RS384, RS512 signatures require RSA (usually private) key of corresponding length.

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:
https://github.com/dvsekhvalnov/jose-jwt
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

The key to this question is using JWT and Bouncy castle libraries for encoding the token and signing it respectively.
JWT for encoding and decoding JWT tokens
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-----

I used http://travistidwell.com/jsencrypt/demo/
to create 2048 bit keys
Using .NET 6:
string CreateRsaToken(string someClaimValue)
{
var rsaPrivateKey = #"-----BEGIN RSA PRIVATE KEY-----
MIIEogIBA...";
using var rsa = RSA.Create();
rsa.ImportFromPem(rsaPrivateKey);
var signingCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256)
{
CryptoProviderFactory = new CryptoProviderFactory { CacheSignatureProviders = false }
};
var now = DateTime.Now;
var jwt = new JwtSecurityToken(
audience: "all",
issuer: "TheBitCoinKing",
claims: new[] {
new Claim("SomeClaim", someClaimValue),
},
notBefore: now,
expires: now.AddMinutes(1),
signingCredentials: signingCredentials
);
return new JwtSecurityTokenHandler().WriteToken(jwt);
}
packages:
Microsoft.IdentityModel.Tokens
System.IdentityModel.Tokens.Jwt

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;
}

Using BouncyCastle and Jose nuget package, the following code works for me.
public static string CreateToken(Dictionary<string, object> payload)
{
string jwt = string.Empty;
RsaPrivateCrtKeyParameters keyPair;
var cert = ConfigurationManager.AppSettings["cert"];
/// cert begins -----BEGIN PRIVATE KEY----- and ends with -END PRIVATE KEY-----";
using (var sr = new StringReader(cert))
{
PemReader pr = new PemReader(sr);
keyPair = (RsaPrivateCrtKeyParameters)pr.ReadObject();
}
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters(keyPair);
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(rsaParams);
jwt = Jose.JWT.Encode(payload, rsa, Jose.JwsAlgorithm.RS256);
}
return jwt;
}

The GetRSAPrivateKey is only available in .NET 4.6. See the URL below.
https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.rsacertificateextensions.getrsaprivatekey(v=vs.110).aspx

Posting the code to create RS256 JWT token for GCP OAuth Token API call, using the Service Account JSON Key-:
using JWT;
using JWT.Algorithms;
using JWT.Serializers;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
namespace GCP
{
class JWTTokenGenerationForGCPOAuthTokenAPI
{
public static string GenerateJWTToken()
{
var rsaParams = ReadAsymmetricKeyParameter();
var encoder = GetRS256JWTEncoder(rsaParams);
var iat = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var exp = DateTimeOffset.UtcNow.AddMinutes(60).ToUnixTimeSeconds();
// create the payload according to your need
// iss is the Service Account Email ID
var payload = new Dictionary<string, object>
{
{ "iss", "<service-account>#<project-id>.iam.gserviceaccount.com"},
{ "scope", "https://www.googleapis.com/auth/cloud-platform" },
{ "aud", "https://oauth2.googleapis.com/token" },
{ "exp", exp},
{ "iat", iat}
};
//Final token
var token = encoder.Encode(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;
}
public static RSAParameters ReadAsymmetricKeyParameter()
{
\\ This key is fetched from the GCP Service Account JSON File.
\\"private_key": "-----BEGIN PRIVATE KEY-----\n<long-code>-----END PRIVATE KEY-----\n",
\\ pick <long-code> from above. Replace all \n with actual new line like shown below.
string pkey = #"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDSoGKK/Dzb8MBy
################################################################
################################################################
################################################################
################################################################
twySMqKKWnIC/zZljrvp4w==";
RsaPrivateCrtKeyParameters rsaPrivateCrtKeyParameters1;
var keyBytes = Convert.FromBase64String(pkey);
var asymmetricKeyParameter = PrivateKeyFactory.CreateKey(keyBytes);
rsaPrivateCrtKeyParameters1 = (RsaPrivateCrtKeyParameters)asymmetricKeyParameter;
RSAParameters r = DotNetUtilities.ToRSAParameters(rsaPrivateCrtKeyParameters1);
return r;
}
}
}
Code Done in : .NET Framework 4.6.1
Nuget Packages :
Bounty Castle - Install-Package BouncyCastle -Version 1.8.6.1

I thought I would add my findings as I had to do this last night. The examples in this page really helped but they didn't work straightaway.
In my specific case, I was trying to generate a JWT token for DocuSign, for some other reason I can't use their SDK and generating the JWT token manually was the right approach for my use case.
var privateKeybyteArray = Encoding.ASCII.GetBytes(#"-----BEGIN RSA PRIVATE KEY-----
xxxxxxxxxxxxxxxxxx
-----END RSA PRIVATE KEY-----");
var payload = new Dictionary<string, object>
{
{ "iss", "3a31fd58-xxxx-xxxx-xxxx-17639ade3c1b" },
{ "sub", "40a3a606-xxxx-xxxx-xxxx-762c6e7dadb6" },
{ "aud", "account-d.docusign.com" },
{ "iat", DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds },
{ "exp", DateTime.UtcNow.AddHours(1).Subtract(new DateTime(1970, 1, 1)).TotalSeconds },
{ "scope", "signature" }
};
var rsaPrivateKey = new RSAParameters();
using (var ms = new MemoryStream(privateKeybyteArray))
{
using (var sr = new StreamReader(ms))
{
var pemReader = new PemReader(sr);
var keyPair = pemReader.ReadObject() as AsymmetricCipherKeyPair;
rsaPrivateKey = DotNetUtilities.ToRSAParameters(keyPair.Private as RsaPrivateCrtKeyParameters);
}
}
var csprivate = new RSACryptoServiceProvider();
csprivate.ImportParameters(rsaPrivateKey);
var algorithm = new RS256Algorithm(csprivate, csprivate);
var serializer = new JsonNetSerializer();
var urlEncoder = new JwtBase64UrlEncoder();
var encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
var token = encoder.Encode(payload, privateKeybyteArray);
I used the JWT-dotnet package. I found the website jsonwebtoken.io really good as it generates the .NET code needed for generating the token, it didn't quite work but it helped for figuring out what I was doing wrong

NET 5.0 or later has a method called "ImportFromPem", so you can read private-key in PEM format as it is.
void GetToken(System.IO.FileInfo privateKey, string service_account, string client_id)
{
using (var rsa = System.Security.Cryptography.RSA.Create())
{
rsa.ImportFromPem(System.IO.File.ReadAllText(privateKey.FullName));
var descriptor = new Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor
{
Issuer = client_id,
Claims = new System.Collections.Generic.Dictionary<string, object>() { ["sub"] = service_account },
SigningCredentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(new Microsoft.IdentityModel.Tokens.RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256),
IssuedAt = System.DateTime.UtcNow,
Expires = System.DateTime.UtcNow.AddMinutes(60),
};
var handler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();
string token = handler.WriteToken(handler.CreateJwtSecurityToken(descriptor));
}
}
Nuget: 「System.IdentityModel.Tokens.Jwt」
And, maybe error: cannot access a disposed object. object name 'rsa' -> Ref
var signingCredentials = new SigningCredentials(rsaSecurityKey, SecurityAlgorithms.RsaSha256)
{
CryptoProviderFactory = new CryptoProviderFactory { CacheSignatureProviders = false }
};

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);

RS256 is a Signature Algorithm not an Encryption Algorithm
Encryption is done with the public key
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);

Related

Signing data with RSA512 v2 [duplicate]

I am using the jose-jwt library and want to create a signed JWT in C# using the RS256 algorithm for encryption. I have no experience with cryptography, so please excuse my ignorance. I see the following example in the docs:
var payload = new Dictionary<string, object>()
{
{ "sub", "mr.x#contoso.com" },
{ "exp", 1300819380 }
};
var privateKey=new X509Certificate2("my-key.p12", "password", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet).PrivateKey as RSACryptoServiceProvider;
string token=Jose.JWT.Encode(payload, privateKey, JwsAlgorithm.RS256);
which shows the use of a p12 file, but how do I use an RSA key file of the form below? I am looking at the docs for X509Certificate2, but I see no option for RSA private keys. It appears to only accept PKCS7, which I understand to be public keys.
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp
wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5
1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh
3tx4VgMtrQ+WEgCjhoTwo23KMBAuJGSYnRmoBZM3lMfTKevIkAidPExvYCdm5dYq3XToLkkLv5L2
pIIVOFMDG+KESnAFV7l2c+cnzRMW0+b6f8mR1CJzZuxVLL6Q02fvLi55/mbSYxECQQDeAw6fiIQX
GukBI4eMZZt4nscy2o12KyYner3VpoeE+Np2q+Z3pvAMd/aNzQ/W9WaI+NRfcxUJrmfPwIGm63il
AkEAxCL5HQb2bQr4ByorcMWm/hEP2MZzROV73yF41hPsRC9m66KrheO9HPTJuo3/9s5p+sqGxOlF
L0NDt4SkosjgGwJAFklyR1uZ/wPJjj611cdBcztlPdqoxssQGnh85BzCj/u3WqBpE2vjvyyvyI5k
X6zk7S0ljKtt2jny2+00VsBerQJBAJGC1Mg5Oydo5NwD6BiROrPxGo2bpTbu/fhrT8ebHkTz2epl
U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ
37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0=
-----END RSA PRIVATE KEY-----
Finally, what is the difference between the two options listed in the docs, and how do I choose between the two?
-------------------------- OPTION 1 --------------------------
RS-* and PS-* family
CLR:
RS256, RS384, RS512 and PS256, PS384, PS512 signatures require
RSACryptoServiceProvider (usually private) key of corresponding
length. CSP need to be forced to use Microsoft Enhanced RSA and AES
Cryptographic Provider. Which usually can be done be re-importing
RSAParameters. See http://clrsecurity.codeplex.com/discussions/243156
for details.
-------------------------- OPTION 2 --------------------------
CORECLR: RS256, RS384, RS512 signatures require RSA (usually private) key of corresponding length.
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:
https://github.com/dvsekhvalnov/jose-jwt
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
The key to this question is using JWT and Bouncy castle libraries for encoding the token and signing it respectively.
JWT for encoding and decoding JWT tokens
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-----
I used http://travistidwell.com/jsencrypt/demo/
to create 2048 bit keys
Using .NET 6:
string CreateRsaToken(string someClaimValue)
{
var rsaPrivateKey = #"-----BEGIN RSA PRIVATE KEY-----
MIIEogIBA...";
using var rsa = RSA.Create();
rsa.ImportFromPem(rsaPrivateKey);
var signingCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256)
{
CryptoProviderFactory = new CryptoProviderFactory { CacheSignatureProviders = false }
};
var now = DateTime.Now;
var jwt = new JwtSecurityToken(
audience: "all",
issuer: "TheBitCoinKing",
claims: new[] {
new Claim("SomeClaim", someClaimValue),
},
notBefore: now,
expires: now.AddMinutes(1),
signingCredentials: signingCredentials
);
return new JwtSecurityTokenHandler().WriteToken(jwt);
}
packages:
Microsoft.IdentityModel.Tokens
System.IdentityModel.Tokens.Jwt
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;
}
Using BouncyCastle and Jose nuget package, the following code works for me.
public static string CreateToken(Dictionary<string, object> payload)
{
string jwt = string.Empty;
RsaPrivateCrtKeyParameters keyPair;
var cert = ConfigurationManager.AppSettings["cert"];
/// cert begins -----BEGIN PRIVATE KEY----- and ends with -END PRIVATE KEY-----";
using (var sr = new StringReader(cert))
{
PemReader pr = new PemReader(sr);
keyPair = (RsaPrivateCrtKeyParameters)pr.ReadObject();
}
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters(keyPair);
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(rsaParams);
jwt = Jose.JWT.Encode(payload, rsa, Jose.JwsAlgorithm.RS256);
}
return jwt;
}
The GetRSAPrivateKey is only available in .NET 4.6. See the URL below.
https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.rsacertificateextensions.getrsaprivatekey(v=vs.110).aspx
Posting the code to create RS256 JWT token for GCP OAuth Token API call, using the Service Account JSON Key-:
using JWT;
using JWT.Algorithms;
using JWT.Serializers;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
namespace GCP
{
class JWTTokenGenerationForGCPOAuthTokenAPI
{
public static string GenerateJWTToken()
{
var rsaParams = ReadAsymmetricKeyParameter();
var encoder = GetRS256JWTEncoder(rsaParams);
var iat = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var exp = DateTimeOffset.UtcNow.AddMinutes(60).ToUnixTimeSeconds();
// create the payload according to your need
// iss is the Service Account Email ID
var payload = new Dictionary<string, object>
{
{ "iss", "<service-account>#<project-id>.iam.gserviceaccount.com"},
{ "scope", "https://www.googleapis.com/auth/cloud-platform" },
{ "aud", "https://oauth2.googleapis.com/token" },
{ "exp", exp},
{ "iat", iat}
};
//Final token
var token = encoder.Encode(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;
}
public static RSAParameters ReadAsymmetricKeyParameter()
{
\\ This key is fetched from the GCP Service Account JSON File.
\\"private_key": "-----BEGIN PRIVATE KEY-----\n<long-code>-----END PRIVATE KEY-----\n",
\\ pick <long-code> from above. Replace all \n with actual new line like shown below.
string pkey = #"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDSoGKK/Dzb8MBy
################################################################
################################################################
################################################################
################################################################
twySMqKKWnIC/zZljrvp4w==";
RsaPrivateCrtKeyParameters rsaPrivateCrtKeyParameters1;
var keyBytes = Convert.FromBase64String(pkey);
var asymmetricKeyParameter = PrivateKeyFactory.CreateKey(keyBytes);
rsaPrivateCrtKeyParameters1 = (RsaPrivateCrtKeyParameters)asymmetricKeyParameter;
RSAParameters r = DotNetUtilities.ToRSAParameters(rsaPrivateCrtKeyParameters1);
return r;
}
}
}
Code Done in : .NET Framework 4.6.1
Nuget Packages :
Bounty Castle - Install-Package BouncyCastle -Version 1.8.6.1
I thought I would add my findings as I had to do this last night. The examples in this page really helped but they didn't work straightaway.
In my specific case, I was trying to generate a JWT token for DocuSign, for some other reason I can't use their SDK and generating the JWT token manually was the right approach for my use case.
var privateKeybyteArray = Encoding.ASCII.GetBytes(#"-----BEGIN RSA PRIVATE KEY-----
xxxxxxxxxxxxxxxxxx
-----END RSA PRIVATE KEY-----");
var payload = new Dictionary<string, object>
{
{ "iss", "3a31fd58-xxxx-xxxx-xxxx-17639ade3c1b" },
{ "sub", "40a3a606-xxxx-xxxx-xxxx-762c6e7dadb6" },
{ "aud", "account-d.docusign.com" },
{ "iat", DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds },
{ "exp", DateTime.UtcNow.AddHours(1).Subtract(new DateTime(1970, 1, 1)).TotalSeconds },
{ "scope", "signature" }
};
var rsaPrivateKey = new RSAParameters();
using (var ms = new MemoryStream(privateKeybyteArray))
{
using (var sr = new StreamReader(ms))
{
var pemReader = new PemReader(sr);
var keyPair = pemReader.ReadObject() as AsymmetricCipherKeyPair;
rsaPrivateKey = DotNetUtilities.ToRSAParameters(keyPair.Private as RsaPrivateCrtKeyParameters);
}
}
var csprivate = new RSACryptoServiceProvider();
csprivate.ImportParameters(rsaPrivateKey);
var algorithm = new RS256Algorithm(csprivate, csprivate);
var serializer = new JsonNetSerializer();
var urlEncoder = new JwtBase64UrlEncoder();
var encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
var token = encoder.Encode(payload, privateKeybyteArray);
I used the JWT-dotnet package. I found the website jsonwebtoken.io really good as it generates the .NET code needed for generating the token, it didn't quite work but it helped for figuring out what I was doing wrong
NET 5.0 or later has a method called "ImportFromPem", so you can read private-key in PEM format as it is.
void GetToken(System.IO.FileInfo privateKey, string service_account, string client_id)
{
using (var rsa = System.Security.Cryptography.RSA.Create())
{
rsa.ImportFromPem(System.IO.File.ReadAllText(privateKey.FullName));
var descriptor = new Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor
{
Issuer = client_id,
Claims = new System.Collections.Generic.Dictionary<string, object>() { ["sub"] = service_account },
SigningCredentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(new Microsoft.IdentityModel.Tokens.RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256),
IssuedAt = System.DateTime.UtcNow,
Expires = System.DateTime.UtcNow.AddMinutes(60),
};
var handler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();
string token = handler.WriteToken(handler.CreateJwtSecurityToken(descriptor));
}
}
Nuget: 「System.IdentityModel.Tokens.Jwt」
And, maybe error: cannot access a disposed object. object name 'rsa' -> Ref
var signingCredentials = new SigningCredentials(rsaSecurityKey, SecurityAlgorithms.RsaSha256)
{
CryptoProviderFactory = new CryptoProviderFactory { CacheSignatureProviders = false }
};
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);
RS256 is a Signature Algorithm not an Encryption Algorithm
Encryption is done with the public key
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);

Sign JWT token using Azure Key Vault

I'm using a private key to sign a JWT token, which works as expected. However, I'd like to leverage Azure Key Vault to do the signing for me, so that the private key doesn't leave KeyVault. I'm struggling to get this to work, but not sure why.
Here's the code that doesn't use KeyVault and does work...
var handler = new JwtSecurityTokenHandler();
var expiryTime = DateTimeOffset.UtcNow.AddMinutes(10).ToUnixTimeSeconds();
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Iss, clientId),
new Claim(JwtRegisteredClaimNames.Sub, integrationUser),
new Claim(JwtRegisteredClaimNames.Aud, "https://test.example.com"),
new Claim(JwtRegisteredClaimNames.Exp, expiryTime.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) // Add JTI for additional security against replay attacks
};
var privateKey = File.ReadAllText(#"selfsigned.key")
.Replace("-----BEGIN PRIVATE KEY-----", "")
.Replace("-----END PRIVATE KEY-----", "");
var privateKeyRaw = Convert.FromBase64String(privateKey);
var provider = new RSACryptoServiceProvider();
provider.ImportPkcs8PrivateKey(new ReadOnlySpan<byte>(privateKeyRaw), out _);
var rsaSecurityKey = new RsaSecurityKey(provider);
var token = new JwtSecurityToken
(
new JwtHeader(new SigningCredentials(rsaSecurityKey, SecurityAlgorithms.RsaSha256)),
new JwtPayload(claims)
);
var token = handler.WriteToken(token);
This works, and if I copy the JWT into jwt.io, and also paste the public key - it says that the signature is verified...
The token also works against the API I'm calling too.
However, if signing with KeyVault...
var handler = new JwtSecurityTokenHandler();
var expiryTime = DateTimeOffset.UtcNow.AddMinutes(10).ToUnixTimeSeconds();
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Iss, clientId),
new Claim(JwtRegisteredClaimNames.Sub, integrationUser),
new Claim(JwtRegisteredClaimNames.Aud, "https://test.example.com"),
new Claim(JwtRegisteredClaimNames.Exp, expiryTime.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) // Add JTI for additional security against replay attacks
};
var header = #"{""alg"":""RS256"",""typ"":""JWT""}";
var payload = JsonConvert.SerializeObject(new JwtPayload(claims));
var headerAndPayload = $"{Base64UrlEncoder.Encode(header)}.{Base64UrlEncoder.Encode(payload)}";
// Sign token
var credential = new InteractiveBrowserCredential();
var client = new KeyClient(vaultUri: new Uri(kvUri), credential);
var key = (KeyVaultKey)client.GetKey("dan-test");
var cryptoClient = new CryptographyClient(keyId: key.Id, credential);
var digest = new SHA256CryptoServiceProvider().ComputeHash(Encoding.Unicode.GetBytes(headerAndPayload));
var signature = await cryptoClient.SignAsync(SignatureAlgorithm.RS256, digest);
var token = $"{headerAndPayload}.{Base64UrlEncoder.Encode(signature.Signature)}";
(uses Azure.Security.KeyVault.Keys and Azure.Identity nuget packages)
This doesn't work. The first two parts of the token - ie. header and payload are identical to the JWT that does work. The only thing that's different is the signature at the end.
I'm out of ideas! Note that this is closely related to this Stackoverflow question, where the answers seem to suggest what I'm doing should be correct.
Your code is mostly correct, though you should use either Encoding.UTF8 or Encoding.ASCII (since the base64url characters are all valid ASCII and you eliminate any BOM concerns) to get the bytes for headerAndPayload.
I was able to get this to work and found that https://jwt.io is rather vague when it says you can paste either a public key or certificate. It has to be PEM-encoded, and if posting an RSA public key you have to use the less-common "BEGIN RSA PUBLIC KEY" label instead of the more-common "BEGIN PUBLIC KEY".
I tried a few things that all should've worked, and when I found that using a certificate from Key Vault did with "BEGIN CERTIFICATE", I went back to trying "BEGIN PUBLIC KEY". It wasn't until, on a whim, when I changed it to "BEGIN RSA PUBLIC KEY" the JWT was successfully verified.
Below is the code I tried using certificate URI:
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
using Azure.Identity;
using Azure.Security.KeyVault.Certificates;
using Azure.Security.KeyVault.Keys;
using Azure.Security.KeyVault.Keys.Cryptography;
using Microsoft.IdentityModel.Tokens;
var arg = args.Length > 0 ? args[0] : throw new Exception("Key Vault key URI required");
var uri = new Uri(arg, UriKind.Absolute);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Iss, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Aud, "https://test.example.com"),
new Claim(JwtRegisteredClaimNames.Exp, DateTimeOffset.Now.AddMinutes(10).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
var header = #"{""alg"":""RS256"",""typ"":""JWT""}";
var payload = JsonSerializer.Serialize(new JwtPayload(claims));
var headerAndPayload = $"{Base64UrlEncoder.Encode(header)}.{Base64UrlEncoder.Encode(payload)}";
var id = new KeyVaultKeyIdentifier(uri);
var credential = new DefaultAzureCredential();
var certClient = new CertificateClient(id.VaultUri, credential);
KeyVaultCertificate cert = await certClient.GetCertificateAsync(id.Name);
using X509Certificate2 pfx = await certClient.DownloadCertificateAsync(id.Name, id.Version);
var pem = PemEncoding.Write("CERTIFICATE".AsSpan(), pfx.RawData);
Console.WriteLine($"Certificate (PEM):\n");
Console.WriteLine(pem);
Console.WriteLine();
using var rsaKey = pfx.GetRSAPublicKey();
var pubkey = rsaKey.ExportRSAPublicKey();
pem = PemEncoding.Write("RSA PUBLIC KEY".AsSpan(), pubkey.AsSpan());
Console.WriteLine($"Public key (PEM):\n");
Console.WriteLine(pem);
Console.WriteLine();
var cryptoClient = new CryptographyClient(cert.KeyId, credential);
using var sha256 = SHA256.Create();
var digest = sha256.ComputeHash(Encoding.ASCII.GetBytes(headerAndPayload));
var signature = (await cryptoClient.SignAsync(SignatureAlgorithm.RS256, digest)).Signature;
var token = $"{headerAndPayload}.{Base64UrlEncoder.Encode(signature)}";
Console.WriteLine($"JWT:\n\n{token}");
For using only a key, the following should work:
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Azure.Identity;
using Azure.Security.KeyVault.Keys;
using Azure.Security.KeyVault.Keys.Cryptography;
using Microsoft.IdentityModel.Tokens;
var arg = args.Length > 0 ? args[0] : throw new Exception("Key Vault key URI required");
var uri = new Uri(arg, UriKind.Absolute);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Iss, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Aud, "https://test.example.com"),
new Claim(JwtRegisteredClaimNames.Exp, DateTimeOffset.Now.AddMinutes(10).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
var header = #"{""alg"":""RS256"",""typ"":""JWT""}";
var payload = JsonSerializer.Serialize(new JwtPayload(claims));
var headerAndPayload = $"{Base64UrlEncoder.Encode(header)}.{Base64UrlEncoder.Encode(payload)}";
var id = new KeyVaultKeyIdentifier(uri);
var credential = new DefaultAzureCredential();
var keyClient = new KeyClient(id.VaultUri, credential);
KeyVaultKey key = await keyClient.GetKeyAsync(id.Name, id.Version);
using var rsaKey = key.Key.ToRSA();
var pubkey = rsaKey.ExportRSAPublicKey();
var pem = PemEncoding.Write("RSA PUBLIC KEY".AsSpan(), pubkey.AsSpan());
Console.WriteLine($"Public key (PEM):\n");
Console.WriteLine(pem);
Console.WriteLine();
var cryptoClient = new CryptographyClient(key.Id, credential);
using var sha256 = SHA256.Create();
var digest = sha256.ComputeHash(Encoding.ASCII.GetBytes(headerAndPayload));
var signature = (await cryptoClient.SignAsync(SignatureAlgorithm.RS256, digest)).Signature;
var token = $"{headerAndPayload}.{Base64UrlEncoder.Encode(signature)}";
Console.WriteLine($"JWT:\n\n{token}");
To generate token you can created your own implementation of CryptoProviderFactory in SigningCredentials.
var credentials = new SigningCredentials(new RsaSecurityKey(RSA.Create()), algorithm: SecurityAlgorithms.RsaSha256);
credentials.CryptoProviderFactory = _cryptoProviderFactory;
var descriptor = new SecurityTokenDescriptor
{
Subject = _owinContext.Request.User.Identity as ClaimsIdentity,
Expires = DateTime.UtcNow.AddHours(4),
SigningCredentials = credentials,
Audience = _configuration.AccessTokenAudience,
Issuer = _configuration.AccessTokenIssuer,
IssuedAt = DateTime.UtcNow,
};
var token = tokenHandler.CreateToken(descriptor);
SignatureProviderFactory implementation:
public class CustomCryptoProviderFactory : CryptoProviderFactory
{
private readonly CryptographyClient _cryptoClient;
public CustomCryptoProviderFactory()
{
var client = new KeyClient(new Uri("{url}"), new DefaultAzureCredential());
var key = client.GetKey("{key-name}");
_cryptoClient = new CryptographyClient(new Uri(key.Value.Key.Id), new DefaultAzureCredential());
}
public override SignatureProvider CreateForSigning(SecurityKey key, string algorithm)
{
return new CustomSignatureProvider(_cryptoClient, key, algorithm);
}
public override SignatureProvider CreateForSigning(SecurityKey key, string algorithm, bool cacheProvider)
{
return new CustomSignatureProvider(_cryptoClient, key, algorithm);
}
public override SignatureProvider CreateForVerifying(SecurityKey key, string algorithm)
{
return new CustomSignatureProvider(_cryptoClient, key, algorithm;
}
public override SignatureProvider CreateForVerifying(SecurityKey key, string algorithm, bool cacheProvider)
{
return new CustomSignatureProvider(_cryptoClient, key, algorithm);
}
}
CustomSignatureProvider implementation
public class CustomSignatureProvider : SignatureProvider
{
private readonly CryptographyClient _cryptoClient;
public CustomSignatureProvider(CryptographyClient cryptoClient,
SecurityKey key,
string algorithm)
: base(key, algorithm)
{
_cryptoClient = cryptoClient;
}
public override byte[] Sign(byte[] input)
{
var result = _cryptoClient.Sign(SignatureAlgorithm.RS256, GetSHA256(input));
return result.Signature;
}
public override bool Verify(byte[] input, byte[] signature)
{
var verificationResult = _cryptoClient.Verify(SignatureAlgorithm.RS256,
GetSHA256(input),
signature);
return verificationResult.IsValid;
}
protected override void Dispose(bool disposing)
{
}
private byte[] GetSHA256(byte[] input)
{
var sha = SHA256.Create();
return sha.ComputeHash(input);
}
}

How to generate JWT token signed with RS256 algorithm in C#

I have a RSA Private key with me and I have to generate a JWT token using RS256 algorithm.
I started with the below code which was working for "HmacSha256" algorithm
but when i change it to RS256 it throws errors like " IDX10634: Unable to create the SignatureProvider.Algorithm: 'System.String',SecurityKey:'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey'"
After that I modified the code with RSACryptoServiceProvider() class. But i didnt get a solution.
Please anyone can help with a sample code using RSACryptoServiceProvider class with a private key.
public static string CreateToken()//Dictionary<string, object> payload
{
string key = GetConfiguration["privateKey"].Tostring();
var securityKey =
new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(
Encoding.UTF8.GetBytes(key));
var credentials =
new Microsoft.IdentityModel.Tokens.SigningCredentials(securityKey, "RS256");
var header = new JwtHeader(credentials);
JwtPayload payload = new JwtPayload();
payload.AddClaim(
new System.Security.Claims.Claim(
"context", "{'user': { 'name': 'username', 'email': 'email' }}",
JsonClaimValueTypes.Json));
payload.AddClaim(new System.Security.Claims.Claim("iss", #"app-key"));
payload.AddClaim(new System.Security.Claims.Claim("aud", "meet.jitsi.com"));
payload.AddClaim(new System.Security.Claims.Claim("sub", "meet.jitsi.com"));
payload.AddClaim(new System.Security.Claims.Claim("room", "TestRoom"));
var secToken = new JwtSecurityToken(header, payload);
var handler = new JwtSecurityTokenHandler();
var tokenString = handler.WriteToken(secToken);
return tokenString;
}

How to Encryption JWT Payload with Public Key RSA_OAEP_256

I'm using "BouncyCastle.NetCore" and "jose-jwt" libraries to sign and encrypt a web token. I'm able to sign with my private key by below code. but the requirements is to also perform OpenID JWT Encryption In order to encrypt the JWT payload, we need to do that using provided public key string (base64 decoded with X509 key spec).
the encode needed to use RSA algorithm and JWE header should include header name “alg" with the value: RSA_OAEP_256.
Below code is sign only with private key but not sure how to complete the encode??
class Program
{
string publicKeyString = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ51s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQAB";
public static async Task Main(string[] args)
{
var payload = new System.Collections.Generic.Dictionary<string, object>()
{
{ "sub", "Testmr.x#contoso.com" },
{ "iss", "https://www.YourBrand.com" },
{ "exp", 1300819380 },
{ "iat", 1446111752 },
{ "preferred_username", "JohnDoe2" },
{ "phone_number", "+2-10-344-3765333" }
};
var token = CreateToken(payload);
Console.WriteLine($"token={token}");
}
public static string CreateToken(object payload)
{
string jwt = string.Empty;
var fileStream = System.IO.File.OpenText("C:\\temp\\my_private_key.pem");
var pemReader = new Org.BouncyCastle.OpenSsl.PemReader(fileStream, new MyPasswordFinder());
var keyPair = (Org.BouncyCastle.Crypto.AsymmetricKeyParameter)pemReader.ReadObject();
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)keyPair);
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(rsaParams);
jwt = JWT.Encode(payload, rsa, JwsAlgorithm.RS256);//,options: new JwtOptions { EncodePayload = true }
}
return jwt;
}
}
The only difference EncodePayload seems to make is how payload ends up being written out to JWT body. It is true by default and does not affect payload encryption (see source code):
return jwtOptions.EncodePayload
? Compact.Serialize(headerBytes, payloadBytes, signature)
: Compact.Serialize(headerBytes, Encoding.UTF8.GetString(payloadBytes), signature);
What you probably want is to specify JweAlgorithm and correct JweEncryption (note, it's a different overload to the same function:
jwt = JWT.Encode(payload, rsa, JweAlgorithm.RSA_OAEP_256, JweEncryption.A256GCM);

Using private key to sign a JWT token

I'm trying to load a private key to sign a JWT token. I got following code and failed with exceptions:
string key =#"-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCTLOQaZ3D0ayC1BSW4LCs3gYmu
eYiWDGRT491PJt/4
-----END PRIVATE KEY-----";
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.RsaSha512);
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.RsaSha512);
var header = new JwtHeader(signingCredentials);
var t = DateTime.UtcNow - new DateTime(1970, 1, 1);
int iat = (int)t.TotalSeconds;
var payload = new JwtPayload
{
{ "iss", "1234-5678-9012-1221-11111"},
{ "iat", iat },
{ "exp", iat + 900},
{ "sub", "aaaaaaaaaaa" }
};
var secToken = new JwtSecurityToken(header, payload);
var tokenString = new JwtSecurityTokenHandler().WriteToken(secToken);
===============Answer===============================
Just for anyone facing the same problem, here is the solution. You need read the post down the bottom to get it work:
http://www.donaldsbaconbytes.com/2016/08/create-jwt-with-a-private-rsa-key/
Your problem is with below line
var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.RsaSha512);
you are trying to use symmetric key with asymmetric algorithm (RSA algorithm).
You can look for other symmetric algorithm to generate signingCredentials something like below
var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256Signature);

Categories