Repository navigation
perf(keys): cache parsed RSA key pair instead of re-parsing per encode (#120) - #126
Merged
Merged
Conversation
jsonwebtoken::crypto::sign re-parses (and fully re-validates) the RSA private key into an aws_lc_rs::signature::RsaKeyPair on every RS*/PS* encode call, which was the dominant cost of RSA signing: RS256 encode measured ~526us with a pre-built EncodingKey, ~2.7x the ~198us a raw cryptography sign takes with an equivalent pre-parsed key. EncodingKey.from_rsa_pem now parses the DER-encoded key into an RsaKeyPair once, at construction time, and encode()/encode_json() sign through that cached key directly via aws_lc_rs, bypassing jsonwebtoken::crypto::sign for the RSA family. Every other algorithm (HMAC, EC, EdDSA) and the decode/verify paths are unchanged; they were already close to their measured floor. Only active with the default aws_lc_rs crypto backend (behind `#[cfg(feature = "aws_lc_rs")]`); the rust_crypto feature used for the Linux aarch64 wheel keeps the previous jsonwebtoken-only path. A malformed RSA private key is now rejected by EncodingKey.from_rsa_pem itself instead of lazily by the first encode() call. Measured (2048-bit key, pre-built EncodingKey): RS256 encode 526us -> 201us (2.6x), now within ~5% of raw cryptography sign (210us). decode and all non-RSA algorithms are unaffected. Closes #120
Add coverage for the cached-key path introduced in the previous commit: repeated encode() calls on the same RSA EncodingKey (RS*/PS*) must keep producing signatures that verify correctly, not just on the first call, and a malformed RSA PEM must fail at EncodingKey.from_rsa_pem construction rather than lazily at encode().
ZhuchkaTriplesix
force-pushed
the
issue/120-cached-parsed-keys
branch
from
September 28, 2026 10:08
158152f to
234d2dd
Compare
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
jsonwebtoken::crypto::signre-parses (and fully re-validates) the RSA private key into anaws_lc_rs::signature::RsaKeyPairon everyencodecall, regardless of whether the caller passed a pre-builtEncodingKey. That re-parse/validation was the dominant cost of RSA signing:encode(before)cryptographyRSA-2048 sign, pre-parsed keyFix
EncodingKey.from_rsa_pemnow parses the DER-encoded key into anRsaKeyPaironce, at construction time, andencode/encode_jsonsign through that cached key directly viaaws_lc_rs, bypassingjsonwebtoken::crypto::signfor the RSA family (RS*/PS*). Every other algorithm (HMAC, EC, EdDSA) and all decode/verify paths are untouched — a follow-up measurement showed they were already close to their floor (e.g. ES256 encode 21µs vs. 19µs rawcryptographysign, EdDSA encode 13µs vs. 26µs), so caching there would add risk for negligible gain. That's a narrower scope than #120's title suggested; the rest of that investigation is written up in the issue and CHANGELOG entry.Only active with the default
aws_lc_rscrypto backend (#[cfg(feature = "aws_lc_rs")]); therust_cryptofeature used for the Linux aarch64 wheel is unchanged and still goes throughjsonwebtoken::crypto::sign.Behavior change: a malformed RSA private key is now rejected by
EncodingKey.from_rsa_pemitself instead of lazily by the firstencode()call. No test relied on the old lazy timing.Results
Pre-built
EncodingKey, 2048-bit RSA key, release build:cryptographysignencodedecodescripts/compare_jwt_libraries.py --algorithms RS256,PS256,HS256,ES256,EdDSA(this branch): RS256 encode ~5,000 ops/sec, PS256 encode ~5,100 ops/sec — consistent with the µs numbers above.Checklist
EncodingKey.from_rsa_pemparses the key once;encode/encode_jsondo no per-call RSA key parsingEncodingKeywithin ~10% of rawcryptographysign (measured ~5% over)aws_lc_rsandrust_cryptofeatures build (cargo build/clippywith and without--no-default-features --features rust_crypto)cargo fmt --check,cargo clippy --all-targets -- -D warningsclean on both feature setscargo test(11/11) andpytest(258 passed, 1 skipped) greenmypycleanCHANGELOG.mdCloses #120.