panva / jose

JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes.
MIT License
5.4k stars 309 forks source link

JWT decryption does not support IVs longer than encryption key length for GCM #678

Closed twwildey closed 3 months ago

twwildey commented 3 months ago

What happened?

The decrypt method under src/runtime/node/decrypt.js invokes checkIvfLength, which assumes the byte length of the IV must be the byte length of the encryption key. While this may be required for many block cipher modes, it is not required for GCM. This same issue was officially fixed by Node.js back in v6: https://github.com/nodejs/node/pull/6376

As such, jose should be updated to support IVs that are longer than encrypted key when operating with GCM block ciphers. This issue prevents encrypted JWTs produced by python-jose from being decrypted by the jose NPM module.

This is actively blocking the development of a Sublime Text plugin to interface with the LSP services for AWS Q Developer.

Version

5.3.0

Runtime

Node.js

Runtime Details

Node 18.18.1

Code to reproduce

I've confirmed that commenting out the checkIvfLength invocation in the decrypt method within src/runtime/node/decrypt.js successfully decrypts a JWT using A256GCM encryption and the dir algorithm.

My integration testing used the decodeCredentialsRequestToken method in aws/language-server-runtimes, which successfully decrypts a JWT using A256GCM encryption and the dir algorithm once the checkIvfLength invocation is commented out in the decrypted method.

Required

panva commented 3 months ago

which assumes the byte length of the IV must be the byte length of the encryption key

That is not what the method is doing. GCM by the JOSE spec requires 96bit iv. That's what is checked to be used. See the JWA RFC

We've identitied and reported the issue of using non-conform iv lengths in python-jose already - https://github.com/mpdavis/python-jose/issues/281

twwildey commented 3 months ago

Understood. I did not consult the JWA spec, but if it requires 96 bits for the IV in AES-GCM modes, then it does seem that python-jose should be updated instead.

Simultaneously, I do not see how longer IVs would compromise the security of symmetric block ciphers in GCM mode. It seems that longer IVs could only increase their entropy, which would improve the security posture. Is it truly that critical for python-jose to resolve this on their side, or could jose allow 96+ bits for IVs in the AES-GCM modes?

panva commented 3 months ago

Simultaneously, I do not see how longer IVs would compromise the security of symmetric block ciphers in GCM mode. It seems that longer IVs could only increase their entropy, which would improve the security posture.

That is not universally the case https://crypto.stackexchange.com/questions/41601/aes-gcm-recommended-iv-size-why-12-bytes

could jose allow 96+ bits for IVs in the AES-GCM modes?

If the specification allowed such. It does not.

Shakeskeyboarde commented 3 months ago

Sure, it makes sense for Jose encryption to be strict. But, it seems like decryption could relax on the spec for better compatibility with encryption sources that are not as strict as Jose.

Encrypting is where the spec and security are critical. Decryption should work if technically possible.

panva commented 3 months ago

Sure, it makes sense for Jose encryption to be strict. But, it seems like decryption could relax on the spec for better compatibility with encryption sources that are not as strict as Jose.

Encrypting is where the spec and security are critical. Decryption should work if technically possible.

Hi @Shakeskeyboarde

That's just not how interoperability is achieved, nor is it the spirit which the specifications are written with. A single actor that doesn't follow the specified parameters does not warrant an ecosystem wide change. Other javascript modules also check for IV length to be 12 byte and of course non-javascript projects do as well, e.g. nimbus.

twwildey commented 3 months ago

Having read through the following resources for GCM and GHASH:

  1. https://en.wikipedia.org/wiki/Galois/Counter_Mode
  2. https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
  3. https://www.iacr.org/archive/fse2012/75490220/75490220.pdf

The argument that a longer IV bit string increases the likelihood of collision requires that GHASH results in collisions for distinct input values or that the hashed values are not "evenly" spaced compared to the 96 bit length IV (i.e. different IVs are spaced 2^32).

The AES key determines the subgroup of GF(2^128) that's used during polynomial multiplication in GHASH for GCM. If the key used for the computation is not a weak key, then the subgroup size - ord(H) - can be confirmed to be above or near 2^96, where ord(H) is the order of the multiplicative subgroup corresponding to the AES key represented by H.

Since all inputs to GHASH are multiplied by H, all IVs of the same length are guaranteed to be spaced by intervals of (2^128 - 1) / ord(H) for the same key. 96 bit length for IVs provides the "best guarantee" that the spacings will be 2^32, since the spacing is effectively determined by ord(H).

For a given AES key, the IV for each message should support up to 2^32 encrypted blocks without risk of collision. More so, the counters used for encryption blocks across different message should never collide. Using a 96 bit IV annihilates the possibility for collision across messages in GCM, since GHASH is not used. More so, when the ord(H) is not exactly 2^96, then spacings are not guaranteed to be 2^32.

twwildey commented 3 months ago

I have merged the following PR in python-jose to address this issue: https://github.com/mpdavis/python-jose/pull/355

twwildey commented 3 months ago

I have merged the following PR in python-jose which fixes test failures that were blocking the distribution of these changes to PyPI: https://github.com/mpdavis/python-jose/pull/356

twwildey commented 3 months ago

I see that python-jose has effectively been abandoned for https://jose.authlib.org/en/

I will be migrating my project to use https://jose.authlib.org/en/ instead.