JWT, JWE, JWS in .Net - Pt 3 - JWE
JWE is the encrypted version of a JWT. Encryption provides a way to ensure privacy of the data from an attacker and if using a pre-shared key, a very strong way of transmitting private data.
The .Net version of the JWT libraries does not also require a signature to be applied, you could assume that the data has integrity if you use an AEAD algorithm for encryption - which you should. However, it appears that you cannot validate the token if it does not have a signature - I'm not sure if there is a way to do that or whether it does not make sense to validate a token with no signature?
Fortunately, to produce a JWE in .Net is very similar to producing a JWS, although you need to generate a cryptographically secure symmetrical key as well as using a certificate to sign it. Naturally, all of this has overhead so although encryption-by-default can be useful, it does come at a price, especially for high-volume systems.
To create a key (the Content Encryption Key - CEK) , you can either just use RNGCryptoServiceProvider from the System.Security.Cryptography namespace like this:
Or you can hash some other piece of data using SHA256 to stretch it. Be careful with this method since you need the input to the SHA function to already be cryptographically secure random or an attacker could discover a pattern and work out how you generate your keys! For instance, do not stretch a numeric userid or guid. In my case, I was stretching a 32 character randomly generated "secret" from an oauth credential to create my pre-shared key.
Be careful with SHA256 and other cryptography classes for thread safety. It might be quicker to pre-create certain types like SHA256 but if ComputeHash is not thread safe, you might break something when used by multiple threads. I believe some forms of the .Net cryptography classes are thread safe and others are not.
Once you have your CEK, the only extra step is to create EncryptingCredentials as well as SigningCredentials:
Note that you need to use algorithms that are supported in .Net (I can't guarantee that the SecurityAlgorithms enum equates to what is supported), that the selected algorithms match the length of the key provided (i.e. 32 bytes for AES256) and that the second algorithm, which is used to encrypt the actual data is a type that includes authenticated data - i.e. a signature for the encrypted data to verify it was not tampered with before decrypting (such as GCM or HMACSHA). If you choose the wrong length of key, the call to CreateSecurityToken will throw a ArgumentOutOfRangeException. The first algorithm is the one that will be used to encrypt the key itself before it is added to the JWE token.
You can use RSA-OAEP for the first parameter but this is not the same as when it is used for the JWE. Firstly, it will only use a 256 bit key for RSA to match the second algorithm (the size of the key) but also, it will need a public key to encrypt and the recipient of the token will need the related private key to decrypt the CEK.
By providing the SigningCredentials and EncryptingCredentials to the call to CreateSecurityToken(), the library will create the token, sign it and then encrypt this as the payload in an outer JWE. This means that the header for the JWT will only contain data about the encrypting parameters (alg, enc etc) and only after it is decrypted, will the signing parameters be visible.
As mentioned before, you do not have to set a SigningCredential but when I tried this, the call to ValidateToken failed which sounds like it cannot validate data that is only encrypted, which might be possible to bypass (since the encrypted data already requires the use of an authenticated algorithm),
Validating is otherwise the same as it is for JWS, except for also setting the value of the TokenDecryptionKey in the TokenValidationParameters in the same way as it was set when it was created.
The .Net version of the JWT libraries does not also require a signature to be applied, you could assume that the data has integrity if you use an AEAD algorithm for encryption - which you should. However, it appears that you cannot validate the token if it does not have a signature - I'm not sure if there is a way to do that or whether it does not make sense to validate a token with no signature?
Fortunately, to produce a JWE in .Net is very similar to producing a JWS, although you need to generate a cryptographically secure symmetrical key as well as using a certificate to sign it. Naturally, all of this has overhead so although encryption-by-default can be useful, it does come at a price, especially for high-volume systems.
To create a key (the Content Encryption Key - CEK) , you can either just use RNGCryptoServiceProvider from the System.Security.Cryptography namespace like this:
var crng = new RNGCryptoServiceProvider();
var keyBytes = new byte[32]; // 256 bits for AES256
crng.GetBytes(keyBytes);
Or you can hash some other piece of data using SHA256 to stretch it. Be careful with this method since you need the input to the SHA function to already be cryptographically secure random or an attacker could discover a pattern and work out how you generate your keys! For instance, do not stretch a numeric userid or guid. In my case, I was stretching a 32 character randomly generated "secret" from an oauth credential to create my pre-shared key.
var keyBytes = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes("some data to stretch"));
Be careful with SHA256 and other cryptography classes for thread safety. It might be quicker to pre-create certain types like SHA256 but if ComputeHash is not thread safe, you might break something when used by multiple threads. I believe some forms of the .Net cryptography classes are thread safe and others are not.
Once you have your CEK, the only extra step is to create EncryptingCredentials as well as SigningCredentials:
var symSecurityKey = new SymmetricSecurityKey(keyBytes);
var creds = new EncryptingCredentials(symSecurityKey, SecurityAlgorithms.Aes256KW, SecurityAlgorithms.Aes256CbcHmacSha512);
Note that you need to use algorithms that are supported in .Net (I can't guarantee that the SecurityAlgorithms enum equates to what is supported), that the selected algorithms match the length of the key provided (i.e. 32 bytes for AES256) and that the second algorithm, which is used to encrypt the actual data is a type that includes authenticated data - i.e. a signature for the encrypted data to verify it was not tampered with before decrypting (such as GCM or HMACSHA). If you choose the wrong length of key, the call to CreateSecurityToken will throw a ArgumentOutOfRangeException. The first algorithm is the one that will be used to encrypt the key itself before it is added to the JWE token.
You can use RSA-OAEP for the first parameter but this is not the same as when it is used for the JWE. Firstly, it will only use a 256 bit key for RSA to match the second algorithm (the size of the key) but also, it will need a public key to encrypt and the recipient of the token will need the related private key to decrypt the CEK.
By providing the SigningCredentials and EncryptingCredentials to the call to CreateSecurityToken(), the library will create the token, sign it and then encrypt this as the payload in an outer JWE. This means that the header for the JWT will only contain data about the encrypting parameters (alg, enc etc) and only after it is decrypted, will the signing parameters be visible.
As mentioned before, you do not have to set a SigningCredential but when I tried this, the call to ValidateToken failed which sounds like it cannot validate data that is only encrypted, which might be possible to bypass (since the encrypted data already requires the use of an authenticated algorithm),
Validating is otherwise the same as it is for JWS, except for also setting the value of the TokenDecryptionKey in the TokenValidationParameters in the same way as it was set when it was created.