Skip to content

x86_64 cross-check

The same benchmarks as the published ones, run on GitHub's x86_64 Linux runner, to check the results hold on an Intel or AMD CPU. That runner gets different CPUs from run to run, so the times here change between runs more than the ratios do; each part names the CPU it ran on.

In this run, on an AMD EPYC 9V74, the HS256 race finishes in the same order as on arm64: ryjwt about 27 times faster than PyJWT into a Struct, and twice as fast as jsonwebtoken's struct. The RS256 race doesn't: aws-lc (under ryjwt and jsonwebtoken) checks RSA signatures in half the time it takes on arm64, and ryjwt finishes first, about 15% ahead of jsonwebtoken and fast-jwt.

The races

Generated 2026-10-10 from bench/results-race/ by bench/race_svg.py, on AMD EPYC 9V74 80-Core Processor (x86_64), Linux. Each library decodes the same token 100,000 times, in 3 rounds, after 1,000 untimed decodes; the time is the mean over all three.

HS256, a 64-byte secret

library 100,000 decodes µs per decode range over 3 rounds vs PyJWT
ryjwt → Struct (Python) 0.19 s 1.91 1.90 to 1.92 26.8x
ryjwt → dict (msgspec) (Python) 0.21 s 2.05 2.05 to 2.06 24.9x
ryjwt → dict (jiter) (Python) 0.26 s 2.57 2.57 to 2.58 19.9x
jsonwebtoken → struct (Rust) 0.38 s 3.81 3.81 to 3.82 13.4x
ryjwt → BaseModel (Python) 0.41 s 4.10 4.10 to 4.10 12.5x
jsonwebtoken → Value (Rust) 0.45 s 4.48 4.47 to 4.48 11.4x
fast-jwt (Bun) 0.60 s 5.98 5.50 to 6.81 8.6x
joserfc (Python) 2.52 s 25.16 25.13 to 25.22 2.0x
jose (Bun) 2.74 s 27.36 26.88 to 28.12 1.9x
PyJWT (Python) 5.12 s 51.16 50.64 to 51.97 1.0x

RS256, a 3072-bit RSA PEM public key

library 100,000 decodes µs per decode range over 3 rounds vs PyJWT
ryjwt → dict (msgspec) (Python) 3.51 s 35.06 35.04 to 35.10 2.9x
ryjwt → Struct (Python) 3.52 s 35.22 35.21 to 35.24 2.9x
ryjwt → dict (jiter) (Python) 3.55 s 35.46 35.45 to 35.47 2.8x
ryjwt → BaseModel (Python) 3.75 s 37.48 37.41 to 37.59 2.7x
jsonwebtoken → struct (Rust) 4.11 s 41.09 41.08 to 41.12 2.5x
jsonwebtoken → Value (Rust) 4.15 s 41.52 41.51 to 41.53 2.4x
fast-jwt (Bun) 4.19 s 41.89 41.45 to 42.76 2.4x
jose (Bun) 6.37 s 63.66 63.23 to 64.51 1.6x
joserfc (Python) 6.47 s 64.66 64.57 to 64.75 1.6x
PyJWT (Python) 10.08 s 100.84 100.58 to 101.03 1.0x

Full matrix

Generated 2026-10-10 from bench/results-matrix/ by bench/compare.py, on AMD EPYC 9V74 80-Core Processor (x86_64), Linux. Run in Docker, one container per library, each limited to 1 CPU (pinned) and 500 MB with no swap; the JWKS server runs in a container of its own, outside that limit. Each case gets 1,000 untimed decodes, then 10,000 timed. Runtimes: Bun 1.3.14, CPython 3.14.8, Rust (release, LTO).

Each decode verifies the signature and checks exp and aud, as a real caller would, and each library's result is checked against the token's claims before timing. Times are µs per decode: the mean over 10,000 decodes of the same token, after 1,000 untimed (lower is better); (Nx) is the speed-up vs pyjwt 2.15.1. Fastest per row in bold; n/a where the library can't (see the end).

Key sources: an HMAC secret (HS256 only); a PEM public key, parsed once; a JWKS document of 3 keys, the signing key in the middle, picked by the token's kid; and that document fetched over HTTPS from a JWKS server (static-web-server, in its own container), timed in the steady state with the keys cached.

Headline: typical token, medium key

key source alg key ryjwt 0.1.1 → dict (msgspec) ryjwt 0.1.1 → Struct ryjwt 0.1.1 → dict (jiter) ryjwt 0.1.1 → BaseModel jsonwebtoken 11.1.0 (aws-lc-rs) → struct jsonwebtoken 11.1.0 (aws-lc-rs) → Value fast-jwt 6.3.3 jose 6.2.12 joserfc 1.7.5 pyjwt 2.15.1
HMAC secret HS256 64 B 2.03 (24.4x) 1.99 (25.0x) 2.48 (20.0x) 4.06 (12.3x) 3.84 (13.0x) 4.58 (10.9x) 6.77 (7.4x) 27.74 (1.8x) 25.39 (2.0x) 49.74 (1.0x)
PEM public key RS256 RSA 3072 35.19 (2.8x) 35.80 (2.8x) 35.43 (2.8x) 37.46 (2.7x) 40.97 (2.4x) 41.80 (2.4x) 43.01 (2.3x) 64.41 (1.5x) 65.06 (1.5x) 99.63 (1.0x)
PEM public key ES384 P-384 196.41 (2.7x) 195.50 (2.7x) 196.24 (2.7x) 199.13 (2.6x) 200.19 (2.6x) 199.02 (2.6x) 510.35 (1.0x) 551.77 (1.0x) 505.23 (1.0x) 527.19 (1.0x)
PEM public key EdDSA Ed25519 39.00 (3.8x) 39.16 (3.8x) 39.47 (3.7x) 41.55 (3.6x) 40.55 (3.6x) 41.02 (3.6x) 57.98 (2.5x) 82.94 (1.8x) 130.01 (1.1x) 147.53 (1.0x)
JWKS document RS256 RSA 3072 35.14 (4.3x) 35.15 (4.3x) 35.40 (4.2x) 37.32 (4.0x) 41.61 (3.6x) 42.26 (3.6x) 47.40 (3.2x) 66.79 (2.3x) 68.36 (2.2x) 150.36 (1.0x)
JWKS URL, sync client RS256 RSA 3072 35.53 (4.4x) 35.76 (4.4x) 36.64 (4.3x) 38.08 (4.1x) n/a n/a n/a n/a n/a 156.61 (1.0x)
JWKS URL, async client RS256 RSA 3072 35.62 (4.4x) 35.69 (4.4x) 36.18 (4.3x) 37.63 (4.2x) n/a n/a 95.40 (1.6x) 68.26 (2.3x) n/a n/a

