jwtk/jjwt

Improving performance (public vs private key use when parsing)

zcourts opened this issue ยท 3 comments

A significant proportion of our request processing is spent on parsing JWT tokens.
I've seen #226 and #473
Between 0.10 and the latest 0.11 there was a clear 10% or so improvement due to the improvements made to make the parser re-usable.

In 0.10 we had this
jwt1
After seeing issue #473 and upgrading we got down to this
jwt2

Configuring the profiler to instrument jjwt classes revealed this
jwt3

It's clear the RSA check is the choke point. I was confused as to why parsing is calling sign. Looking through RsaSignatureValidator I see it checks if the key is a PublicKey otherwise it signs.
I changed to using the public key and got an immediate 70% improvement.
jwt4

So the obvious question to me and what I want to ask is, is this code path any less secure?
Is this difference in performance known about already?
If there's no security difference, why would you ever use the private key to verify when using RSA?

Circling back to this:

The code isn't any less secure because the resulting output is still used in a security-necessary bitwise-or comparison to the previous computed signature value (i.e. MessageDigest.isEqual).

Verifying signatures with the private key was an old/legacy 'feature' for server-side applications that could verify signatures with their existing private key (whereas clients always referenced the public key) - I suspect you might have initially fallen into this use case, as well as sometimes it's just 'easier' to reference the private key you already have.

The performance implications however as you discovered can be significant, and in reality, it's just not necessary (i.e. if the server has a private key, it can also easily reference or derive the public key), and public key verification is significantly faster.

The documentation was updated to reflect using the PublicKey for verification via https://github.com/jwtk/jjwt#verification-key a long time ago (years ago?) so hopefully very few people come across this, but the logic to allow PrivateKeys was retained for backwards/legacy compatibility.

I hope that helps explain the difference!

Out of curiosity, do you think there should be an exception if the Parser is supplied with a PrivateKey? Or allow the computation as it does currently? Because of backwards compatibility, this behavior probably couldn't be changed until 1.0, but I'm curious what you think. Cheers!

@lhazlewood notification for this got lost amongst the endless GH notifications I get.
It's an interesting question. I think the performance difference is significant enough to raise an exception if a private key is provided. As you said, the public key can be obtained if the PK is what's available.

If there is a really really strong argument for retaining the behaviour in 1.0 I'd suggest provide an explicit f(PrivayeKey key,...) signature and warn/note the performance difference. I mean, I can't think of a case that warrants accepting it to be 70% slower when there's no difference in the security provided so I don't think this is necessary and would advocate dropping it and raising an exception.

@zcourts - great minds think alike ๐Ÿ˜„ I'm working on the jwe branch which already has this and many other changes that enforce type-safety of arguments on sign/verify, encrypt/decrypt with Java generics. So yep, only PrivateKeys will be allowed to create asymmetric key algorithm signatures once this is released ๐Ÿ˜