-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Huge performance gap of JwtPublicKeyVerify
depending on the key within a keyset used for signature generation
#6
Comments
Thank you for filing this issue. The main fear we have is that we don't want to use a complicated parser on unverified data if we can avoid it. There are other things we can do:
I am curious: do you use Tink to generate the keys you use? Do you import them? If so, how? |
@tholenst Would you mind me having a stab at it, though it will take some time?
Yes, I am using Tink to generate keys for JWT and encrypt the generated keysets with internal KMS (implemented tink's By |
Thank you. Yes, you understood me correctly. Indeed, we want to be careful with parsing unverified tokens. Parsing JSON can be tricky. However, we should be able to do something here indeed; the issue is very real obviously. I will try to take a look. |
This is still something we would like to fix, but it turns out the internals make it more complicated than expected. |
Help us help you
We'd like to know more about
your Tink deployment.
Describe the bug:
JwtPublicKeyVerify
primitive shows a huge performance gap in verifying a JWT depending on the key, within a keyset, usedto generate the JWT signature.
What was the expected behavior?
No significant signature verification performance gap
How can we reproduce the bug?
Clone the BM code repository and hit the following command:
Do you have any debugging information?
WrappedJwtPublicKeyVerify#verifyAndDecode
doesn't use TINK's conventional way of determining the right key within a keyset by parsing the keyId prefix which is done atO(1)
time.Instead, it iterates over
PrimitiveSet<JwtPublicKeyVerifyInternal>
until it finds a matching key which generates the same signature as the one in the JWT, which makes the worst-case time complexityO(n)
.But, it's not enough to explain the huge perf gap shown in the benchmark.
The real problem comes from the fact that
verifyAndDecode
generates a token signature with all tried keys within a keyset until it finds a match and usesGeneralSecurityException
to control the code execution flow, which is very costly due to stacktrace generation.A CPU profiling result for the worst case BM reveals that CPU cycles consumed for signature verification & token decoding process can potentially be optimized away. (For interactive version, hit
curl -O https://raw.githubusercontent.com/ks-yim/tink-bm/master/src/jmh/java/com/example/tink/bm/jwt-public-key-verify-cpu-profiling.svg
and open the downloaded file on your local)What version of Tink are you using?
1.7.0
Can you tell us more about your development environment?
JDK 11, MacBook Pro(16-inch, 2021) Apple M1 Pro 32 GB
Is there anything else you'd like to add?
I'd like to suggest to move
JwtFormat#splitSignedCompact
fromJwtPublicKeyVerificationInternal
toJwtPublicKeyVerification
so that we can have access to the parsedkeyID
upfront and find the right keyset atO(1)
and do the token decoding & signature verification only once no matter how many keys exist in the keyset.This will introduce a change to
JwtPublicKeyVerifyInternal#verifyAndDecodeWithKid
's method signature so I'd like to get some advices from the maintainers about whether it is the right direction.The text was updated successfully, but these errors were encountered: