dvsekhvalnov / jose-jwt

Ultimate Javascript Object Signing and Encryption (JOSE), JSON Web Token (JWT) and Json Web Keys (JWK) Implementation for .NET and .NET Core
MIT License
947 stars 184 forks source link

AES256GCM JWE encryption in .NET standard 2.0 using Bouncy Castle #207

Open shaiArn opened 1 year ago

shaiArn commented 1 year ago

Hey @dvsekhvalnov,

I'm trying to encode a JWT with JWE encryption using AES 256 GCM. On Windows, it works great: JWT.Encode(payload, jwk, JweAlgorithm.RSA_OAEP, JweEncryption.A256GCM, extraHeaders: header) The tricky part is that I need it to run on iOS, and the AES 256 GCM algorithm used is using the bcrypt dll that does not exist on iOS.

Is it possible to use Bouncy Castle's implementation instead of the .NET core one?

dvsekhvalnov commented 1 year ago

Hi @shaiArn,

library is using managed AES-GCM implementation on NETSTANDARD 2.1 runtime. Should work just fine on .NetCore 3.0+

Please check out yourself: https://github.com/dvsekhvalnov/jose-jwt/blob/master/jose-jwt/crypto/AesGcmNetCore.cs

What exact error are you facing?

Answering your questions about Bouncy Castle. It's against library design goal to be zero-dependency. There are different reasons behind it.

On the other hand you can easily implement it yourself and register your own encryption implementation with:

   JWT.DefaultSettings.RegisterJwe(JweEncryption.A256GCM, YourBCImplementation);

If i ever have time, probably can publish alternate set of implementations as separate library too.

shaiArn commented 1 year ago

Hi @dvsekhvalnov, thank you for your answer.

Here is the full error:

DllNotFoundException: bcrypt.dll assembly:<unknown assembly> type:<unknown type> member:(null)
Jose.AesGcm.OpenAlgorithmProvider (System.String alg, System.String provider, System.String chainingMode) (at <e53f5c14391a4dae86321317d2821418>:0)
Jose.AesGcm.Encrypt (System.Byte[] key, System.Byte[] iv, System.Byte[] aad, System.Byte[] plainText) (at <e53f5c14391a4dae86321317d2821418>:0)
Jose.AesGcmEncryption.Encrypt (System.Byte[] aad, System.Byte[] plainText, System.Byte[] cek) (at <e53f5c14391a4dae86321317d2821418>:0)
Jose.JWE.EncryptBytes (System.Byte[] plaintext, System.Collections.Generic.IEnumerable`1[T] recipients, Jose.JweEncryption enc, System.Byte[] aad, Jose.SerializationMode mode, System.Nullable`1[T] compression, System.Collections.Generic.IDictionary`2[TKey,TValue] extraProtectedHeaders, System.Collections.Generic.IDictionary`2[TKey,TValue] unprotectedHeaders, Jose.JwtSettings settings) (at <e53f5c14391a4dae86321317d2821418>:0)
Jose.JWT.EncodeBytes (System.Byte[] payload, System.Object key, Jose.JweAlgorithm alg, Jose.JweEncryption enc, System.Nullable`1[T] compression, System.Collections.Generic.IDictionary`2[TKey,TValue] extraHeaders, Jose.JwtSettings settings) (at <e53f5c14391a4dae86321317d2821418>:0)
Jose.JWT.Encode (System.String payload, System.Object key, Jose.JweAlgorithm alg, Jose.JweEncryption enc, System.Nullable`1[T] compression, System.Collections.Generic.IDictionary`2[TKey,TValue] extraHeaders, Jose.JwtSettings settings) (at <e53f5c14391a4dae86321317d2821418>:0)
Jose.JWT.Encode (System.Object payload, System.Object key, Jose.JweAlgorithm alg, Jose.JweEncryption enc, System.Nullable`1[T] compression, System.Collections.Generic.IDictionary`2[TKey,TValue] extraHeaders, Jose.JwtSettings settings) (at <e53f5c14391a4dae86321317d2821418>:0)

