1

I am implementing a client for communicate with some server by the cryptological way. The client sends get request with public RSA key to the server. Documentation "how to communicate with server" has the sample with java code. The following code generates the public key:

KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair keypair = keyGen.genKeyPair();
byte[] pubKeyBytes = keypair.getPublic().getEncoded();

I need to implement my client in C#. I found the way how to do the same in C#:

var rsa = new RSACryptoServiceProvider(2048);
var parameters = _rsa.ExportParameters(includePrivateParameters: false);

The client uses parameters and solution from How to adapt public/private RSA keys of C# for using them as in Java? That's ok. The client can generate the key which can pass verification on the server. As result, I have the encrypted response and trying to decrypt it.

responseContent = rsa.Decrypt(responseContent)

But this code throws following exception:

System.Security.Cryptography.CryptographicException: 'The data to be decrypted exceeds the maximum for this modulus of 256 bytes.'

responseContent is byte array with length 250996. And as I see there is impossible to decrypt response content by way above.

From the documentation, I know that

enter image description here

Also, I have an example how to decrypt response in java:

JWEObject jweObject = JWEObject.parse(encryptedPayload); 
RSAPrivateKey rsaPrivatteKey = (RSAPrivateKey)KeyFactory
    .getInstance("RSA")
    .generatePrivate(new PKCS8EncodedKeySpec(keybytes));
RSADecrypter RSADecrypter rsaDecrypter= new RSADecrypter(rsaPrivateKey);
JWEObject jweObject.decrypt(rsaDecrypter); 
String decryptedResponse = jweObject.getPayload().toString();

I thought that rsa.Decrypt is analog of code above. But as I sow - not. After some research, I found that my response is JWE source. Based on https://www.rfc-editor.org/rfc/rfc7516 I split my response to parts which was separated by '.' and decode each of them from base64url. As result I have:

  • Header (which is JSON: {"enc":"A256CBC-HS512", "alg":"RSA1_5"})
  • Encryption key (size 256 bytes)
  • Initialization Vector (size 16 bytes)
  • Cipertext (size 1844688 bytes)
  • Authentication Tag (size 32 bytes)

I think the main content is in Cipertext and I need to decrypt it. But I don't know how because of the Cipertext size is more than 256 bytes and I can't use rsa.Decrypt.

How to decrypt source when size of it is more than RSA key?

Community
  • 1
  • 1
Valentyn Zakharenko
  • 2,710
  • 1
  • 21
  • 47
  • 1
    When you need to encrypt a lot of data 'with RSA', you actually generate a unique symmetric algorithm key (for example, an AES key), encrypt data with it, encrypt the symmetric key with an RSA key and send both the RSA-encrypted key and AES-encrypted data together. Probably that JWE-related code parses such a packet and reverses the process, while `rsa.Decrypt` only does pure RSA decryption. Try to look at what `JWEObject` handling exactly does. – Roman Puchkovskiy Mar 16 '18 at 19:44
  • 1
    JWE in JWEObject is for "JSON Web Encryption": https://tools.ietf.org/html/rfc7516. So those bytes you have are not really encrypted payload. It's JSON which contains encrypted payload + useful information about how to decrypt it. – Evk Mar 16 '18 at 20:33
  • @Evk Now I understand 'encrypted json' from documentation in another view. – Valentyn Zakharenko Mar 16 '18 at 22:02

2 Answers2

2

I have just finished an experimental console app using .NET6 to get familiar with the concepts of JWT, JWS and JWE.

Maybe this could help someone...

I wanted to get it working without the use of any third party packages. These are all using statements:

using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

You will need a TokenValidationParameters object that configures the validation. For this, you need to have:

  • a public key to verify the signature
  • a private key to decrypt the header & payload

Both of these are stored locally for demo purposes.

// Get public key to verify the signature
string publicKeySign = File.ReadAllText(@"C:\Docs\publicKeySign.pem");
var rsaPublicKey = RSA.Create();
rsaPublicKey.ImportFromPem(publicKeySign);

// Get private key to decrypt
string privateKeyEncrypt = File.ReadAllText(@"C:\Docs\privateKeyEncrypt.key");
rsaPrivateKey = RSA.Create();
rsaPrivateKey.ImportFromPem(privateKeyEncrypt);

// Pass those keys into the TokenValidationParameters object
var validationParameters = new TokenValidationParameters
{
    ValidateIssuerSigningKey = true,
    ValidateIssuer = true,
    ValidateAudience = true,
    ValidIssuer = "http://mysite.example.com",
    ValidAudience = "http://myaudience.example.com",
    IssuerSigningKey = new RsaSecurityKey(rsaPublicKey),
    TokenDecryptionKey = new RsaSecurityKey(rsaPrivateKey)
};

// Pass in the token (as a string) and your tokenValidationParameters
JwtSecurityTokenHandler jwtSecurityTokenHandler = new();
TokenValidationResult result = await jwtSecurityTokenHandler.ValidateTokenAsync(token, tokenValidationParameters);

The result is a TokenValidationResult, MS Docs here. It has the following properties:

  1. Claims, which is a Dictionary<string,object>
  2. ClaimsIdentity, which in turn has a property:
    • Claims, which is an IEnumerable

This way, you can inspect the claims sent along with the JWE. Hope this helps, have a good one!

1

I found this library js-jose that can do exactly what I need. I added it as NuGet package, written following code:

 JWT.Decode(responseContent, // content as String read from Response
            rsa, // RSACryptoServiceProvider
            JweAlgorithm.RSA1_5, 
            JweEncryption.A256CBC_HS512); 

and has decrypted content as result.

Valentyn Zakharenko
  • 2,710
  • 1
  • 21
  • 47