Skip to content

JWT decode benchmarks: the full matrix

Generated 2026-10-10 from bench/results-matrix/ by bench/compare.py, on Neoverse-N2 (aarch64), 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 → Struct ryjwt 0.1.1 → dict (msgspec) ryjwt 0.1.1 → dict (jiter) jsonwebtoken 11.1.0 (aws-lc-rs) → struct ryjwt 0.1.1 → BaseModel jsonwebtoken 11.1.0 (aws-lc-rs) → Value fast-jwt 6.3.3 joserfc 1.7.5 jose 6.2.12 pyjwt 2.15.1
HMAC secret HS256 64 B 2.07 (28.6x) 2.18 (27.2x) 2.57 (23.1x) 3.81 (15.5x) 4.38 (13.5x) 4.80 (12.4x) 7.10 (8.3x) 27.88 (2.1x) 38.16 (1.6x) 59.24 (1.0x)
PEM public key RS256 RSA 3072 70.29 (1.7x) 70.50 (1.7x) 70.71 (1.7x) 77.02 (1.5x) 72.67 (1.6x) 77.81 (1.5x) 48.93 (2.4x) 75.15 (1.6x) 79.29 (1.5x) 117.42 (1.0x)
PEM public key ES384 P-384 252.31 (1.5x) 252.81 (1.5x) 252.42 (1.5x) 255.26 (1.5x) 254.39 (1.5x) 256.52 (1.5x) 485.72 (0.8x) 354.61 (1.1x) 542.04 (0.7x) 382.31 (1.0x)
PEM public key EdDSA Ed25519 56.85 (2.3x) 56.91 (2.3x) 57.28 (2.3x) 58.38 (2.2x) 59.15 (2.2x) 58.76 (2.2x) 53.66 (2.4x) 108.03 (1.2x) 87.45 (1.5x) 129.51 (1.0x)
JWKS document RS256 RSA 3072 70.25 (2.5x) 70.58 (2.5x) 70.66 (2.5x) 78.18 (2.2x) 72.76 (2.4x) 78.68 (2.2x) 54.08 (3.2x) 79.72 (2.2x) 81.91 (2.1x) 175.08 (1.0x)
JWKS URL, sync client RS256 RSA 3072 70.71 (2.6x) 71.47 (2.6x) 71.46 (2.6x) n/a 73.07 (2.5x) n/a n/a n/a n/a 183.54 (1.0x)
JWKS URL, async client RS256 RSA 3072 70.79 (2.6x) 71.11 (2.6x) 71.35 (2.6x) n/a 73.21 (2.5x) n/a 105.41 (1.7x) n/a 82.46 (2.2x) n/a

HMAC secret

