Last Updated on February 11, 2024


In a previous article, I talked about different ways to create RSA keys in .NET.  Since then, I received some questions about how to convert those RSA keys to JSON Web Keys (JWK).

Basically, .NET RSA classes give the ability to export the raw RSA key into a structure (RSAParameters) containing the key properties. This structure is useful to manipulate RSA key properties under the scope of .NET processes or services.  However, if your goal is to send a public key to a third party in the network or to share the key with non-.NET systems, you might need to use a standard key format like the JSON Web Key (JWK) data structure.

In this article, I’ll provide code samples to generate RSA Keys in a JWK format with .NET.

What is a JSON Web Key (JWK)?

The term JSON stands for JavaScript Object Notation and a JSON Web Key (JWK) is a standard model or data structure used to represent, serialize and deserialize cryptographic keys in JSON format.

Here is a simple definition of a JWK as stated in the standard RFC7517:

A JSON WEB KEY (JWK) IS A JAVASCRIPT OBJECT NOTATION (JSON) [RFC7159]
   DATA STRUCTURE THAT REPRESENTS A CRYPTOGRAPHIC KEY.  THIS
   SPECIFICATION ALSO DEFINES A JWK SET JSON DATA STRUCTURE THAT
   REPRESENTS A SET OF JWKS

 

While a JWK represents a cryptographic key, a JWK Set is a data structure representing a group of JKWs. A JWK Set is a way of grouping many JWKs into a single JSON where each JWK is identified by a unique identifier. Then, the next question is:  why would someone need to bundle JWKs into a JWK Set? Here is a valid use-case: “Let’s say I am coding a system that generates JSON Web Tokens (JWT) for third parties, and I have a pool of 3 different private keys available to sign the token. I can then expose an endpoint to share the 3 public keys of the keys pool into a single JWK Set to allow third-party systems to validate the signature by picking the key corresponding to the key Id used to sign the token“.

Now, let’s look at the parameters of an RSA JWK.

Example of RSA JWK

The following JWK is an RSA private key:

{
  "kty": "RSA",
  "alg": "RSA-OAEP",
  "kid": "my_key_id",
  "use": "enc",
  "n": "vN1ozNsgFps1ThDp_6o9_vpAMF_MXtwgfGKWmLoLtmBHfHkvB0M5CFFFOdOiDibQnEEpUNyw4cFJq7A2uRuARvOWcHXImTD0teqQMihreoeraQVvqn3sQZgPfmtIxjAOR7jPXYs8jzlGTi4C4Hxd6R4DIFUYKEPbsU55ZGgrt8YDFLcX_-lPw7c8NrPJWTJROqMmAo-_tE-4Fn_qhaRwlYWUasU7X5_gTFPkKtrKf8E6KK9zWdretzQtRIn4i05ZzZCcaUTZZ6TjHnqyHy5feRZ-qQYQN-82pbHT05FEyY16mb9eBpc-GcIISj7HrTrNP9uVloyY3PqZoQBdm7VduQ",
  "e": "AQAB",
  "d": "VUi9_FOu58ZqibgFkpezqyIiPjXkOam5WbHDQ3Avvck4PrMSU3s9k83vLq-sbCuG5Chgmc0uaqM3uzm1XF1YzbFNa7ckQFjlCAjF2IB8GDaPZrs3s4ttViluRa34jNZCfIytxxYK4LoCT2djkS9X0N7mloEWiYgq-qPvGTIZG8CchFQq9oUV2Pw42tMIVnCw4kFmLobmaauLqN6dHrAhHkPQSal_J__10pVOEDCNCqsg1m0IE8xqBRIF4DKVY4E6g3wlem22N78KGtRPzix62ZL7Z4Iuwzvoe4WwHSbbmOgE8Afv9Ao7Las7fSNdnpXmEAgFaNgmMliBuXzkJ8aPqQ",
  "p": "wViAHb3zBvDP_snNq0mgi3TGkLaNolzSfZTAc2N2DqvFnltXzwPZEByg73cyFKZ5lMY9f9LP16c7c_kUNbde8RwPX876T8c8iJTW19xTbrqMAZu5FDBmKzkyRiQetjEXz6GAZHxmYc93KSFN_CWgn_dI9GP7K2Pb0MCw_dU2jCM",
  "q": "-hEwvPR4qP-XNTLNw-BUTwMRx4WTc1bQlvEBy_ckrat-VZ386LiV9REi1aa2dC6AjXqL2KKHoSpEt9KBkFN4rGKf7_SFmNJGZLqdch-X_qdXkFIgfAac0mfBoN1a_KF1mZlpp-6e1MhTRRfSnapqZ1zmhq9qHHdrLzXkTXKzjnM",
  "dp": "KjhQKa577k0RrlqU7c1zIGDMp3clsInCcSfVap0Sf5uk2LKrlwoJEUqfHguSRQ4hSIqNjWcUecwL4IRIlH5JXi85cDt7T4Z7Qnv1-kcjdO-JeSRvIuh6tv-TadujVTedMnra1ZaQqNUr-TBLnj79N1FBfhSDGXOB4bDiNVukwK8",
  "dq": "0ymi-1yBa0vxNoYBBW-wIcxYviAIxDPCDkPTnZXDzjdrhgbUIvyD_J6MyNdvaKo_-bhK0RNl7m0P_B3xNJuX4hGiNvFHwyWFNdfzzgPpyKssoP0I21KGKJJfCmUBSSVader7MkiorMvArS6RtnvKQCBNtdv4gbNBumsKr2-1Ogs",
  "qi": "QXKtVuNckn4mu6nsc1MF3g6dZEoPkR76AFUO1N-NxgkMVPHXgbiWLIbo1ibrAvJtzMVyYQ11nvVLTfsX_EdtrLQA-XX2BzgiUw7w_zSPSCt-FPN6_Z_upDnQ-IhyLPAruc_pImefBNONg7Zs94MazqeVgsSvgey2Vn4PuDv8tgc"
}

NB: In this example, the JWK contains private key materials. Given that this key is already published here, please do not use it in your code. JWKs containing private keys should not be exposed to third parties and should not be publicly available.

Here is the corresponding RSA public key is:

{
  "kty": "RSA",
  "alg": "RSA-OAEP",
  "kid": "my_key_id",
  "use": "enc",
  "n": "vN1ozNsgFps1ThDp_6o9_vpAMF_MXtwgfGKWmLoLtmBHfHkvB0M5CFFFOdOiDibQnEEpUNyw4cFJq7A2uRuARvOWcHXImTD0teqQMihreoeraQVvqn3sQZgPfmtIxjAOR7jPXYs8jzlGTi4C4Hxd6R4DIFUYKEPbsU55ZGgrt8YDFLcX_-lPw7c8NrPJWTJROqMmAo-_tE-4Fn_qhaRwlYWUasU7X5_gTFPkKtrKf8E6KK9zWdretzQtRIn4i05ZzZCcaUTZZ6TjHnqyHy5feRZ-qQYQN-82pbHT05FEyY16mb9eBpc-GcIISj7HrTrNP9uVloyY3PqZoQBdm7VduQ",
  "e": "AQAB"
}

This RSA JWK contains the following fields:

  • kty:  The key type which corresponds to the cryptographic algorithm family of the key (for example RSA, EC, oct, and OKP).
  • kid: The identifier of the key, used to select a key among a set of JWKs keys.
  • alg: The algorithm for which we intend to use the key. Algorithms are listed in JSON Web Signature and Encryption Algorithms standard (JWA).
  • use: Identifies the purpose of the public key. The public key can be used to sign data (sig) or encrypt data (enc).
  • d, p, q, dp, dq, qi, n, e: properties of the RSA key. The public key contains only the modulus (n) and exponent(e) properties. For more information about RSA properties, read our previous article on the matter.





Generating an RSA JWK in .NET

Let’s go through the step-by-step process to generate an RSA JWK in .NET

Step 1: Create an RSA key with RSA.Create

At first, you need to create an RSA Key with the RSA.Create method.