HMAC secret

alg key payload token B ryjwt 0.1.1 → dict (msgspec) ryjwt 0.1.1 → Struct ryjwt 0.1.1 → dict (jiter) ryjwt 0.1.1 → BaseModel jsonwebtoken 11.1.0 (aws-lc-rs) → struct jsonwebtoken 11.1.0 (aws-lc-rs) → Value fast-jwt 6.3.3 jose 6.2.12 joserfc 1.7.5 pyjwt 2.15.1
HS256 32 B small 231 1.03 (30.8x) 1.11 (28.6x) 1.18 (27.0x) 2.25 (14.1x) 1.98 (16.0x) 2.32 (13.7x) 9.43 (3.4x) 34.18 (0.9x) 22.55 (1.4x) 31.84 (1.0x)
HS256 32 B typical 661 2.08 (24.2x) 2.02 (24.9x) 2.48 (20.3x) 4.05 (12.4x) 3.77 (13.4x) 4.45 (11.3x) 10.06 (5.0x) 34.40 (1.5x) 25.40 (2.0x) 50.37 (1.0x)
HS256 32 B medium 2244 5.18 (21.7x) 3.28 (34.2x) 6.36 (17.6x) 6.17 (18.2x) 7.51 (14.9x) 11.93 (9.4x) 13.56 (8.3x) 36.62 (3.1x) 38.17 (2.9x) 112.16 (1.0x)
HS256 32 B large 20884 44.52 (20.6x) 21.84 (42.1x) 51.57 (17.8x) 40.09 (22.9x) 52.72 (17.4x) 110.53 (8.3x) 104.11 (8.8x) 132.57 (6.9x) 204.33 (4.5x) 918.50 (1.0x)
HS256 64 B small 231 1.04 (30.6x) 1.11 (28.9x) 1.19 (27.0x) 2.24 (14.2x) 1.96 (16.3x) 2.32 (13.8x) 4.14 (7.7x) 24.47 (1.3x) 20.26 (1.6x) 31.96 (1.0x)
HS256 64 B typical 661 2.03 (24.4x) 1.99 (25.0x) 2.48 (20.0x) 4.06 (12.3x) 3.84 (13.0x) 4.58 (10.9x) 6.77 (7.4x) 27.74 (1.8x) 25.39 (2.0x) 49.74 (1.0x)
HS256 64 B medium 2244 6.21 (18.0x) 3.29 (33.9x) 6.34 (17.6x) 6.17 (18.1x) 7.03 (15.9x) 11.98 (9.3x) 12.80 (8.7x) 36.21 (3.1x) 38.51 (2.9x) 111.65 (1.0x)
HS256 64 B large 20884 44.76 (19.8x) 21.81 (40.7x) 51.94 (17.1x) 40.14 (22.1x) 52.75 (16.8x) 109.85 (8.1x) 98.58 (9.0x) 123.99 (7.2x) 204.59 (4.3x) 887.21 (1.0x)
HS256 256 B small 231 1.03 (32.7x) 1.12 (30.0x) 1.17 (28.6x) 2.25 (14.9x) 2.27 (14.7x) 2.48 (13.5x) 4.38 (7.7x) 25.84 (1.3x) 20.38 (1.6x) 33.50 (1.0x)
HS256 256 B typical 661 2.01 (26.0x) 1.98 (26.5x) 2.48 (21.1x) 4.05 (12.9x) 4.07 (12.9x) 4.72 (11.1x) 6.84 (7.7x) 33.57 (1.6x) 25.50 (2.1x) 52.34 (1.0x)
HS256 256 B medium 2244 5.21 (22.1x) 3.28 (35.0x) 6.36 (18.1x) 6.18 (18.6x) 7.33 (15.7x) 12.19 (9.4x) 11.92 (9.7x) 35.59 (3.2x) 38.58 (3.0x) 115.00 (1.0x)
HS256 256 B large 20884 44.81 (19.9x) 21.86 (40.8x) 51.64 (17.3x) 40.20 (22.2x) 52.99 (16.8x) 109.83 (8.1x) 97.64 (9.1x) 126.62 (7.0x) 205.46 (4.3x) 891.79 (1.0x)
HS256 4096 B small 231 1.04 (63.4x) 1.13 (58.3x) 1.17 (56.4x) 2.24 (29.3x) 4.35 (15.1x) 4.63 (14.2x) 6.50 (10.1x) 30.77 (2.1x) 22.77 (2.9x) 65.70 (1.0x)
HS256 4096 B typical 661 2.05 (41.8x) 1.99 (43.2x) 2.47 (34.7x) 4.09 (21.0x) 6.21 (13.8x) 6.90 (12.4x) 8.60 (10.0x) 32.97 (2.6x) 27.56 (3.1x) 85.78 (1.0x)
HS256 4096 B medium 2244 5.30 (28.2x) 3.20 (46.7x) 6.31 (23.7x) 6.16 (24.2x) 9.39 (15.9x) 14.28 (10.4x) 14.21 (10.5x) 39.29 (3.8x) 40.58 (3.7x) 149.22 (1.0x)
HS256 4096 B large 20884 44.98 (20.5x) 21.85 (42.2x) 51.72 (17.8x) 40.16 (23.0x) 55.03 (16.8x) 112.15 (8.2x) 98.93 (9.3x) 127.30 (7.2x) 207.43 (4.4x) 922.54 (1.0x)

PEM public key