alg key payload token B ryjwt 0.1.1 → Struct ryjwt 0.1.1 → dict (msgspec) ryjwt 0.1.1 → dict (jiter) jsonwebtoken 11.1.0 (aws-lc-rs) → struct ryjwt 0.1.1 → BaseModel jsonwebtoken 11.1.0 (aws-lc-rs) → Value fast-jwt 6.3.3 joserfc 1.7.5 jose 6.2.12 pyjwt 2.15.1
HS256 32 B small 231 1.13 (32.0x) 1.06 (34.2x) 1.18 (30.8x) 1.94 (18.7x) 2.30 (15.8x) 2.29 (15.8x) 11.23 (3.2x) 21.84 (1.7x) 45.00 (0.8x) 36.22 (1.0x)
HS256 32 B typical 661 2.08 (28.2x) 2.19 (26.7x) 2.61 (22.4x) 3.76 (15.6x) 4.38 (13.4x) 4.71 (12.4x) 10.05 (5.8x) 27.90 (2.1x) 45.49 (1.3x) 58.57 (1.0x)
HS256 32 B medium 2244 3.72 (37.1x) 6.07 (22.7x) 7.13 (19.3x) 7.48 (18.4x) 6.34 (21.7x) 13.26 (10.4x) 13.99 (9.9x) 45.73 (3.0x) 49.67 (2.8x) 137.82 (1.0x)
HS256 32 B large 20884 26.83 (39.1x) 59.26 (17.7x) 61.37 (17.1x) 58.20 (18.0x) 40.62 (25.8x) 127.07 (8.3x) 117.08 (9.0x) 261.30 (4.0x) 171.83 (6.1x) 1048.89 (1.0x)
HS256 64 B small 231 1.12 (32.8x) 1.07 (34.4x) 1.18 (31.3x) 1.93 (19.1x) 2.28 (16.1x) 2.29 (16.1x) 4.94 (7.5x) 21.78 (1.7x) 35.10 (1.0x) 36.83 (1.0x)
HS256 64 B typical 661 2.07 (28.6x) 2.18 (27.2x) 2.57 (23.1x) 3.81 (15.5x) 4.38 (13.5x) 4.80 (12.4x) 7.10 (8.3x) 27.88 (2.1x) 38.16 (1.6x) 59.24 (1.0x)
HS256 64 B medium 2244 3.72 (37.1x) 6.07 (22.7x) 7.15 (19.3x) 7.53 (18.3x) 6.33 (21.8x) 13.34 (10.3x) 19.68 (7.0x) 45.34 (3.0x) 48.08 (2.9x) 137.93 (1.0x)
HS256 64 B large 20884 26.83 (39.0x) 59.44 (17.6x) 61.90 (16.9x) 58.25 (18.0x) 40.68 (25.7x) 126.89 (8.2x) 113.57 (9.2x) 262.18 (4.0x) 165.49 (6.3x) 1046.66 (1.0x)
HS256 256 B small 231 1.13 (34.4x) 1.06 (36.6x) 1.17 (33.1x) 2.22 (17.5x) 2.28 (17.0x) 2.45 (15.8x) 4.65 (8.3x) 21.77 (1.8x) 33.34 (1.2x) 38.76 (1.0x)
HS256 256 B typical 661 2.06 (29.8x) 2.19 (28.0x) 2.59 (23.8x) 3.97 (15.5x) 4.36 (14.1x) 4.95 (12.4x) 7.11 (8.6x) 28.09 (2.2x) 38.78 (1.6x) 61.41 (1.0x)
HS256 256 B medium 2244 3.71 (37.9x) 6.07 (23.1x) 7.13 (19.7x) 7.77 (18.1x) 6.35 (22.1x) 13.43 (10.5x) 12.55 (11.2x) 46.00 (3.1x) 49.34 (2.8x) 140.47 (1.0x)
HS256 256 B large 20884 26.81 (39.2x) 58.88 (17.9x) 61.46 (17.1x) 58.40 (18.0x) 40.71 (25.8x) 126.71 (8.3x) 113.63 (9.3x) 261.87 (4.0x) 164.80 (6.4x) 1051.55 (1.0x)
HS256 4096 B small 231 1.13 (67.9x) 1.06 (72.3x) 1.17 (65.6x) 3.94 (19.4x) 2.29 (33.4x) 4.24 (18.1x) 6.43 (11.9x) 23.73 (3.2x) 38.80 (2.0x) 76.57 (1.0x)
HS256 4096 B typical 661 2.08 (48.3x) 2.18 (46.0x) 2.59 (38.7x) 5.75 (17.5x) 4.36 (23.0x) 6.73 (14.9x) 8.92 (11.3x) 29.69 (3.4x) 44.47 (2.3x) 100.36 (1.0x)
HS256 4096 B medium 2244 3.71 (48.0x) 6.04 (29.5x) 7.14 (24.9x) 9.52 (18.7x) 6.33 (28.2x) 15.37 (11.6x) 15.44 (11.5x) 47.45 (3.8x) 50.82 (3.5x) 178.20 (1.0x)
HS256 4096 B large 20884 27.33 (39.9x) 59.08 (18.4x) 61.37 (17.8x) 60.15 (18.1x) 40.69 (26.8x) 129.00 (8.4x) 114.84 (9.5x) 263.70 (4.1x) 166.46 (6.5x) 1089.82 (1.0x)

PEM public key