In this example, we’ll create an interface( IRsaKeyGenerator ) exposing a GenerateKey method that we will use to generate the RSA Key.  That offers the flexibility to register the interface later in a DI container if needed (even though this guide is not using a DI container).

using Microsoft.IdentityModel.Tokens;
using System.Runtime.InteropServices;
using System.Security.Cryptography;

namespace GenerateRsaJwk
{
    public interface IRsaKeyGenerator
    {
        /// <summary>
        /// Generates a new Rsa Key
        /// </summary>
        /// <param name="keySize">Size of the Rsa Key</param>
        /// <param name="keyId">Id or name of the key</param>
        /// <returns></returns>
        RsaSecurityKey GenerateKey(int keySize, [Optional] string keyId);
    }

    /// <summary>
    /// Default Implementation of IRsaKeyGenerator
    /// </summary>
    public sealed class RsaKeyGenerator : IRsaKeyGenerator
    {
        /// <summary>
        /// Generates a new Rsa Key
        /// </summary>
        /// <param name="keySize">Size of the Rsa Key</param>
        /// <param name="keyId">Id or name of the key</param>
        /// <returns></returns>
        public RsaSecurityKey GenerateKey(int keySize, [Optional] string keyId)
        {
            using (RSA rsa = RSA.Create())
            {
                rsa.KeySize = keySize;
                RSAParameters parameters = rsa.ExportParameters(true);
                return new RsaSecurityKey(parameters) { KeyId = String.IsNullOrWhiteSpace(keyId) ? Guid.NewGuid().ToString() : keyId };
        
            }
        }
    }
}

The GenerateKey method returns a RsaSecurityKey object from Microsoft.IdentiyModel.Tokens namespace.

Step 2: Convert the RsaSecurityKey object to a JsonWebKey object

The JsonWebKey class, from the Microsoft.IdentiyModel.Tokens namespace, is a convenient way to work with JSON Web Keys in .NET. Let’s explore the options available to convert an RsaSecurityKey object to a JsonWebKey object.

Option 1: Convert RsaSecurityKey to a JsonWebKey with JsonWebKeyConverter. ConvertFromRsaSecurityKey

You can convert the RsaSecurityKey object to a JsonWebKey by using the ConvertFromRsaSecurityKey method of the JsonWebKeyConverter class.

IRsaKeyGenerator generator = new RsaKeyGenerator();
RsaSecurityKey key = generator.GenerateKey(2048, "my_key_id");
// Use JsonWebKeyConverter to convert the RSA Key in JWK object
JsonWebKey jwk = JsonWebKeyConverter.ConvertFromRSASecurityKey(key);

Option 2: Manually convert RsaSecurityKey  to a JsonWebKey

The second option consists of writing your own method to convert it manually. Here is the code of an extension that converts the RsaSecurityKey object to a JsonWebKey object.

IRsaKeyGenerator generator = new RsaKeyGenerator();
RsaSecurityKey key = generator.GenerateKey(2048, "my_key_id");
//Use the custom ToJwk extension to convert the RSA Key in JWK object
JsonWebKey jwk = key.ToJwk(RsaAlgorithm.Rs256, true);

And this is the source code of the custom ToJwt extension method that I implemented:

public static class RsaSecurityKeyExtensions
{
    /// <summary>
    /// Get the JsonWebKey representation of the RSA key
    /// </summary>
    /// <param name="includePrivateKey">Include private key in JsonWebKey (if the current key contains the private key)</param>
    /// <param name="algorithm">Signature or encryption algorithm for which this key will be used for</param>
    /// <returns></returns>
    public static JsonWebKey ToJwk(this RsaSecurityKey key, RsaAlgorithm algorithm, bool includePrivateKey = true)
    {
        RSAParameters parameters;

        if (key.Rsa != null)
            parameters = key.Rsa.ExportParameters(includePrivateKey);
        else
            parameters = key.Parameters;

        var algorithmInfo = algorithm.GetJsonWebAlgorithm();
        JsonWebKey result = new JsonWebKey()
        {
            Kty = JsonWebAlgorithmsKeyTypes.RSA,
            Kid = key.KeyId,
            Use = algorithmInfo?.PublicKeyUse,
            Alg = algorithmInfo?.Name,
            N = parameters.Modulus == null ? null : Base64UrlEncoder.Encode(parameters.Modulus),
            E = parameters.Exponent == null ? null : Base64UrlEncoder.Encode(parameters.Exponent)
        };

        if (includePrivateKey)
        {
            result.P = parameters.P == null ? null : Base64UrlEncoder.Encode(parameters.P);
            result.Q = parameters.Q == null ? null : Base64UrlEncoder.Encode(parameters.Q);
            result.D = parameters.D == null ? null : Base64UrlEncoder.Encode(parameters.D);
            result.DQ = parameters.DQ == null ? null : Base64UrlEncoder.Encode(parameters.DQ);
            result.DP = parameters.DP == null ? null : Base64UrlEncoder.Encode(parameters.DP);
            result.QI = parameters.InverseQ == null ? null : Base64UrlEncoder.Encode(parameters.InverseQ);
        }
        return result;
    }
}

The whole solution is available under the source code section.





Step 3: Serialize the JsonWebKey object to a JSON string

The goal of this last step of the process is to have the string representation of the JSON Web Key as stated in RFC7517.

Option 1: Serialize the JsonWebKey object with JsonExtensions.SerializeToJson

The JsonExtensions helper class from the System.IdentityModels.Tokens.Jwt namespace offers static methods to serialize and deserialize tokens or objects.

var jwkString = JsonExtensions.SerializeToJson(jwk);

This code outputs the following string:

{"alg":"RS256","d":"bwQSncBiAN52k1yYDcxqoTZQUeAp-nnrcFlEKnctKxHopYaft0YUXcUSuepxzch0CmXVdpH5JAv_GZPUjHdiFQJ6ZlG_-7w3k07MJMkuBrKVJelndX4HE-4Kby3kp4soln5GKgelwW44ZqH1VSsZRx70a07WZQRPQXVTwvHnchWdeVhph6IxQjcVVIeOAWesrG-djKW9RHtw2L9CsjSi7eI_l1gHvgejwyrjTZkC3oUtXCwvbuY7PO3AehjWqiLIkPxF40eJPbjXF3SY1guYEk9O_1AQ8L4STLheCzVqhJDdAvWekWc0M0uaWjQAsDgyDthYSi7QivE956wxDC-i9Q","dp":"CnxsmRElLZX6RB_QGlmQ48vk5hoxKuRZI96W9Lo8Rsu5WP6O2ODrS40PddVI-AnjmP1OTa01FpODg3eGSgzzs9IW-bBrnzICsBEshM2g3u6u2YoW_Tji4-sf8Trcpdx9Q5pLq2Q1PzFpIZ6X-ATd4FytTVndLP5TY65C5iPiIDc","dq":"ixFH6SYsuGY1FTFcU2fDlZrnLablFRYR2kxpkEpPKnv4NBWP_zw87HsT-_dkWIYqd5yhpmwBGisF5PshrZkSuQMVYgML7aB0A_Kbmzklus02ASpv97ZiURDVewKAaSfIcbHMJEyrFH7umTR-vc_54XD2UBp6odsLpXnGKHvT1hU","e":"AQAB","kid":"my_key_id","kty":"RSA","n":"wZa-qzkSmOxiudr7WbEAiRwwotPaZAjJT8L_uAmyfFwXPL9jC_B3lVAkSQc5peXlk6ronlodiQJfFj8Fja_7LhkMi6p2TNmHvrTmAs1Am2raNLK-1vFrlrpJLayHlnJCLdWReioikcEWRdyzvQEPD9om0-5GMwKOY02nVB0yd463UkeXL0pLyCENmPkGCZmpmU5w-FVzxnMVY9ur2K1DRr0ZhliPtAta5HZxSCAFoVttYSbH-nibJbK25qsGIhDjEeD9JdRFATVFlG1ojrHGYH6hsNOVPSynMr19Uzn8hD5cFkF3A23J95P7MCpajs4TpqwcSv3o1JZSBdnw9l6W1Q","p":"xBHBJlOS7xEB1VntUGQnP4cZWBdRqcd4dNeGHzOQw75Gm5Ctf5TKygztu4tX0OE5Zj8zICyb_pVMGs7Tx9nbGVRFhDqGzRC3n98HhXO-A56M-wdqXesehA4DYrvYnK15ww3ASZ0nCCfBs0oaoXnkxfmMTxzjgSsxl_e3SHA04xc","q":"_MLkmStAEk8-LLeF62IYml1S22xKF5Cc4IH61iAO6jnsjBpwk_y8O0vmF_bNEPxtxggozVzYnLu3Phncl44RFRlOWHcIXkIJ4rYPk1LU4FBRNW9uKNqgpcHTL42w74QUWA1GdTUHt_A7d8PZ58RNxAOFCo5EuipTIG1cWrgyOPM","qi":"QCZSMVr1Fnesx3-WEC5UNWaLRQ_JqmqGcLi_XofVcJHl62i386ETuBPuTrMvY13z2Cxj4kjQx-JKC_JdBr1ESD7Est1CauwfNrMiVXvaNtEA4Fl8JeN7Kxl0_K53z1kbuW6jWvxwRcNYvC9I3gvqr9ksFKWl5TFJlSYYlQ0i0q4","use":"sig"}