alg key payload token B ryjwt 0.1.1 → dict (msgspec) ryjwt 0.1.1 → Struct ryjwt 0.1.1 → dict (jiter) ryjwt 0.1.1 → BaseModel jsonwebtoken 11.1.0 (aws-lc-rs) → struct jsonwebtoken 11.1.0 (aws-lc-rs) → Value fast-jwt 6.3.3 jose 6.2.12 joserfc 1.7.5 pyjwt 2.15.1
RS256 RSA 2048 small 561 16.52 (3.4x) 16.61 (3.4x) 16.74 (3.4x) 17.73 (3.2x) 20.17 (2.8x) 20.19 (2.8x) 22.45 (2.5x) 44.90 (1.3x) 40.48 (1.4x) 56.24 (1.0x)
RS256 RSA 2048 typical 961 17.56 (4.2x) 17.47 (4.2x) 17.99 (4.1x) 19.48 (3.8x) 21.70 (3.4x) 22.09 (3.4x) 25.53 (2.9x) 47.77 (1.6x) 45.02 (1.6x) 74.14 (1.0x)
RS256 RSA 2048 medium 2574 20.85 (7.3x) 18.73 (8.2x) 21.93 (7.0x) 21.65 (7.1x) 25.30 (6.0x) 30.08 (5.1x) 31.06 (4.9x) 61.38 (2.5x) 58.54 (2.6x) 152.79 (1.0x)
RS256 RSA 2048 large 21214 60.40 (14.9x) 37.46 (24.0x) 67.26 (13.3x) 55.58 (16.2x) 71.25 (12.6x) 142.68 (6.3x) 120.63 (7.4x) 139.11 (6.5x) 228.48 (3.9x) 897.87 (1.0x)
RS256 RSA 3072 small 731 34.09 (2.4x) 34.16 (2.4x) 34.28 (2.4x) 36.20 (2.2x) 39.40 (2.1x) 39.76 (2.0x) 40.48 (2.0x) 63.67 (1.3x) 59.88 (1.4x) 81.38 (1.0x)
RS256 RSA 3072 typical 1131 35.19 (2.8x) 35.80 (2.8x) 35.43 (2.8x) 37.46 (2.7x) 40.97 (2.4x) 41.80 (2.4x) 43.01 (2.3x) 64.41 (1.5x) 65.06 (1.5x) 99.63 (1.0x)
RS256 RSA 3072 medium 2744 39.22 (4.1x) 36.45 (4.4x) 39.55 (4.0x) 39.41 (4.1x) 44.53 (3.6x) 49.61 (3.2x) 49.09 (3.3x) 71.39 (2.2x) 79.42 (2.0x) 159.79 (1.0x)
RS256 RSA 3072 large 21384 78.70 (11.7x) 54.93 (16.8x) 85.74 (10.8x) 73.20 (12.6x) 93.09 (9.9x) 154.64 (6.0x) 140.20 (6.6x) 160.37 (5.8x) 248.76 (3.7x) 922.24 (1.0x)
RS256 RSA 4096 small 902 57.60 (2.0x) 57.88 (2.0x) 57.88 (2.0x) 58.81 (2.0x) 65.71 (1.8x) 67.30 (1.7x) 65.03 (1.8x) 88.41 (1.3x) 86.16 (1.4x) 116.37 (1.0x)
RS256 RSA 4096 typical 1302 58.72 (2.3x) 58.89 (2.2x) 59.14 (2.2x) 60.99 (2.2x) 67.45 (2.0x) 68.25 (1.9x) 66.71 (2.0x) 89.90 (1.5x) 91.26 (1.4x) 132.25 (1.0x)
RS256 RSA 4096 medium 2915 61.93 (3.1x) 60.05 (3.2x) 63.16 (3.1x) 63.21 (3.1x) 70.98 (2.7x) 75.86 (2.6x) 72.46 (2.7x) 94.91 (2.1x) 104.49 (1.9x) 194.90 (1.0x)
RS256 RSA 4096 large 21555 103.41 (9.3x) 78.63 (12.2x) 108.90 (8.8x) 96.98 (9.9x) 117.68 (8.2x) 180.57 (5.3x) 161.75 (5.9x) 181.32 (5.3x) 278.74 (3.4x) 960.13 (1.0x)
ES256 P-256 small 304 50.59 (1.8x) 50.79 (1.8x) 50.75 (1.8x) 52.10 (1.8x) 52.58 (1.8x) 52.84 (1.7x) 56.73 (1.6x) 78.98 (1.2x) 86.11 (1.1x) 92.40 (1.0x)
ES256 P-256 typical 704 51.94 (2.1x) 52.01 (2.1x) 52.50 (2.1x) 54.15 (2.0x) 54.61 (2.0x) 55.05 (2.0x) 57.60 (1.9x) 81.19 (1.4x) 91.37 (1.2x) 110.80 (1.0x)
ES256 P-256 medium 2317 55.23 (3.2x) 53.13 (3.3x) 56.20 (3.1x) 56.06 (3.1x) 57.86 (3.0x) 62.83 (2.8x) 62.84 (2.8x) 87.30 (2.0x) 105.59 (1.7x) 174.67 (1.0x)
ES256 P-256 large 20957 96.97 (9.8x) 71.72 (13.2x) 101.79 (9.3x) 90.06 (10.5x) 103.79 (9.1x) 168.47 (5.6x) 153.86 (6.1x) 170.80 (5.5x) 275.82 (3.4x) 945.64 (1.0x)
ES384 P-384 small 346 193.73 (2.6x) 193.69 (2.6x) 193.82 (2.6x) 195.32 (2.6x) 195.77 (2.6x) 196.61 (2.6x) 505.22 (1.0x) 541.41 (0.9x) 499.77 (1.0x) 506.28 (1.0x)
ES384 P-384 typical 746 196.41 (2.7x) 195.50 (2.7x) 196.24 (2.7x) 199.13 (2.6x) 200.19 (2.6x) 199.02 (2.6x) 510.35 (1.0x) 551.77 (1.0x) 505.23 (1.0x) 527.19 (1.0x)
ES384 P-384 medium 2359 200.68 (2.9x) 197.16 (3.0x) 200.51 (2.9x) 200.60 (2.9x) 202.17 (2.9x) 207.35 (2.8x) 517.45 (1.1x) 559.57 (1.1x) 523.48 (1.1x) 590.18 (1.0x)
ES384 P-384 large 20999 265.48 (5.2x) 237.23 (5.8x) 268.50 (5.1x) 255.42 (5.4x) 269.78 (5.1x) 338.05 (4.1x) 631.71 (2.2x) 681.84 (2.0x) 715.59 (1.9x) 1380.73 (1.0x)
ES512 P-521 small 394 337.35 (1.5x) 337.23 (1.5x) 337.31 (1.5x) 338.07 (1.5x) n/a n/a 1198.20 (0.4x) 1253.71 (0.4x) 490.86 (1.0x) 500.29 (1.0x)
ES512 P-521 typical 794 335.75 (1.5x) 335.22 (1.6x) 335.93 (1.5x) 338.14 (1.5x) n/a n/a 1182.22 (0.4x) 1238.99 (0.4x) 497.98 (1.0x) 520.12 (1.0x)
ES512 P-521 medium 2407 341.85 (1.7x) 339.51 (1.7x) 345.91 (1.7x) 343.09 (1.7x) n/a n/a 1179.39 (0.5x) 1242.33 (0.5x) 512.81 (1.1x) 585.18 (1.0x)
ES512 P-521 large 21047 407.65 (3.4x) 378.44 (3.6x) 410.50 (3.3x) 396.67 (3.4x) n/a n/a 1322.53 (1.0x) 1388.90 (1.0x) 708.92 (1.9x) 1366.49 (1.0x)
EdDSA Ed25519 small 306 37.50 (3.5x) 37.74 (3.5x) 38.10 (3.4x) 39.09 (3.4x) 38.45 (3.4x) 38.70 (3.4x) 55.34 (2.4x) 81.05 (1.6x) 125.90 (1.0x) 131.01 (1.0x)
EdDSA Ed25519 typical 706 39.00 (3.8x) 39.16 (3.8x) 39.47 (3.7x) 41.55 (3.6x) 40.55 (3.6x) 41.02 (3.6x) 57.98 (2.5x) 82.94 (1.8x) 130.01 (1.1x) 147.53 (1.0x)
EdDSA Ed25519 medium 2319 45.24 (4.6x) 42.25 (5.0x) 45.38 (4.6x) 45.33 (4.6x) 45.87 (4.6x) 50.78 (4.1x) 65.93 (3.2x) 91.65 (2.3x) 147.23 (1.4x) 210.19 (1.0x)
EdDSA Ed25519 large 20959 104.77 (9.4x) 80.82 (12.2x) 110.70 (8.9x) 98.99 (10.0x) 111.64 (8.8x) 172.43 (5.7x) 176.57 (5.6x) 200.66 (4.9x) 329.91 (3.0x) 985.43 (1.0x)