alg key payload token B ryjwt 0.1.1 → Struct ryjwt 0.1.1 → dict (msgspec) ryjwt 0.1.1 → dict (jiter) jsonwebtoken 11.1.0 (aws-lc-rs) → struct ryjwt 0.1.1 → BaseModel jsonwebtoken 11.1.0 (aws-lc-rs) → Value fast-jwt 6.3.3 joserfc 1.7.5 jose 6.2.12 pyjwt 2.15.1
RS256 RSA 2048 small 561 29.40 (2.2x) 29.66 (2.2x) 29.53 (2.2x) 33.43 (2.0x) 30.69 (2.2x) 33.78 (2.0x) 25.83 (2.6x) 45.99 (1.4x) 54.62 (1.2x) 66.03 (1.0x)
RS256 RSA 2048 typical 961 30.36 (2.8x) 30.53 (2.8x) 31.14 (2.8x) 35.02 (2.5x) 32.84 (2.6x) 35.75 (2.4x) 29.06 (3.0x) 52.01 (1.7x) 60.28 (1.4x) 86.50 (1.0x)
RS256 RSA 2048 medium 2574 32.01 (5.1x) 34.54 (4.8x) 35.48 (4.6x) 39.05 (4.2x) 34.78 (4.7x) 44.92 (3.7x) 36.03 (4.6x) 70.36 (2.3x) 77.94 (2.1x) 164.66 (1.0x)
RS256 RSA 2048 large 21214 55.15 (19.3x) 88.01 (12.1x) 90.01 (11.8x) 89.77 (11.9x) 68.92 (15.4x) 158.76 (6.7x) 137.00 (7.8x) 292.28 (3.6x) 190.74 (5.6x) 1063.92 (1.0x)
RS256 RSA 3072 small 731 69.31 (1.4x) 69.19 (1.4x) 69.51 (1.4x) 76.00 (1.3x) 70.56 (1.4x) 75.93 (1.3x) 46.32 (2.1x) 69.18 (1.4x) 75.21 (1.3x) 96.63 (1.0x)
RS256 RSA 3072 typical 1131 70.29 (1.7x) 70.50 (1.7x) 70.71 (1.7x) 77.02 (1.5x) 72.67 (1.6x) 77.81 (1.5x) 48.93 (2.4x) 75.15 (1.6x) 79.29 (1.5x) 117.42 (1.0x)
RS256 RSA 3072 medium 2744 72.45 (2.7x) 74.63 (2.6x) 75.29 (2.6x) 81.21 (2.4x) 74.63 (2.6x) 87.28 (2.2x) 55.24 (3.5x) 93.26 (2.1x) 90.73 (2.1x) 194.78 (1.0x)
RS256 RSA 3072 large 21384 95.20 (11.6x) 127.72 (8.7x) 129.95 (8.5x) 131.91 (8.4x) 109.03 (10.1x) 200.93 (5.5x) 165.19 (6.7x) 317.45 (3.5x) 211.96 (5.2x) 1104.98 (1.0x)
RS256 RSA 4096 small 902 119.49 (1.1x) 118.79 (1.1x) 118.52 (1.1x) 127.94 (1.1x) 119.85 (1.1x) 128.71 (1.1x) 76.05 (1.8x) 100.83 (1.3x) 104.19 (1.3x) 136.03 (1.0x)
RS256 RSA 4096 typical 1302 120.04 (1.3x) 119.66 (1.3x) 120.51 (1.3x) 129.70 (1.2x) 122.06 (1.3x) 130.83 (1.2x) 79.05 (2.0x) 106.65 (1.5x) 108.49 (1.4x) 156.48 (1.0x)
RS256 RSA 4096 medium 2915 121.33 (1.9x) 123.80 (1.9x) 125.00 (1.9x) 133.88 (1.7x) 124.01 (1.9x) 139.44 (1.7x) 85.13 (2.7x) 125.25 (1.9x) 120.33 (1.9x) 234.08 (1.0x)
RS256 RSA 4096 large 21555 144.70 (7.8x) 177.77 (6.4x) 179.68 (6.3x) 184.45 (6.2x) 158.61 (7.2x) 253.52 (4.5x) 188.51 (6.0x) 352.70 (3.2x) 243.74 (4.7x) 1135.21 (1.0x)
ES256 P-256 small 304 53.20 (2.0x) 53.06 (2.0x) 53.30 (2.0x) 54.68 (1.9x) 54.43 (1.9x) 54.99 (1.9x) 59.67 (1.8x) 94.11 (1.1x) 91.15 (1.2x) 105.83 (1.0x)
ES256 P-256 typical 704 54.49 (2.3x) 54.50 (2.3x) 55.33 (2.3x) 56.56 (2.3x) 56.88 (2.2x) 57.59 (2.2x) 61.19 (2.1x) 100.29 (1.3x) 96.09 (1.3x) 127.85 (1.0x)
ES256 P-256 medium 2317 55.86 (3.8x) 58.60 (3.6x) 59.74 (3.5x) 60.35 (3.5x) 58.68 (3.6x) 66.19 (3.2x) 67.93 (3.1x) 123.88 (1.7x) 107.61 (2.0x) 209.87 (1.0x)
ES256 P-256 large 20957 79.22 (14.1x) 112.39 (9.9x) 113.84 (9.8x) 111.45 (10.0x) 93.08 (12.0x) 180.75 (6.2x) 171.65 (6.5x) 354.63 (3.1x) 229.37 (4.9x) 1115.08 (1.0x)
ES384 P-384 small 346 250.52 (1.4x) 250.15 (1.4x) 250.21 (1.4x) 252.71 (1.4x) 251.15 (1.4x) 252.91 (1.4x) 480.35 (0.7x) 346.88 (1.0x) 526.63 (0.7x) 359.26 (1.0x)
ES384 P-384 typical 746 252.31 (1.5x) 252.81 (1.5x) 252.42 (1.5x) 255.26 (1.5x) 254.39 (1.5x) 256.52 (1.5x) 485.72 (0.8x) 354.61 (1.1x) 542.04 (0.7x) 382.31 (1.0x)
ES384 P-384 medium 2359 252.11 (1.8x) 254.62 (1.8x) 256.03 (1.8x) 258.03 (1.8x) 254.71 (1.8x) 264.22 (1.7x) 492.61 (0.9x) 374.29 (1.2x) 544.59 (0.8x) 459.46 (1.0x)
ES384 P-384 large 20999 282.09 (4.9x) 315.65 (4.4x) 317.16 (4.3x) 315.11 (4.4x) 296.07 (4.6x) 385.94 (3.6x) 606.53 (2.3x) 618.67 (2.2x) 674.60 (2.0x) 1374.12 (1.0x)
ES512 P-521 small 394 504.93 (1.0x) 503.99 (1.0x) 503.67 (1.0x) n/a 504.96 (1.0x) n/a 1255.92 (0.4x) 492.20 (1.0x) 1321.00 (0.4x) 499.10 (1.0x)
ES512 P-521 typical 794 500.63 (1.0x) 500.68 (1.0x) 500.29 (1.0x) n/a 502.40 (1.0x) n/a 1242.05 (0.4x) 498.71 (1.0x) 1311.14 (0.4x) 522.52 (1.0x)
ES512 P-521 medium 2407 504.45 (1.2x) 507.22 (1.2x) 507.90 (1.2x) n/a 506.54 (1.2x) n/a 1237.75 (0.5x) 521.38 (1.2x) 1314.28 (0.5x) 601.79 (1.0x)
ES512 P-521 large 21047 532.41 (2.9x) 565.84 (2.7x) 567.91 (2.7x) n/a 546.15 (2.8x) n/a 1380.91 (1.1x) 763.47 (2.0x) 1468.85 (1.0x) 1518.28 (1.0x)
EdDSA Ed25519 small 306 55.73 (2.0x) 55.83 (2.0x) 56.16 (2.0x) 56.66 (1.9x) 56.92 (1.9x) 56.79 (1.9x) 51.23 (2.1x) 103.36 (1.1x) 83.55 (1.3x) 110.14 (1.0x)
EdDSA Ed25519 typical 706 56.85 (2.3x) 56.91 (2.3x) 57.28 (2.3x) 58.38 (2.2x) 59.15 (2.2x) 58.76 (2.2x) 53.66 (2.4x) 108.03 (1.2x) 87.45 (1.5x) 129.51 (1.0x)
EdDSA Ed25519 medium 2319 59.10 (3.5x) 61.35 (3.4x) 62.39 (3.3x) 62.72 (3.3x) 61.61 (3.4x) 68.34 (3.0x) 61.98 (3.4x) 127.64 (1.6x) 98.92 (2.1x) 207.70 (1.0x)
EdDSA Ed25519 large 20959 87.49 (12.7x) 120.25 (9.3x) 122.28 (9.1x) 119.61 (9.3x) 101.72 (10.9x) 188.31 (5.9x) 169.71 (6.6x) 359.94 (3.1x) 229.10 (4.9x) 1113.81 (1.0x)