One of the caveats of using the SerializeToJson object is the fact that you have less control over the output of the serialization. For example, the returned JSON is not indented and you cannot specify the output format when calling the method. However, you can reload the output and then serialize it again with indentation by using an extra 1 liner:

var jwkString = JsonExtensions.SerializeToJson(jwk);
var jwkStringIndented = JValue.Parse(jwkString).ToString(Formatting.Indented)

That code gives a much prettier JSON output:

{
  "alg": "RS256",
  "d": "bwQSncBiAN52k1yYDcxqoTZQUeAp-nnrcFlEKnctKxHopYaft0YUXcUSuepxzch0CmXVdpH5JAv_GZPUjHdiFQJ6ZlG_-7w3k07MJMkuBrKVJelndX4HE-4Kby3kp4soln5GKgelwW44ZqH1VSsZRx70a07WZQRPQXVTwvHnchWdeVhph6IxQjcVVIeOAWesrG-djKW9RHtw2L9CsjSi7eI_l1gHvgejwyrjTZkC3oUtXCwvbuY7PO3AehjWqiLIkPxF40eJPbjXF3SY1guYEk9O_1AQ8L4STLheCzVqhJDdAvWekWc0M0uaWjQAsDgyDthYSi7QivE956wxDC-i9Q",
  "dp": "CnxsmRElLZX6RB_QGlmQ48vk5hoxKuRZI96W9Lo8Rsu5WP6O2ODrS40PddVI-AnjmP1OTa01FpODg3eGSgzzs9IW-bBrnzICsBEshM2g3u6u2YoW_Tji4-sf8Trcpdx9Q5pLq2Q1PzFpIZ6X-ATd4FytTVndLP5TY65C5iPiIDc",
  "dq": "ixFH6SYsuGY1FTFcU2fDlZrnLablFRYR2kxpkEpPKnv4NBWP_zw87HsT-_dkWIYqd5yhpmwBGisF5PshrZkSuQMVYgML7aB0A_Kbmzklus02ASpv97ZiURDVewKAaSfIcbHMJEyrFH7umTR-vc_54XD2UBp6odsLpXnGKHvT1hU",
  "e": "AQAB",
  "kid": "my_key_id",
  "kty": "RSA",
  "n": "wZa-qzkSmOxiudr7WbEAiRwwotPaZAjJT8L_uAmyfFwXPL9jC_B3lVAkSQc5peXlk6ronlodiQJfFj8Fja_7LhkMi6p2TNmHvrTmAs1Am2raNLK-1vFrlrpJLayHlnJCLdWReioikcEWRdyzvQEPD9om0-5GMwKOY02nVB0yd463UkeXL0pLyCENmPkGCZmpmU5w-FVzxnMVY9ur2K1DRr0ZhliPtAta5HZxSCAFoVttYSbH-nibJbK25qsGIhDjEeD9JdRFATVFlG1ojrHGYH6hsNOVPSynMr19Uzn8hD5cFkF3A23J95P7MCpajs4TpqwcSv3o1JZSBdnw9l6W1Q",
  "p": "xBHBJlOS7xEB1VntUGQnP4cZWBdRqcd4dNeGHzOQw75Gm5Ctf5TKygztu4tX0OE5Zj8zICyb_pVMGs7Tx9nbGVRFhDqGzRC3n98HhXO-A56M-wdqXesehA4DYrvYnK15ww3ASZ0nCCfBs0oaoXnkxfmMTxzjgSsxl_e3SHA04xc",
  "q": "_MLkmStAEk8-LLeF62IYml1S22xKF5Cc4IH61iAO6jnsjBpwk_y8O0vmF_bNEPxtxggozVzYnLu3Phncl44RFRlOWHcIXkIJ4rYPk1LU4FBRNW9uKNqgpcHTL42w74QUWA1GdTUHt_A7d8PZ58RNxAOFCo5EuipTIG1cWrgyOPM",
  "qi": "QCZSMVr1Fnesx3-WEC5UNWaLRQ_JqmqGcLi_XofVcJHl62i386ETuBPuTrMvY13z2Cxj4kjQx-JKC_JdBr1ESD7Est1CauwfNrMiVXvaNtEA4Fl8JeN7Kxl0_K53z1kbuW6jWvxwRcNYvC9I3gvqr9ksFKWl5TFJlSYYlQ0i0q4",
  "use": "sig"
}