JWKS document

alg key payload token B ryjwt 0.1.1 → dict (msgspec) ryjwt 0.1.1 → Struct ryjwt 0.1.1 → dict (jiter) ryjwt 0.1.1 → BaseModel jsonwebtoken 11.1.0 (aws-lc-rs) → struct jsonwebtoken 11.1.0 (aws-lc-rs) → Value fast-jwt 6.3.3 jose 6.2.12 joserfc 1.7.5 pyjwt 2.15.1
RS256 RSA 2048 small 561 16.57 (5.1x) 17.41 (4.9x) 16.63 (5.1x) 17.70 (4.8x) 20.69 (4.1x) 20.73 (4.1x) 24.74 (3.4x) 48.98 (1.7x) 43.54 (2.0x) 85.09 (1.0x)
RS256 RSA 2048 typical 961 17.59 (6.6x) 17.55 (6.7x) 18.02 (6.5x) 19.54 (6.0x) 22.27 (5.2x) 22.91 (5.1x) 27.28 (4.3x) 50.58 (2.3x) 48.33 (2.4x) 116.82 (1.0x)
RS256 RSA 2048 medium 2574 20.87 (11.3x) 18.75 (12.6x) 21.84 (10.8x) 21.57 (10.9x) 26.38 (8.9x) 32.12 (7.3x) 37.29 (6.3x) 56.88 (4.1x) 62.17 (3.8x) 235.66 (1.0x)
RS256 RSA 2048 large 21214 61.56 (27.5x) 37.48 (45.2x) 67.96 (24.9x) 55.80 (30.4x) 80.36 (21.1x) 148.06 (11.4x) 201.54 (8.4x) 139.75 (12.1x) 234.79 (7.2x) 1694.01 (1.0x)
RS256 RSA 3072 small 731 34.04 (3.4x) 34.27 (3.4x) 34.32 (3.4x) 34.90 (3.3x) 40.00 (2.9x) 40.13 (2.9x) 42.69 (2.7x) 65.24 (1.8x) 62.93 (1.8x) 115.81 (1.0x)
RS256 RSA 3072 typical 1131 35.14 (4.3x) 35.15 (4.3x) 35.40 (4.2x) 37.32 (4.0x) 41.61 (3.6x) 42.26 (3.6x) 47.40 (3.2x) 66.79 (2.3x) 68.36 (2.2x) 150.36 (1.0x)
RS256 RSA 3072 medium 2744 38.34 (6.9x) 36.33 (7.3x) 39.34 (6.8x) 39.44 (6.7x) 45.82 (5.8x) 51.56 (5.2x) 56.03 (4.7x) 73.88 (3.6x) 81.73 (3.3x) 265.83 (1.0x)
RS256 RSA 3072 large 21384 79.89 (21.5x) 54.97 (31.3x) 85.97 (20.0x) 73.30 (23.4x) 96.72 (17.8x) 169.60 (10.1x) 223.37 (7.7x) 161.63 (10.6x) 257.83 (6.7x) 1718.64 (1.0x)
RS256 RSA 4096 small 902 57.71 (2.7x) 57.91 (2.6x) 57.80 (2.6x) 58.73 (2.6x) 67.03 (2.3x) 66.53 (2.3x) 67.85 (2.3x) 90.23 (1.7x) 92.25 (1.7x) 153.08 (1.0x)
RS256 RSA 4096 typical 1302 58.86 (3.2x) 58.87 (3.2x) 59.29 (3.2x) 61.53 (3.0x) 68.10 (2.7x) 68.98 (2.7x) 71.34 (2.6x) 91.33 (2.0x) 94.47 (2.0x) 187.02 (1.0x)
RS256 RSA 4096 medium 2915 62.03 (5.0x) 60.60 (5.1x) 63.60 (4.8x) 63.05 (4.9x) 72.24 (4.3x) 77.86 (3.9x) 81.94 (3.8x) 99.07 (3.1x) 109.14 (2.8x) 307.40 (1.0x)
RS256 RSA 4096 large 21555 103.94 (16.9x) 79.01 (22.3x) 108.56 (16.2x) 96.85 (18.2x) 123.33 (14.3x) 192.60 (9.1x) 246.73 (7.1x) 187.13 (9.4x) 285.72 (6.2x) 1761.37 (1.0x)
ES256 P-256 small 304 53.42 (2.1x) 50.90 (2.2x) 50.76 (2.2x) 52.12 (2.1x) 53.14 (2.1x) 53.34 (2.1x) 57.34 (1.9x) 83.16 (1.3x) 89.87 (1.2x) 110.77 (1.0x)
ES256 P-256 typical 704 51.97 (2.9x) 52.16 (2.9x) 52.48 (2.8x) 54.20 (2.8x) 55.27 (2.7x) 55.68 (2.7x) 61.59 (2.4x) 85.50 (1.7x) 95.77 (1.6x) 149.10 (1.0x)
ES256 P-256 medium 2317 55.04 (4.8x) 53.44 (5.0x) 56.24 (4.7x) 56.15 (4.7x) 61.47 (4.3x) 64.34 (4.1x) 71.60 (3.7x) 90.15 (3.0x) 109.33 (2.4x) 266.63 (1.0x)
ES256 P-256 large 20957 97.37 (17.8x) 71.84 (24.1x) 101.88 (17.0x) 90.14 (19.2x) 110.41 (15.7x) 180.25 (9.6x) 237.21 (7.3x) 176.72 (9.8x) 289.64 (6.0x) 1734.80 (1.0x)
ES384 P-384 small 346 194.15 (2.7x) 193.54 (2.7x) 193.89 (2.7x) 195.20 (2.7x) 198.00 (2.7x) 198.63 (2.6x) 509.99 (1.0x) 543.65 (1.0x) 502.73 (1.0x) 526.03 (1.0x)
ES384 P-384 typical 746 195.81 (2.9x) 196.16 (2.9x) 196.30 (2.9x) 198.93 (2.8x) 200.35 (2.8x) 200.86 (2.8x) 518.05 (1.1x) 546.04 (1.0x) 508.27 (1.1x) 561.51 (1.0x)
ES384 P-384 medium 2359 200.54 (3.4x) 197.04 (3.5x) 200.74 (3.4x) 200.52 (3.4x) 203.36 (3.4x) 209.62 (3.3x) 527.12 (1.3x) 557.84 (1.2x) 524.00 (1.3x) 683.65 (1.0x)
ES384 P-384 large 20999 265.64 (8.1x) 240.43 (9.0x) 268.81 (8.0x) 255.78 (8.4x) 275.94 (7.8x) 348.50 (6.2x) 723.00 (3.0x) 663.95 (3.2x) 719.39 (3.0x) 2154.10 (1.0x)
ES512 P-521 small 394 336.78 (1.5x) 337.50 (1.5x) 337.60 (1.5x) 338.81 (1.5x) n/a n/a 1200.98 (0.4x) 1246.75 (0.4x) 495.37 (1.1x) 521.57 (1.0x)
ES512 P-521 typical 794 335.56 (1.7x) 335.90 (1.7x) 336.04 (1.7x) 337.55 (1.6x) n/a n/a 1190.93 (0.5x) 1231.73 (0.5x) 501.73 (1.1x) 556.14 (1.0x)
ES512 P-521 medium 2407 341.38 (2.0x) 339.54 (2.0x) 343.58 (2.0x) 342.54 (2.0x) n/a n/a 1189.20 (0.6x) 1228.58 (0.6x) 524.52 (1.3x) 676.84 (1.0x)
ES512 P-521 large 21047 406.28 (5.3x) 378.86 (5.7x) 410.73 (5.2x) 397.12 (5.4x) n/a n/a 1417.38 (1.5x) 1385.60 (1.6x) 716.65 (3.0x) 2150.67 (1.0x)
EdDSA Ed25519 small 306 37.47 (4.0x) 37.73 (4.0x) 37.59 (4.0x) 39.22 (3.8x) 38.96 (3.8x) 41.56 (3.6x) 58.15 (2.6x) 82.91 (1.8x) 129.93 (1.1x) 149.14 (1.0x)
EdDSA Ed25519 typical 706 38.95 (4.7x) 39.13 (4.6x) 39.39 (4.6x) 41.54 (4.4x) 41.68 (4.4x) 41.72 (4.4x) 61.91 (2.9x) 83.94 (2.2x) 132.72 (1.4x) 181.70 (1.0x)
EdDSA Ed25519 medium 2319 45.03 (6.7x) 42.13 (7.1x) 45.11 (6.7x) 45.39 (6.6x) 46.87 (6.4x) 52.64 (5.7x) 74.54 (4.0x) 93.14 (3.2x) 148.55 (2.0x) 300.97 (1.0x)
EdDSA Ed25519 large 20959 105.89 (16.5x) 80.90 (21.6x) 110.64 (15.8x) 99.11 (17.6x) 117.77 (14.8x) 185.35 (9.4x) 259.63 (6.7x) 199.43 (8.8x) 339.07 (5.2x) 1748.45 (1.0x)