JWKS document

alg key payload token B ryjwt 0.1.1 → Struct ryjwt 0.1.1 → dict (msgspec) ryjwt 0.1.1 → dict (jiter) jsonwebtoken 11.1.0 (aws-lc-rs) → struct ryjwt 0.1.1 → BaseModel jsonwebtoken 11.1.0 (aws-lc-rs) → Value fast-jwt 6.3.3 joserfc 1.7.5 jose 6.2.12 pyjwt 2.15.1
RS256 RSA 2048 small 561 29.40 (3.4x) 29.79 (3.3x) 29.42 (3.4x) 34.11 (2.9x) 30.72 (3.2x) 34.13 (2.9x) 28.96 (3.4x) 50.11 (2.0x) 60.50 (1.6x) 98.74 (1.0x)
RS256 RSA 2048 typical 961 30.52 (4.5x) 30.51 (4.5x) 30.84 (4.5x) 35.50 (3.9x) 32.82 (4.2x) 36.55 (3.8x) 31.65 (4.4x) 55.70 (2.5x) 65.25 (2.1x) 138.31 (1.0x)
RS256 RSA 2048 medium 2574 32.01 (9.0x) 34.71 (8.3x) 35.50 (8.1x) 40.21 (7.1x) 34.78 (8.2x) 46.58 (6.2x) 43.44 (6.6x) 74.86 (3.8x) 73.61 (3.9x) 286.59 (1.0x)
RS256 RSA 2048 large 21214 55.23 (36.0x) 88.43 (22.5x) 90.35 (22.0x) 97.50 (20.4x) 68.94 (28.8x) 166.50 (11.9x) 239.92 (8.3x) 306.80 (6.5x) 197.40 (10.1x) 1987.66 (1.0x)
RS256 RSA 3072 small 731 69.27 (2.0x) 69.42 (2.0x) 69.28 (2.0x) 75.95 (1.8x) 70.55 (1.9x) 76.27 (1.8x) 50.01 (2.7x) 73.21 (1.9x) 76.12 (1.8x) 137.16 (1.0x)
RS256 RSA 3072 typical 1131 70.25 (2.5x) 70.58 (2.5x) 70.66 (2.5x) 78.18 (2.2x) 72.76 (2.4x) 78.68 (2.2x) 54.08 (3.2x) 79.72 (2.2x) 81.91 (2.1x) 175.08 (1.0x)
RS256 RSA 3072 medium 2744 72.37 (4.5x) 74.32 (4.3x) 75.32 (4.3x) 82.57 (3.9x) 74.62 (4.3x) 88.53 (3.6x) 65.20 (4.9x) 98.64 (3.3x) 95.51 (3.4x) 322.44 (1.0x)
RS256 RSA 3072 large 21384 95.12 (21.2x) 128.01 (15.8x) 129.84 (15.6x) 139.59 (14.5x) 109.16 (18.5x) 209.44 (9.6x) 260.80 (7.7x) 333.97 (6.0x) 217.28 (9.3x) 2019.15 (1.0x)
RS256 RSA 4096 small 902 118.66 (1.5x) 118.54 (1.5x) 118.65 (1.5x) 128.66 (1.4x) 119.91 (1.5x) 129.36 (1.4x) 80.32 (2.3x) 105.46 (1.7x) 107.22 (1.7x) 183.31 (1.0x)
RS256 RSA 4096 typical 1302 119.75 (1.9x) 119.67 (1.9x) 120.86 (1.8x) 130.68 (1.7x) 122.08 (1.8x) 131.85 (1.7x) 83.24 (2.7x) 111.41 (2.0x) 112.73 (2.0x) 222.11 (1.0x)
RS256 RSA 4096 medium 2915 121.32 (3.1x) 123.84 (3.0x) 124.77 (3.0x) 135.36 (2.7x) 123.97 (3.0x) 141.23 (2.6x) 96.18 (3.9x) 130.11 (2.8x) 126.37 (2.9x) 370.48 (1.0x)
RS256 RSA 4096 large 21555 144.54 (14.3x) 177.63 (11.6x) 179.21 (11.5x) 192.97 (10.7x) 158.50 (13.0x) 260.95 (7.9x) 292.07 (7.1x) 366.34 (5.6x) 249.68 (8.3x) 2064.56 (1.0x)
ES256 P-256 small 304 53.60 (2.4x) 53.33 (2.4x) 53.46 (2.4x) 55.08 (2.3x) 54.49 (2.4x) 55.49 (2.3x) 61.25 (2.1x) 99.18 (1.3x) 93.64 (1.4x) 128.39 (1.0x)
ES256 P-256 typical 704 54.81 (3.1x) 54.50 (3.1x) 54.84 (3.1x) 57.08 (2.9x) 56.84 (3.0x) 57.87 (2.9x) 66.04 (2.5x) 108.01 (1.6x) 99.49 (1.7x) 168.37 (1.0x)
ES256 P-256 medium 2317 55.85 (5.8x) 58.29 (5.5x) 59.28 (5.4x) 61.84 (5.2x) 58.62 (5.5x) 67.75 (4.7x) 77.06 (4.2x) 134.50 (2.4x) 109.39 (2.9x) 321.37 (1.0x)
ES256 P-256 large 20957 79.49 (25.5x) 111.88 (18.1x) 114.44 (17.7x) 119.51 (17.0x) 93.37 (21.7x) 188.14 (10.8x) 275.08 (7.4x) 375.74 (5.4x) 237.02 (8.6x) 2027.49 (1.0x)
ES384 P-384 small 346 251.00 (1.5x) 250.15 (1.5x) 250.69 (1.5x) 253.32 (1.5x) 251.06 (1.5x) 253.41 (1.5x) 484.29 (0.8x) 352.74 (1.1x) 530.73 (0.7x) 385.65 (1.0x)
ES384 P-384 typical 746 252.49 (1.7x) 252.66 (1.7x) 252.87 (1.7x) 255.78 (1.7x) 254.61 (1.7x) 256.83 (1.7x) 490.20 (0.9x) 359.60 (1.2x) 537.89 (0.8x) 425.97 (1.0x)
ES384 P-384 medium 2359 252.18 (2.3x) 254.87 (2.3x) 255.55 (2.3x) 259.82 (2.2x) 254.75 (2.3x) 264.45 (2.2x) 502.72 (1.1x) 380.72 (1.5x) 553.13 (1.0x) 576.51 (1.0x)
ES384 P-384 large 20999 282.08 (8.1x) 315.26 (7.2x) 316.92 (7.2x) 322.57 (7.1x) 296.12 (7.7x) 391.87 (5.8x) 706.07 (3.2x) 634.95 (3.6x) 681.46 (3.3x) 2281.15 (1.0x)
ES512 P-521 small 394 504.27 (1.0x) 504.75 (1.0x) 505.30 (1.0x) n/a 505.09 (1.0x) n/a 1261.06 (0.4x) 495.65 (1.1x) 1329.69 (0.4x) 529.13 (1.0x)
ES512 P-521 typical 794 500.94 (1.1x) 501.14 (1.1x) 500.56 (1.1x) n/a 502.19 (1.1x) n/a 1249.85 (0.5x) 508.53 (1.1x) 1315.93 (0.4x) 565.86 (1.0x)
ES512 P-521 medium 2407 505.33 (1.4x) 507.22 (1.4x) 507.46 (1.4x) n/a 506.56 (1.4x) n/a 1250.06 (0.6x) 528.03 (1.4x) 1320.01 (0.5x) 715.48 (1.0x)
ES512 P-521 large 21047 532.90 (4.6x) 566.77 (4.3x) 568.05 (4.3x) n/a 546.32 (4.4x) n/a 1480.23 (1.6x) 777.81 (3.1x) 1474.58 (1.6x) 2424.82 (1.0x)
EdDSA Ed25519 small 306 55.91 (2.4x) 56.09 (2.3x) 55.73 (2.4x) 57.20 (2.3x) 56.94 (2.3x) 57.16 (2.3x) 54.47 (2.4x) 106.70 (1.2x) 86.65 (1.5x) 131.65 (1.0x)
EdDSA Ed25519 typical 706 56.75 (3.0x) 56.90 (3.0x) 57.29 (3.0x) 59.11 (2.9x) 59.13 (2.9x) 59.82 (2.8x) 57.69 (2.9x) 111.60 (1.5x) 90.12 (1.9x) 169.11 (1.0x)
EdDSA Ed25519 medium 2319 59.00 (5.4x) 61.27 (5.2x) 62.40 (5.1x) 63.80 (5.0x) 61.65 (5.2x) 69.86 (4.5x) 69.75 (4.6x) 132.11 (2.4x) 101.97 (3.1x) 317.86 (1.0x)
EdDSA Ed25519 large 20959 87.42 (23.1x) 121.22 (16.6x) 122.55 (16.5x) 126.72 (15.9x) 101.68 (19.8x) 196.72 (10.3x) 270.15 (7.5x) 370.72 (5.4x) 233.37 (8.6x) 2017.96 (1.0x)