Option 2: Manually serialize the JsonWebKey object

Serializing the JsonWebKey object manually is not as straightforward as it seems to be. For instance, using the following code to serialize the key gives the following output:

var options = new JsonSerializerOptions { WriteIndented = true };
string jwkString = System.Text.Json.JsonSerializer.Serialize(jwk, options);
{
  "AdditionalData": {},
  "Alg": "RS256",
  "Crv": null,
  "D": "bwQSncBiAN52k1yYDcxqoTZQUeAp-nnrcFlEKnctKxHopYaft0YUXcUSuepxzch0CmXVdpH5JAv_GZPUjHdiFQJ6ZlG_-7w3k07MJMkuBrKVJelndX4HE-4Kby3kp4soln5GKgelwW44ZqH1VSsZRx70a07WZQRPQXVTwvHnchWdeVhph6IxQjcVVIeOAWesrG-djKW9RHtw2L9CsjSi7eI_l1gHvgejwyrjTZkC3oUtXCwvbuY7PO3AehjWqiLIkPxF40eJPbjXF3SY1guYEk9O_1AQ8L4STLheCzVqhJDdAvWekWc0M0uaWjQAsDgyDthYSi7QivE956wxDC-i9Q",
  "DP": "CnxsmRElLZX6RB_QGlmQ48vk5hoxKuRZI96W9Lo8Rsu5WP6O2ODrS40PddVI-AnjmP1OTa01FpODg3eGSgzzs9IW-bBrnzICsBEshM2g3u6u2YoW_Tji4-sf8Trcpdx9Q5pLq2Q1PzFpIZ6X-ATd4FytTVndLP5TY65C5iPiIDc",
  "DQ": "ixFH6SYsuGY1FTFcU2fDlZrnLablFRYR2kxpkEpPKnv4NBWP_zw87HsT-_dkWIYqd5yhpmwBGisF5PshrZkSuQMVYgML7aB0A_Kbmzklus02ASpv97ZiURDVewKAaSfIcbHMJEyrFH7umTR-vc_54XD2UBp6odsLpXnGKHvT1hU",
  "E": "AQAB",
  "K": null,
  "KeyId": "my_key_id",
  "KeyOps": [],
  "Kid": "my_key_id",
  "Kty": "RSA",
  "N": "wZa-qzkSmOxiudr7WbEAiRwwotPaZAjJT8L_uAmyfFwXPL9jC_B3lVAkSQc5peXlk6ronlodiQJfFj8Fja_7LhkMi6p2TNmHvrTmAs1Am2raNLK-1vFrlrpJLayHlnJCLdWReioikcEWRdyzvQEPD9om0-5GMwKOY02nVB0yd463UkeXL0pLyCENmPkGCZmpmU5w-FVzxnMVY9ur2K1DRr0ZhliPtAta5HZxSCAFoVttYSbH-nibJbK25qsGIhDjEeD9JdRFATVFlG1ojrHGYH6hsNOVPSynMr19Uzn8hD5cFkF3A23J95P7MCpajs4TpqwcSv3o1JZSBdnw9l6W1Q",
  "Oth": null,
  "P": "xBHBJlOS7xEB1VntUGQnP4cZWBdRqcd4dNeGHzOQw75Gm5Ctf5TKygztu4tX0OE5Zj8zICyb_pVMGs7Tx9nbGVRFhDqGzRC3n98HhXO-A56M-wdqXesehA4DYrvYnK15ww3ASZ0nCCfBs0oaoXnkxfmMTxzjgSsxl_e3SHA04xc",
  "Q": "_MLkmStAEk8-LLeF62IYml1S22xKF5Cc4IH61iAO6jnsjBpwk_y8O0vmF_bNEPxtxggozVzYnLu3Phncl44RFRlOWHcIXkIJ4rYPk1LU4FBRNW9uKNqgpcHTL42w74QUWA1GdTUHt_A7d8PZ58RNxAOFCo5EuipTIG1cWrgyOPM",
  "QI": "QCZSMVr1Fnesx3-WEC5UNWaLRQ_JqmqGcLi_XofVcJHl62i386ETuBPuTrMvY13z2Cxj4kjQx-JKC_JdBr1ESD7Est1CauwfNrMiVXvaNtEA4Fl8JeN7Kxl0_K53z1kbuW6jWvxwRcNYvC9I3gvqr9ksFKWl5TFJlSYYlQ0i0q4",
  "Use": "sig",
  "X": null,
  "X5c": [],
  "X5t": null,
  "X5tS256": null,
  "X5u": null,
  "Y": null,
  "KeySize": 2048,
  "HasPrivateKey": true,
  "CryptoProviderFactory": {
    "CryptoProviderCache": {},
    "CustomCryptoProvider": null,
    "CacheSignatureProviders": true,
    "SignatureProviderObjectPoolCacheSize": 48
  }
}

