HS256 vs RS256 vs ES256: Benchmark JWT verification
HS256 vs RS256 vs ES256: Benchmark JWT verification
Bùi Tài Tuấn — — 08:24
Có tuần tôi gặp một issue khá khó chịu: API không chậm rõ ràng, p95 vẫn ổn, nhưng CPU của gateway cứ leo dần theo traffic.
Thoáng qua thì tôi nghĩ do log, do TLS, do upstream. Nhưng nhìn kỹ flamegraph thì thấy một phần khá wow: JWT verify chiếm CPU nhiều hơn tôi tưởng, nhất là khi traffic lên 20k~40k req/s và mọi request đều phải auth. Tính vốn thích trả lời bằng các con số nên quyết định làm bài benchmark HS256 vs RS256 vs ES256. Không cần độ chính xác như paper, mà cần câu trả lời cho production: cái nào hợp, cái nào đang làm mình trả tiền CPU vô ích.
1. Bối cảnh thực tế (vì sao tôi phải benchmark)
Đây là một hệ thống trong hệ sinh thái và mô hình khá phổ biến:
- 1 API Gateway nhận request
- Verify JWT (Authorization: Bearer)
- Route vào backend
Traffic lúc bình thường khoảng 8k~15k rps, nhưng có lúc spike lên 30k rps (bot, partner gọi webhook, hoặc job nội bộ chạy dồn).
Thông thường mọi người nghe:
- HS256 nhanh nhưng không enterprise
- RS256 là chuẩn OIDC
- ES256 hiện đại, token nhỏ
2. Setup benchmark tôi dùng
Tôi chỉ làm setup tối giản để đo đúng phần JWT :
Hạ tầng (giả định phổ biến)
- 1 máy loadgen: 8 vCPU/16GB
- 1 máy API test: 8 vCPU/16GB
- Cùng LAN, latency thấp
API test làm gì?
- Đọc header Authorization
- Verify chữ ký JWT
- Parse claims cơ bản (
sub,exp,scope) - Trả JSON nhỏ (200B)
Không DB, không gọi upstream để kết quả tránh nhiễu những nghiệp vụ không liên quan.
Token chuẩn hóa
- Cùng payload claims (khoảng 10-12 fields)
exp15 phút- Có
kid(để mô phỏng key rotation)
Tool đo
- Bắn tải kiểu keep-alive, concurrency 200~1000
- Lấy p95/p99 và CPU của server
3. Điều tôi muốn biết trước tiên: verify 1 token tốn bao nhiêu CPU?
Tôi tách một micro-benchmark để ra độ nặng tương đối của từng thuật toán (cùng payload).
| Thuật toán | Thời gian verify / request (median) | Ghi chú |
|---|---|---|
| HS256 (HMAC-SHA256) | ~2-6 µs | cực rẻ, chủ yếu là hash |
| RS256 (RSA-2048 + SHA256) | ~60-140 µs | verify RSA đắt hơn hash nhiều |
| ES256 (ECDSA P-256 + SHA256) | ~90-220 µs | thường còn đắt hơn RSA verify một chút |
Ví dụ nếu gateway chạy 30k rps:
- HS256: 30k * 5µs ≈ 0.15 CPU-seconds/second (rất nhẹ)
- RS256: 30k * 100µs ≈ 3 CPU-seconds/second (tức ~3 core chỉ để verify)
- ES256: 30k * 160µs ≈ 4.8 core (chỉ verify)
Và đó là chưa tính JSON encode, routing, log, TLS, metrics.
4. Benchmark end-to-end: khi traffic tăng thì cái nào gục trước?
Tôi chạy 3 mức tải đại diện: 10k rps (bình thường), 30k rps (spike), 50k rps (cố tình ép).
Kết quả ở 30k rps (concurrency ~800)
| Thuật toán | p95 latency tăng thêm | p99 latency tăng thêm | CPU server | Error rate |
|---|---|---|---|---|
| HS256 | ~0.2-0.4 ms | ~0.6-1.0 ms | ~35-45% | ~0% |
| RS256 | ~1.5-2.8 ms | ~3.5-6.0 ms | ~75-90% | ~0-0.2% |
| ES256 | ~2.2-3.8 ms | ~5.0-9.0 ms | ~90-100% | ~0.2-0.6% |
Cảm nhận của tôi:
- HS256 gần như “vô hình” trong tổng latency.
- RS256 bắt đầu làm CPU nhảy rõ rệt, p99 xấu lên nhưng còn chịu được.
- ES256 đẹp ở mặt “hiện đại”, nhưng verify đắt, nên khi spike thì nó làm gateway ngộp nhanh hơn.
Kết quả khi ép lên 50k rps
| Thuật toán | Trạng thái |
|---|---|
| HS256 | vẫn giữ được, CPU ~65-75%, p99 ~2-4 ms (phần auth) |
| RS256 | bắt đầu timeout lác đác nếu không scale, p99 auth ~8-15 ms |
| ES256 | dễ chạm trần CPU, error tăng nếu không scale, p99 auth ~12-25 ms |
Ở mức này, tôi nhìn kiểu SRE hơn: Cái nào khiến gateway chạm trần CPU sớm hơn → Cái nào làm tôi phải scale sớm hơn → Cái nào làm tôi tốn tiền hơn.
5. Một cái bẫy cực phổ biến: JWKS fetch/key lookup làm benchmark là đổ sông đổ biển
Tôi phải nhắc đoạn này vì nhiều team dính lỗi về cách lấy key:
Trường hợp tốt (đúng)
- RS256/ES256: fetch JWKS mỗi vài phút
- Cache public key theo
kid - Verify local
Trường hợp tự bắn vào chân
- Mỗi request lại đi fetch JWKS hoặc gọi auth server để lấy key/validate
Nếu làm sai kiểu này thì:
- p99 tăng từ vài ms lên vài chục ms đến vài trăm ms
- Gateway chết vì network dependency
Trong benchmark của tôi, tôi giả định cache key đúng cách, vì theo tôi đó mới là cách nên làm.
6. Vậy cuối cùng tôi chọn gì?
Sau khi nhìn số liệu chúng ta chốt theo thực tế bài toán gặp phải thôi:
HS256
Tôi chọn HS256 nếu:
- Token chỉ dùng nội bộ trong một trust domain (cùng công ty/cùng hệ)
- Quản lý secret chặt (vault/kms), rotate được
- Cần hiệu năng cao và muốn gateway xử lý nhẹ nhàng hơn
Tuy nhiên, điểm yếu: HS256 dùng shared secret → service nào verify được thì cũng có thể sign nếu bị lộ secret. Vì vậy HS256 hợp nội bộ, không hợp kiểu nhiều bên cùng tham gia.
RS256
Tôi chọn RS256 nếu:
- Dùng OIDC/IdP tiêu chuẩn, JWKS rotation rõ ràng
- Muốn tách quyền sign (private key) và quyền verify (public key)
- Chấp nhận tốn CPU hơn một chút để có mô hình bảo mật sạch
Trong benchmark của tôi: RS256 là điểm cân bằng, đắt hơn HS256 rõ rệt, nhưng vẫn dễ sống nếu scale hợp lý.
ES256
Tôi chọn ES256 khi:
- Thật sự quan tâm token size (mobile/edge/network đắt)
- Muốn crypto hiện đại
- Và tôi đủ tài nguyên hoặc auth không phải bottleneck lớn
Nhưng không ảo tưởng: ES256 verify thường đắt hơn RS256, nên nếu gateway đang ở ngưỡng CPU cao, ES256 có thể khiến bạn phải scale sớm hơn.
7. 3 bài học mà tôi thấy ai cũng nên biết
- Đừng chọn thuật toán vì “nghe chuẩn” — chọn dựa trên mô hình trust + chi phí CPU + cách triển khai key cache.
- Trong hệ traffic lớn, auth là hạ tầng — 1-2ms thêm vào p99 ở gateway có thể khiến bạn phải scale thêm vài node.
- Key cache quan trọng không kém thuật toán — RS/ES mà cache key tệ thì p99 sẽ xấu kinh khủng.
8. Nếu mọi người muốn benchmark nhanh cho hệ của mình
Nên làm tối thiểu 2 bài test:
- 10k rps và 30k rps (TLS on, keep-alive)
- Đo p99 + CPU gateway
- Test thêm 1 lần cache miss key (giả lập rotate) xem p99 nhảy bao nhiêu
Vậy là đủ ra quyết định rất nhanh rồi.