JWKS URL, sync client

alg key payload token B ryjwt 0.1.1 → Struct ryjwt 0.1.1 → dict (msgspec) ryjwt 0.1.1 → dict (jiter) jsonwebtoken 11.1.0 (aws-lc-rs) → struct ryjwt 0.1.1 → BaseModel jsonwebtoken 11.1.0 (aws-lc-rs) → Value fast-jwt 6.3.3 joserfc 1.7.5 jose 6.2.12 pyjwt 2.15.1
RS256 RSA 2048 small 561 29.87 (3.5x) 29.77 (3.5x) 29.84 (3.5x) n/a 31.04 (3.4x) n/a n/a n/a n/a 104.95 (1.0x)
RS256 RSA 2048 typical 961 30.77 (4.7x) 30.94 (4.7x) 31.76 (4.6x) n/a 33.16 (4.4x) n/a n/a n/a n/a 145.09 (1.0x)
RS256 RSA 2048 medium 2574 32.41 (9.2x) 34.93 (8.6x) 36.12 (8.3x) n/a 35.17 (8.5x) n/a n/a n/a n/a 299.54 (1.0x)
RS256 RSA 2048 large 21214 55.71 (37.3x) 88.28 (23.5x) 90.48 (23.0x) n/a 69.68 (29.8x) n/a n/a n/a n/a 2078.67 (1.0x)
RS256 RSA 3072 small 731 69.81 (2.0x) 69.63 (2.0x) 69.81 (2.0x) n/a 70.89 (2.0x) n/a n/a n/a n/a 142.64 (1.0x)
RS256 RSA 3072 typical 1131 70.71 (2.6x) 71.47 (2.6x) 71.46 (2.6x) n/a 73.07 (2.5x) n/a n/a n/a n/a 183.54 (1.0x)
RS256 RSA 3072 medium 2744 72.26 (4.7x) 74.89 (4.5x) 75.88 (4.4x) n/a 75.03 (4.5x) n/a n/a n/a n/a 336.50 (1.0x)
RS256 RSA 3072 large 21384 95.50 (22.1x) 128.41 (16.4x) 130.43 (16.2x) n/a 109.51 (19.3x) n/a n/a n/a n/a 2109.23 (1.0x)
RS256 RSA 4096 small 902 119.10 (1.6x) 119.15 (1.6x) 119.25 (1.6x) n/a 120.27 (1.6x) n/a n/a n/a n/a 190.24 (1.0x)
RS256 RSA 4096 typical 1302 119.96 (1.9x) 120.13 (1.9x) 120.61 (1.9x) n/a 122.45 (1.9x) n/a n/a n/a n/a 229.80 (1.0x)
RS256 RSA 4096 medium 2915 121.79 (3.1x) 124.28 (3.1x) 125.35 (3.0x) n/a 124.33 (3.1x) n/a n/a n/a n/a 380.35 (1.0x)
RS256 RSA 4096 large 21555 145.14 (14.9x) 177.40 (12.2x) 180.39 (12.0x) n/a 158.85 (13.6x) n/a n/a n/a n/a 2161.30 (1.0x)
ES256 P-256 small 304 53.54 (2.5x) 53.47 (2.5x) 53.58 (2.5x) n/a 54.83 (2.4x) n/a n/a n/a n/a 131.28 (1.0x)
ES256 P-256 typical 704 54.81 (3.2x) 55.12 (3.2x) 55.31 (3.2x) n/a 57.25 (3.1x) n/a n/a n/a n/a 174.96 (1.0x)
ES256 P-256 medium 2317 56.26 (5.9x) 59.07 (5.7x) 59.89 (5.6x) n/a 58.95 (5.7x) n/a n/a n/a n/a 333.79 (1.0x)
ES256 P-256 large 20957 79.70 (26.6x) 112.53 (18.9x) 114.82 (18.5x) n/a 93.60 (22.7x) n/a n/a n/a n/a 2122.78 (1.0x)
ES384 P-384 small 346 250.29 (1.6x) 250.81 (1.6x) 251.01 (1.6x) n/a 251.43 (1.6x) n/a n/a n/a n/a 391.33 (1.0x)
ES384 P-384 typical 746 253.29 (1.7x) 252.26 (1.7x) 253.08 (1.7x) n/a 254.98 (1.7x) n/a n/a n/a n/a 434.12 (1.0x)
ES384 P-384 medium 2359 252.73 (2.3x) 255.22 (2.3x) 255.92 (2.3x) n/a 255.06 (2.3x) n/a n/a n/a n/a 588.93 (1.0x)
ES384 P-384 large 20999 282.94 (8.4x) 316.31 (7.5x) 317.50 (7.5x) n/a 296.52 (8.0x) n/a n/a n/a n/a 2368.80 (1.0x)
ES512 P-521 small 394 504.37 (1.1x) 504.11 (1.1x) 504.45 (1.1x) n/a 505.13 (1.1x) n/a n/a n/a n/a 538.10 (1.0x)
ES512 P-521 typical 794 501.00 (1.2x) 501.21 (1.2x) 501.15 (1.2x) n/a 502.51 (1.2x) n/a n/a n/a n/a 582.76 (1.0x)
ES512 P-521 medium 2407 505.06 (1.5x) 507.41 (1.5x) 508.73 (1.4x) n/a 506.62 (1.5x) n/a n/a n/a n/a 736.02 (1.0x)
ES512 P-521 large 21047 533.50 (4.7x) 567.37 (4.5x) 568.61 (4.5x) n/a 546.77 (4.6x) n/a n/a n/a n/a 2533.26 (1.0x)
EdDSA Ed25519 small 306 56.54 (2.4x) 56.05 (2.4x) 56.06 (2.4x) n/a 57.33 (2.4x) n/a n/a n/a n/a 136.97 (1.0x)
EdDSA Ed25519 typical 706 57.19 (3.1x) 57.47 (3.1x) 57.64 (3.1x) n/a 59.50 (3.0x) n/a n/a n/a n/a 175.91 (1.0x)
EdDSA Ed25519 medium 2319 59.40 (5.5x) 62.00 (5.3x) 62.84 (5.2x) n/a 62.02 (5.3x) n/a n/a n/a n/a 329.59 (1.0x)
EdDSA Ed25519 large 20959 87.99 (23.9x) 121.72 (17.3x) 122.85 (17.2x) n/a 102.25 (20.6x) n/a n/a n/a n/a 2106.90 (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 → Struct ryjwt 0.1.1 → dict (msgspec) ryjwt 0.1.1 → dict (jiter) jsonwebtoken 11.1.0 (aws-lc-rs) → struct ryjwt 0.1.1 → BaseModel jsonwebtoken 11.1.0 (aws-lc-rs) → Value fast-jwt 6.3.3 joserfc 1.7.5 jose 6.2.12 pyjwt 2.15.1
RS256 RSA 2048 small 561 30.32 (3.5x) 29.85 (3.5x) 29.91 (3.5x) n/a 31.16 (3.4x) n/a 78.98 (1.3x) n/a 58.27 (1.8x) n/a
RS256 RSA 2048 typical 961 30.91 (4.7x) 31.02 (4.7x) 31.40 (4.6x) n/a 33.29 (4.4x) n/a 67.89 (2.1x) n/a 61.61 (2.4x) n/a
RS256 RSA 2048 medium 2574 32.50 (9.2x) 35.02 (8.6x) 36.09 (8.3x) n/a 35.25 (8.5x) n/a 72.13 (4.2x) n/a 75.13 (4.0x) n/a
RS256 RSA 2048 large 21214 55.81 (37.2x) 88.92 (23.4x) 90.61 (22.9x) n/a 69.89 (29.7x) n/a 197.28 (10.5x) n/a 198.50 (10.5x) n/a
RS256 RSA 3072 small 731 69.79 (2.0x) 69.90 (2.0x) 69.81 (2.0x) n/a 71.00 (2.0x) n/a 99.67 (1.4x) n/a 77.94 (1.8x) n/a
RS256 RSA 3072 typical 1131 70.79 (2.6x) 71.11 (2.6x) 71.35 (2.6x) n/a 73.21 (2.5x) n/a 105.41 (1.7x) n/a 82.46 (2.2x) n/a
RS256 RSA 3072 medium 2744 72.55 (4.6x) 75.04 (4.5x) 75.83 (4.4x) n/a 75.10 (4.5x) n/a 116.73 (2.9x) n/a 96.97 (3.5x) n/a
RS256 RSA 3072 large 21384 95.60 (22.1x) 129.12 (16.3x) 130.44 (16.2x) n/a 109.76 (19.2x) n/a 237.30 (8.9x) n/a 220.37 (9.6x) n/a
RS256 RSA 4096 small 902 119.52 (1.6x) 119.10 (1.6x) 119.31 (1.6x) n/a 120.38 (1.6x) n/a 154.68 (1.2x) n/a 108.69 (1.8x) n/a
RS256 RSA 4096 typical 1302 121.07 (1.9x) 120.58 (1.9x) 120.65 (1.9x) n/a 122.61 (1.9x) n/a 159.15 (1.4x) n/a 112.55 (2.0x) n/a
RS256 RSA 4096 medium 2915 121.85 (3.1x) 124.38 (3.1x) 125.52 (3.0x) n/a 124.45 (3.1x) n/a 164.05 (2.3x) n/a 125.81 (3.0x) n/a
RS256 RSA 4096 large 21555 145.23 (14.9x) 178.72 (12.1x) 179.96 (12.0x) n/a 159.22 (13.6x) n/a 289.43 (7.5x) n/a 259.68 (8.3x) n/a
ES256 P-256 small 304 53.69 (2.4x) 53.56 (2.5x) 53.62 (2.4x) n/a 54.97 (2.4x) n/a 3551.20 (0.04x) n/a 91.56 (1.4x) n/a
ES256 P-256 typical 704 54.91 (3.2x) 55.14 (3.2x) 55.37 (3.2x) n/a 57.40 (3.0x) n/a 3542.82 (0.05x) n/a 94.50 (1.9x) n/a
ES256 P-256 medium 2317 57.21 (5.8x) 58.86 (5.7x) 59.80 (5.6x) n/a 59.17 (5.6x) n/a 3519.20 (0.09x) n/a 107.34 (3.1x) n/a
ES256 P-256 large 20957 79.81 (26.6x) 113.67 (18.7x) 115.09 (18.4x) n/a 93.77 (22.6x) n/a 3610.72 (0.6x) n/a 236.19 (9.0x) n/a
ES384 P-384 small 346 250.97 (1.6x) 250.68 (1.6x) 251.39 (1.6x) n/a 251.79 (1.6x) n/a 12313.10 (0.03x) n/a 528.34 (0.7x) n/a
ES384 P-384 typical 746 253.19 (1.7x) 252.79 (1.7x) 253.09 (1.7x) n/a 255.01 (1.7x) n/a 12329.96 (0.04x) n/a 534.26 (0.8x) n/a
ES384 P-384 medium 2359 252.38 (2.3x) 255.47 (2.3x) 256.91 (2.3x) n/a 255.55 (2.3x) n/a 12273.71 (0.05x) n/a 550.02 (1.1x) n/a
ES384 P-384 large 20999 282.71 (8.4x) 317.10 (7.5x) 317.44 (7.5x) n/a 296.94 (8.0x) n/a 12346.25 (0.2x) n/a 684.75 (3.5x) n/a
ES512 P-521 small 394 505.56 (1.1x) 505.46 (1.1x) 505.02 (1.1x) n/a 505.82 (1.1x) n/a 28790.14 (0.02x) n/a 1324.84 (0.4x) n/a
ES512 P-521 typical 794 500.82 (1.2x) 500.93 (1.2x) 501.50 (1.2x) n/a 503.08 (1.2x) n/a 28730.05 (0.02x) n/a 1316.37 (0.4x) n/a
ES512 P-521 medium 2407 504.61 (1.5x) 508.37 (1.4x) 508.62 (1.4x) n/a 507.07 (1.5x) n/a 28648.03 (0.03x) n/a 1316.22 (0.6x) n/a
ES512 P-521 large 21047 532.87 (4.8x) 568.48 (4.5x) 568.89 (4.5x) n/a 546.80 (4.6x) n/a 28673.01 (0.09x) n/a 1479.09 (1.7x) n/a
EdDSA Ed25519 small 306 56.92 (2.4x) 56.53 (2.4x) 56.19 (2.4x) n/a 57.44 (2.4x) n/a n/a n/a 85.39 (1.6x) n/a
EdDSA Ed25519 typical 706 57.29 (3.1x) 57.92 (3.0x) 57.76 (3.0x) n/a 59.66 (2.9x) n/a n/a n/a 87.31 (2.0x) n/a
EdDSA Ed25519 medium 2319 59.52 (5.5x) 61.88 (5.3x) 63.12 (5.2x) n/a 62.15 (5.3x) n/a n/a n/a 99.65 (3.3x) n/a
EdDSA Ed25519 large 20959 88.20 (23.9x) 120.81 (17.4x) 122.82 (17.2x) n/a 102.45 (20.6x) n/a n/a n/a 234.94 (9.0x) 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 → Struct ryjwt 0.1.1 → dict (msgspec) ryjwt 0.1.1 → dict (jiter) jsonwebtoken 11.1.0 (aws-lc-rs) → struct ryjwt 0.1.1 → BaseModel jsonwebtoken 11.1.0 (aws-lc-rs) → Value fast-jwt 6.3.3 joserfc 1.7.5 jose 6.2.12 pyjwt 2.15.1
JWKS URL, sync client 2.7 2.4 2.5 n/a 2.5 n/a n/a n/a n/a 2.3
JWKS URL, async client 2.8 2.5 2.7 n/a 2.6 n/a 1.9 n/a 1.4 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.
  • 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.
  • jose 6.2.12, JWKS URL, sync client: jose's API is async only.
  • pyjwt 2.15.1, JWKS URL, async client: PyJWT has no async JWKS client.

Notes

  • 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 (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 → 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.
  • 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: claims decoded into typed structs with the fields ryjwt's msgspec lane decodes; other claims ignored.
  • 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) → 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.
  • 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.
  • jose 6.2.12: HMAC secret imported once (crypto.subtle.importKey); importSPKI, createLocalJWKSet, createRemoteJWKSet.
  • 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.