JWKS URL, sync client

alg key payload token B ryjwt 0.1.1 → dict (msgspec) ryjwt 0.1.1 → Struct ryjwt 0.1.1 → dict (jiter) ryjwt 0.1.1 → BaseModel jsonwebtoken 11.1.0 (aws-lc-rs) → struct jsonwebtoken 11.1.0 (aws-lc-rs) → Value fast-jwt 6.3.3 jose 6.2.12 joserfc 1.7.5 pyjwt 2.15.1
RS256 RSA 2048 small 561 16.86 (5.4x) 16.99 (5.4x) 17.17 (5.3x) 18.02 (5.1x) n/a n/a n/a n/a n/a 91.42 (1.0x)
RS256 RSA 2048 typical 961 17.94 (7.0x) 17.80 (7.0x) 18.57 (6.7x) 19.85 (6.3x) n/a n/a n/a n/a n/a 124.83 (1.0x)
RS256 RSA 2048 medium 2574 21.26 (11.4x) 19.11 (12.7x) 22.59 (10.8x) 21.94 (11.1x) n/a n/a n/a n/a n/a 243.31 (1.0x)
RS256 RSA 2048 large 21214 61.21 (28.4x) 37.86 (45.9x) 67.97 (25.5x) 55.87 (31.1x) n/a n/a n/a n/a n/a 1736.22 (1.0x)
RS256 RSA 3072 small 731 34.41 (3.5x) 34.59 (3.5x) 34.61 (3.5x) 35.67 (3.4x) n/a n/a n/a n/a n/a 120.98 (1.0x)
RS256 RSA 3072 typical 1131 35.53 (4.4x) 35.76 (4.4x) 36.64 (4.3x) 38.08 (4.1x) n/a n/a n/a n/a n/a 156.61 (1.0x)
RS256 RSA 3072 medium 2744 39.31 (7.0x) 36.81 (7.5x) 40.20 (6.8x) 39.72 (6.9x) n/a n/a n/a n/a n/a 274.96 (1.0x)
RS256 RSA 3072 large 21384 80.03 (22.4x) 55.43 (32.3x) 86.15 (20.8x) 73.17 (24.5x) n/a n/a n/a n/a n/a 1789.65 (1.0x)
RS256 RSA 4096 small 902 58.23 (2.8x) 58.46 (2.8x) 58.39 (2.8x) 59.24 (2.7x) n/a n/a n/a n/a n/a 161.77 (1.0x)
RS256 RSA 4096 typical 1302 59.36 (3.3x) 59.32 (3.3x) 59.93 (3.3x) 62.17 (3.2x) n/a n/a n/a n/a n/a 197.62 (1.0x)
RS256 RSA 4096 medium 2915 62.56 (5.1x) 60.51 (5.3x) 63.77 (5.0x) 63.40 (5.0x) n/a n/a n/a n/a n/a 320.00 (1.0x)
RS256 RSA 4096 large 21555 104.19 (17.4x) 79.11 (23.0x) 110.10 (16.5x) 96.86 (18.7x) n/a n/a n/a n/a n/a 1815.98 (1.0x)
ES256 P-256 small 304 51.05 (2.3x) 51.41 (2.3x) 51.54 (2.3x) 52.50 (2.3x) n/a n/a n/a n/a n/a 118.19 (1.0x)
ES256 P-256 typical 704 52.49 (3.0x) 52.53 (3.0x) 53.05 (2.9x) 54.63 (2.8x) n/a n/a n/a n/a n/a 155.14 (1.0x)
ES256 P-256 medium 2317 55.60 (5.0x) 53.66 (5.2x) 56.99 (4.9x) 56.46 (4.9x) n/a n/a n/a n/a n/a 278.39 (1.0x)
ES256 P-256 large 20957 97.85 (18.3x) 72.23 (24.8x) 103.87 (17.2x) 89.85 (19.9x) n/a n/a n/a n/a n/a 1788.19 (1.0x)
ES384 P-384 small 346 194.36 (2.7x) 194.44 (2.7x) 194.07 (2.7x) 195.72 (2.7x) n/a n/a n/a n/a n/a 531.99 (1.0x)
ES384 P-384 typical 746 196.33 (2.9x) 196.28 (2.9x) 196.87 (2.9x) 199.18 (2.9x) n/a n/a n/a n/a n/a 568.56 (1.0x)
ES384 P-384 medium 2359 200.20 (3.5x) 197.87 (3.5x) 201.18 (3.5x) 200.96 (3.5x) n/a n/a n/a n/a n/a 695.07 (1.0x)
ES384 P-384 large 20999 266.23 (8.4x) 237.63 (9.4x) 269.54 (8.3x) 255.46 (8.7x) n/a n/a n/a n/a n/a 2227.97 (1.0x)
ES512 P-521 small 394 337.78 (1.6x) 338.28 (1.6x) 338.03 (1.6x) 346.28 (1.5x) n/a n/a n/a n/a n/a 527.82 (1.0x)
ES512 P-521 typical 794 336.56 (1.7x) 336.19 (1.7x) 337.40 (1.7x) 345.63 (1.6x) n/a n/a n/a n/a n/a 564.71 (1.0x)
ES512 P-521 medium 2407 341.93 (2.0x) 340.58 (2.0x) 344.59 (2.0x) 350.88 (2.0x) n/a n/a n/a n/a n/a 694.46 (1.0x)
ES512 P-521 large 21047 406.96 (5.5x) 379.87 (5.9x) 412.32 (5.4x) 407.41 (5.5x) n/a n/a n/a n/a n/a 2232.93 (1.0x)
EdDSA Ed25519 small 306 37.86 (4.1x) 38.27 (4.0x) 38.03 (4.0x) 39.59 (3.9x) n/a n/a n/a n/a n/a 153.45 (1.0x)
EdDSA Ed25519 typical 706 39.49 (4.8x) 40.29 (4.7x) 39.87 (4.7x) 41.86 (4.5x) n/a n/a n/a n/a n/a 188.00 (1.0x)
EdDSA Ed25519 medium 2319 45.29 (6.9x) 42.69 (7.3x) 45.70 (6.8x) 45.76 (6.8x) n/a n/a n/a n/a n/a 311.37 (1.0x)
EdDSA Ed25519 large 20959 105.76 (17.2x) 81.36 (22.4x) 111.68 (16.3x) 99.13 (18.4x) n/a n/a n/a n/a n/a 1819.34 (1.0x)