As you can see here, the output contains null properties and properties with default values. In addition, you end up having a JWK with properties not defined in the RFC (like AdditionalData, KeySize …)

Basically, you need to write more code in order to have more control over the output of the serialized key.

Here is a code sample where we are using the Newtonsoft.Json package to manually serialize the JsonWebKey object to JSON string. For more details, see the source code section.

public static string SerializeToJson(this JsonWebKey jwk, bool indented = true)
{
    //JsonWebKeySerializationWrapper is a custom wrapper class
    JsonWebKeySerializationWrapper wrapper = new JsonWebKeySerializationWrapper()
    {
        Kty = jwk.Kty,
        Kid = jwk.Kid,
        Alg = jwk.Alg,
        KeyOps = jwk.KeyOps,
        K = jwk.K,
        E = jwk.E,
        N = jwk.N,
        Oth = jwk.Oth,
        P = jwk.P,
        Q = jwk.Q,
        QI = jwk.QI,
        DQ = jwk.DQ,
        DP = jwk.DP,
        D = jwk.D,
        Crv = jwk.Crv,
        Use = jwk.Use,
        X = jwk.X,
        Y = jwk.Y,
        X5c = jwk.X5c,
        X5t = jwk.X5t,
        X5tS256 = jwk.X5tS256,
        X5u = jwk.X5u
    };
    return JsonConvert.SerializeObject(wrapper, indented ? IndentedSettings : Settings);
}

That gives the following output:

{
  "kty": "RSA",
  "alg": "RS256",
  "kid": "my_key_id",
  "use": "sig",
  "n": "wZa-qzkSmOxiudr7WbEAiRwwotPaZAjJT8L_uAmyfFwXPL9jC_B3lVAkSQc5peXlk6ronlodiQJfFj8Fja_7LhkMi6p2TNmHvrTmAs1Am2raNLK-1vFrlrpJLayHlnJCLdWReioikcEWRdyzvQEPD9om0-5GMwKOY02nVB0yd463UkeXL0pLyCENmPkGCZmpmU5w-FVzxnMVY9ur2K1DRr0ZhliPtAta5HZxSCAFoVttYSbH-nibJbK25qsGIhDjEeD9JdRFATVFlG1ojrHGYH6hsNOVPSynMr19Uzn8hD5cFkF3A23J95P7MCpajs4TpqwcSv3o1JZSBdnw9l6W1Q",
  "e": "AQAB",
  "d": "bwQSncBiAN52k1yYDcxqoTZQUeAp-nnrcFlEKnctKxHopYaft0YUXcUSuepxzch0CmXVdpH5JAv_GZPUjHdiFQJ6ZlG_-7w3k07MJMkuBrKVJelndX4HE-4Kby3kp4soln5GKgelwW44ZqH1VSsZRx70a07WZQRPQXVTwvHnchWdeVhph6IxQjcVVIeOAWesrG-djKW9RHtw2L9CsjSi7eI_l1gHvgejwyrjTZkC3oUtXCwvbuY7PO3AehjWqiLIkPxF40eJPbjXF3SY1guYEk9O_1AQ8L4STLheCzVqhJDdAvWekWc0M0uaWjQAsDgyDthYSi7QivE956wxDC-i9Q",
  "p": "xBHBJlOS7xEB1VntUGQnP4cZWBdRqcd4dNeGHzOQw75Gm5Ctf5TKygztu4tX0OE5Zj8zICyb_pVMGs7Tx9nbGVRFhDqGzRC3n98HhXO-A56M-wdqXesehA4DYrvYnK15ww3ASZ0nCCfBs0oaoXnkxfmMTxzjgSsxl_e3SHA04xc",
  "q": "_MLkmStAEk8-LLeF62IYml1S22xKF5Cc4IH61iAO6jnsjBpwk_y8O0vmF_bNEPxtxggozVzYnLu3Phncl44RFRlOWHcIXkIJ4rYPk1LU4FBRNW9uKNqgpcHTL42w74QUWA1GdTUHt_A7d8PZ58RNxAOFCo5EuipTIG1cWrgyOPM",
  "dp": "CnxsmRElLZX6RB_QGlmQ48vk5hoxKuRZI96W9Lo8Rsu5WP6O2ODrS40PddVI-AnjmP1OTa01FpODg3eGSgzzs9IW-bBrnzICsBEshM2g3u6u2YoW_Tji4-sf8Trcpdx9Q5pLq2Q1PzFpIZ6X-ATd4FytTVndLP5TY65C5iPiIDc",
  "dq": "ixFH6SYsuGY1FTFcU2fDlZrnLablFRYR2kxpkEpPKnv4NBWP_zw87HsT-_dkWIYqd5yhpmwBGisF5PshrZkSuQMVYgML7aB0A_Kbmzklus02ASpv97ZiURDVewKAaSfIcbHMJEyrFH7umTR-vc_54XD2UBp6odsLpXnGKHvT1hU",
  "qi": "QCZSMVr1Fnesx3-WEC5UNWaLRQ_JqmqGcLi_XofVcJHl62i386ETuBPuTrMvY13z2Cxj4kjQx-JKC_JdBr1ESD7Est1CauwfNrMiVXvaNtEA4Fl8JeN7Kxl0_K53z1kbuW6jWvxwRcNYvC9I3gvqr9ksFKWl5TFJlSYYlQ0i0q4"
}




Source Code

The code samples (w/ .NET 7.0) used for this article are available in my GitHub samples repository.