Some more context and information

  1. The code is running as part of a Unity app that is meant to run as an iOS app
  2. When developing on a PC (Windows) - The code runs well.
  3. When running on a Mac/iOS device, we get the error above.
  4. When using A128CBC_HS256(or any other CBC algo) instead of A256GCM (or any other GCM algo), the error is not raised.

Do you think we there's a chance to use A256GCM without using a 3rd party dependency?

dvsekhvalnov commented 1 year ago

Yeah, it looks it doesn't use .Netstandard 2.1 according to stack trace.

I'm not an expert on Unity itself, but quick googling gave me https://forum.unity.com/threads/unity-future-net-development-status.1092205/

Probably you can check if Unity support .Netstandard 2.1 runtime or any plans.

shaiArn commented 1 year ago

@dvsekhvalnov, thank you for your previous suggestions and for offering to help with this issue.

  1. I examined Unity's support for .Netstandard 2.1. and I found out that that the package manager, by default, imports the 1.4 version of this NuGet package, which I guess explains the original issue.

However, when I force it to use the 2.1. version I get the following error: TypeLoadException: Could not load type of field 'Jose.JsonMapper:SerializeOptions' (0) due to: Could not load file or assembly 'System.Text.Json.... I am now looking into it and trying to understand what might be causing this error.

  1. I am also working on registering my own encryption implementation using Bouncy Castle, but I am having some difficulty getting it to work properly. I do get a valid JWT, but it seems like the payload is not being encrypted well. I think it might have to do with handling the protected header part. But i'm not sure.

Here is the encrypt method of my implementation:

 public byte[][] Encrypt(byte[] aad, byte[] plainText, byte[] cek)
        {
            const int nonceLength = 12; // in bytes
            const int tagLenth = 16; // in bytes

            var nonce = new byte[nonceLength];
            RandomNumberGenerator.Fill(nonce);

            var plaintextBytes = plainText;
            var bcCiphertext = new byte[plaintextBytes.Length + tagLenth];

            var cipher = new GcmBlockCipher(new AesEngine());
            var parameters = new AeadParameters(new KeyParameter(cek), tagLenth * 8, nonce);
            cipher.Init(true, parameters);

            var offset = cipher.ProcessBytes(plaintextBytes, 0, plaintextBytes.Length, bcCiphertext, 0);
            cipher.DoFinal(bcCiphertext, offset);

            // Bouncy Castle includes the authentication tag in the ciphertext
            var ciphertext = new byte[plaintextBytes.Length];
            var tag = new byte[tagLenth];
            Buffer.BlockCopy(bcCiphertext, 0, ciphertext, 0, plaintextBytes.Length);
            Buffer.BlockCopy(bcCiphertext, plaintextBytes.Length, tag, 0, tagLenth);

            return new byte[][] { nonce, ciphertext, tag };
        }

(I used Scott Brady's blog post as a reference)

Is there anything that you see that I might have missed in my code? Any guidance would be greatly appreciated.

dvsekhvalnov commented 1 year ago

Hi @shaiArn ,

  1. Probably just install https://www.nuget.org/packages/System.Text.Json from nuget, if it can't be found (it should but..) ?

  2. I'll give you link to java version that using BouncyCastle for AESGCM encryption, hopefully it will be more useful than my comments without trying to run your code: (just in case it is under tag v3.4)

https://bitbucket.org/connect2id/nimbus-jose-jwt/src/3.4/src/main/java/com/nimbusds/jose/crypto/AESGCM.java

shaiArn commented 1 year ago

@dvsekhvalnov, thank you!

As for 1 - It seems like mono does not support AES GCM. So unless there's a workaround to it, I guess the Bouncy Castle lead is the one to follow.

Thank you for the Java version. If I figure it out, I'll share my solution here.