JWKS URL, async client

pyjwt 2.15.1 has no async JWKS client: speed-ups are vs its sync one.

alg key payload token B ryjwt 0.1.1 → dict (msgspec) ryjwt 0.1.1 → Struct ryjwt 0.1.1 → dict (jiter) ryjwt 0.1.1 → BaseModel jsonwebtoken 11.1.0 (aws-lc-rs) → struct jsonwebtoken 11.1.0 (aws-lc-rs) → Value fast-jwt 6.3.3 jose 6.2.12 joserfc 1.7.5 pyjwt 2.15.1
RS256 RSA 2048 small 561 16.95 (5.4x) 17.12 (5.3x) 17.18 (5.3x) 18.06 (5.1x) n/a n/a 70.86 (1.3x) 48.98 (1.9x) n/a n/a
RS256 RSA 2048 typical 961 18.11 (6.9x) 17.93 (7.0x) 18.51 (6.7x) 19.88 (6.3x) n/a n/a 60.90 (2.0x) 48.31 (2.6x) n/a n/a
RS256 RSA 2048 medium 2574 21.26 (11.4x) 19.24 (12.6x) 22.30 (10.9x) 22.11 (11.0x) n/a n/a 63.20 (3.8x) 55.39 (4.4x) n/a n/a
RS256 RSA 2048 large 21214 61.29 (28.3x) 38.03 (45.7x) 67.75 (25.6x) 55.64 (31.2x) n/a n/a 160.21 (10.8x) 141.35 (12.3x) n/a n/a
RS256 RSA 3072 small 731 34.48 (3.5x) 34.75 (3.5x) 34.81 (3.5x) 35.80 (3.4x) n/a n/a 91.17 (1.3x) 66.17 (1.8x) n/a n/a
RS256 RSA 3072 typical 1131 35.62 (4.4x) 35.69 (4.4x) 36.18 (4.3x) 37.63 (4.2x) n/a n/a 95.40 (1.6x) 68.26 (2.3x) n/a n/a
RS256 RSA 3072 medium 2744 39.26 (7.0x) 37.02 (7.4x) 40.09 (6.9x) 39.76 (6.9x) n/a n/a 99.77 (2.8x) 74.57 (3.7x) n/a n/a
RS256 RSA 3072 large 21384 81.61 (21.9x) 55.61 (32.2x) 86.31 (20.7x) 73.92 (24.2x) n/a n/a 202.78 (8.8x) 164.62 (10.9x) n/a n/a
RS256 RSA 4096 small 902 58.44 (2.8x) 58.42 (2.8x) 58.29 (2.8x) 59.57 (2.7x) n/a n/a 140.66 (1.2x) 89.63 (1.8x) n/a n/a
RS256 RSA 4096 typical 1302 59.33 (3.3x) 59.41 (3.3x) 59.88 (3.3x) 61.95 (3.2x) n/a n/a 141.36 (1.4x) 92.77 (2.1x) n/a n/a
RS256 RSA 4096 medium 2915 62.78 (5.1x) 60.60 (5.3x) 63.82 (5.0x) 63.58 (5.0x) n/a n/a 149.25 (2.1x) 98.50 (3.2x) n/a n/a
RS256 RSA 4096 large 21555 107.06 (17.0x) 79.79 (22.8x) 110.06 (16.5x) 96.91 (18.7x) n/a n/a 247.05 (7.4x) 202.14 (9.0x) n/a n/a
ES256 P-256 small 304 51.21 (2.3x) 51.51 (2.3x) 51.31 (2.3x) 52.58 (2.2x) n/a n/a 4041.29 (0.03x) 81.56 (1.4x) n/a n/a
ES256 P-256 typical 704 52.58 (3.0x) 52.60 (2.9x) 53.07 (2.9x) 54.63 (2.8x) n/a n/a 4007.20 (0.04x) 84.20 (1.8x) n/a n/a
ES256 P-256 medium 2317 55.77 (5.0x) 53.76 (5.2x) 56.74 (4.9x) 56.52 (4.9x) n/a n/a 4032.55 (0.07x) 91.38 (3.0x) n/a n/a
ES256 P-256 large 20957 98.80 (18.1x) 72.30 (24.7x) 103.19 (17.3x) 90.74 (19.7x) n/a n/a 4051.14 (0.4x) 187.01 (9.6x) n/a n/a
ES384 P-384 small 346 194.41 (2.7x) 194.31 (2.7x) 194.37 (2.7x) 195.68 (2.7x) n/a n/a 20481.30 (0.03x) 545.03 (1.0x) n/a n/a
ES384 P-384 typical 746 197.08 (2.9x) 197.02 (2.9x) 197.28 (2.9x) 199.26 (2.9x) n/a n/a 20569.97 (0.03x) 550.49 (1.0x) n/a n/a
ES384 P-384 medium 2359 200.26 (3.5x) 197.84 (3.5x) 201.24 (3.5x) 201.03 (3.5x) n/a n/a 20535.86 (0.03x) 561.07 (1.2x) n/a n/a
ES384 P-384 large 20999 267.11 (8.3x) 238.59 (9.3x) 270.24 (8.2x) 256.13 (8.7x) n/a n/a 20596.10 (0.1x) 679.30 (3.3x) n/a n/a
ES512 P-521 small 394 337.71 (1.6x) 338.00 (1.6x) 337.93 (1.6x) 340.14 (1.6x) n/a n/a 48832.45 (0.01x) 1245.81 (0.4x) n/a n/a
ES512 P-521 typical 794 336.56 (1.7x) 336.09 (1.7x) 337.14 (1.7x) 339.22 (1.7x) n/a n/a 48909.48 (0.01x) 1245.70 (0.5x) n/a n/a
ES512 P-521 medium 2407 343.01 (2.0x) 340.97 (2.0x) 343.98 (2.0x) 343.60 (2.0x) n/a n/a 48605.80 (0.01x) 1242.19 (0.6x) n/a n/a
ES512 P-521 large 21047 407.93 (5.5x) 379.90 (5.9x) 412.62 (5.4x) 398.17 (5.6x) n/a n/a 48263.48 (0.05x) 1395.13 (1.6x) n/a n/a
EdDSA Ed25519 small 306 38.05 (4.0x) 38.33 (4.0x) 38.07 (4.0x) 39.69 (3.9x) n/a n/a n/a 81.98 (1.9x) n/a n/a
EdDSA Ed25519 typical 706 39.95 (4.7x) 39.76 (4.7x) 39.92 (4.7x) 42.01 (4.5x) n/a n/a n/a 87.17 (2.2x) n/a n/a
EdDSA Ed25519 medium 2319 44.80 (6.9x) 42.80 (7.3x) 45.70 (6.8x) 45.86 (6.8x) n/a n/a n/a 93.66 (3.3x) n/a n/a
EdDSA Ed25519 large 20959 106.31 (17.1x) 81.48 (22.3x) 111.66 (16.3x) 99.25 (18.3x) n/a n/a n/a 208.51 (8.7x) n/a n/a

First fetch from a JWKS URL

Milliseconds from a fresh client to its first decode: connect, TLS handshake, GET the JWKS, parse it, verify (typical token, RSA 3072). The median of 5 fresh clients, each on a new connection.

key source ryjwt 0.1.1 → dict (msgspec) ryjwt 0.1.1 → Struct ryjwt 0.1.1 → dict (jiter) ryjwt 0.1.1 → BaseModel jsonwebtoken 11.1.0 (aws-lc-rs) → struct jsonwebtoken 11.1.0 (aws-lc-rs) → Value fast-jwt 6.3.3 jose 6.2.12 joserfc 1.7.5 pyjwt 2.15.1
JWKS URL, sync client 2.0 2.5 2.3 2.4 n/a n/a n/a n/a n/a 2.0
JWKS URL, async client 2.0 2.6 2.3 2.4 n/a n/a 2.0 1.2 n/a n/a

Not applicable

  • jsonwebtoken 11.1.0 (aws-lc-rs) → struct, every key source, ES512: jsonwebtoken has no ES512 (P-521).
  • jsonwebtoken 11.1.0 (aws-lc-rs) → struct, JWKS URL, sync client: jsonwebtoken doesn't fetch JWKS URLs (it takes your own HTTP client and cache).
  • jsonwebtoken 11.1.0 (aws-lc-rs) → struct, JWKS URL, async client: jsonwebtoken doesn't fetch JWKS URLs (it takes your own HTTP client and cache).
  • jsonwebtoken 11.1.0 (aws-lc-rs) → Value, every key source, ES512: jsonwebtoken has no ES512 (P-521).
  • jsonwebtoken 11.1.0 (aws-lc-rs) → Value, JWKS URL, sync client: jsonwebtoken doesn't fetch JWKS URLs (it takes your own HTTP client and cache).
  • jsonwebtoken 11.1.0 (aws-lc-rs) → Value, JWKS URL, async client: jsonwebtoken doesn't fetch JWKS URLs (it takes your own HTTP client and cache).
  • fast-jwt 6.3.3, JWKS URL, sync client: fast-jwt's JWKS support (get-jwks) is async only.
  • fast-jwt 6.3.3, JWKS URL, async client, EdDSA: get-jwks converts JWKs with jwk-to-pem, which doesn't support OKP (Ed25519) keys.
  • jose 6.2.12, JWKS URL, sync client: jose's API is async only.
  • joserfc 1.7.5, JWKS URL, sync client: joserfc has no JWKS URL client.
  • joserfc 1.7.5, JWKS URL, async client: joserfc has no JWKS URL client.
  • pyjwt 2.15.1, JWKS URL, async client: PyJWT has no async JWKS client.

Notes

  • ryjwt 0.1.1 → dict (msgspec): SecretKey, PublicKey, PublicKey.from_jwks, JWKSClient.decode and .adecode.
  • ryjwt 0.1.1 → dict (msgspec): JWKS URL: trusting the test CA through SSL_CERT_FILE.
  • ryjwt 0.1.1 → Struct: SecretKey, PublicKey, PublicKey.from_jwks, JWKSClient.decode and .adecode.
  • ryjwt 0.1.1 → Struct: JWKS URL: trusting the test CA through SSL_CERT_FILE.
  • ryjwt 0.1.1 → dict (jiter): SecretKey, PublicKey, PublicKey.from_jwks, JWKSClient.decode and .adecode.
  • ryjwt 0.1.1 → dict (jiter): JWKS URL: trusting the test CA through SSL_CERT_FILE.
  • ryjwt 0.1.1 → BaseModel: SecretKey, PublicKey, PublicKey.from_jwks, JWKSClient.decode and .adecode.
  • ryjwt 0.1.1 → BaseModel: JWKS URL: trusting the test CA through SSL_CERT_FILE.
  • jsonwebtoken 11.1.0 (aws-lc-rs) → struct: JWKS: each key turned into a DecodingKey once, picked per token by kid (decode_header).
  • jsonwebtoken 11.1.0 (aws-lc-rs) → struct: struct: claims decoded into typed structs with the fields ryjwt's msgspec lane decodes; other claims ignored.
  • jsonwebtoken 11.1.0 (aws-lc-rs) → Value: JWKS: each key turned into a DecodingKey once, picked per token by kid (decode_header).
  • fast-jwt 6.3.3: JWKS document: a verifier per key, made once, picked by the token's kid (read with createDecoder).
  • fast-jwt 6.3.3: JWKS URL rows are fast-jwt + get-jwks 11.0.3 (fast-jwt's documented integration), so they time both: on every verify get-jwks converts the cached JWK to a PEM (with jwk-to-pem) and fast-jwt imports that PEM (createPublicKey), which is why they're slow.
  • jose 6.2.12: HMAC secret imported once (crypto.subtle.importKey); importSPKI, createLocalJWKSet, createRemoteJWKSet.
  • joserfc 1.7.5: jwt.decode(token, key, algorithms=[...]), then JWTClaimsRegistry(exp=..., aud=...).validate(token.claims), both essential.
  • joserfc 1.7.5: keys imported once (OctKey, RSAKey, ECKey, OKPKey); JWKS document: a KeySet, which picks the key by kid.
  • joserfc 1.7.5: EdDSA: joserfc warns on every decode (SecurityWarning: RFC 9864 deprecates EdDSA), as it does by default.
  • pyjwt 2.15.1: PEM: the key loaded once, with load_pem_public_key.
  • pyjwt 2.15.1: JWKS document: PyJWKSet, picking the key by get_unverified_header's kid.
  • pyjwt 2.15.1: JWKS URL: PyJWKClient(cache_keys=True), its ssl_context trusting the test CA.