JWT Signing Algorithms — HS256, RS256, ES256 Explained with Examples
In this tutorial, you will learn about JWT Signing Algorithms. We cover key concepts, practical examples, and best practices to help you master this topic.
JWT signing algorithms define how the signature is computed and verified, with symmetric (HS256) using a shared secret and asymmetric (RS256, ES256) using public-private key pairs.
What You'll Learn
The differences between HS256, RS256, and ES256, how to generate keys for each, when to use each algorithm, and security best practices.
Why It Matters
Algorithm choice affects security, performance, and key management. Using the wrong algorithm (e.g., HS256 in a multi-service architecture) creates security vulnerabilities. Understanding each algorithm helps you choose correctly.
Real-World Use
Auth0 defaults to RS256 for multi-service architectures. Firebase uses RS256. Internal Microservices might use HS256 for simplicity. Google uses ES256 for its low-latency requirements.
flowchart TD
A["JWT Signing"] --> B["Symmetric\nHS256"]
A --> C["Asymmetric\nRS256, ES256"]
B --> D["Single secret\nSign + Verify"]
C --> E["Private key: Sign\nPublic key: Verify"]
B --> F["Simple, fast\n1 key to protect"]
C --> G["Distributed verification\nPublic key is safe to share"]
style A fill:#dbeafe,stroke:#2563eb
style B fill:#fef3c7,stroke:#d97706
style C fill:#dcfce7,stroke:#16a34a
Algorithm Comparison
| Algorithm | Type | Key Size | Speed | Use Case |
|---|---|---|---|---|
| HS256 | Symmetric (HMAC) | 256+ bits | Fastest | Single service, internal |
| RS256 | Asymmetric (RSA) | 2048+ bits | Slower sign, fast verify | Distributed microservices |
| ES256 | Asymmetric (ECDSA) | 256 bits | Fast, compact | Mobile, IoT, performance |
Code Example: HS256 (Symmetric)
import jwt
import datetime
# Single shared secret
SECRET = "my-very-secure-secret-at-least-32-chars"
# Sign
token = jwt.encode(
{"sub": "user-123", "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)},
SECRET,
algorithm="HS256"
)
# Verify (uses same secret)
decoded = jwt.decode(token, SECRET, algorithms=["HS256"])
Code Example: RS256 (Asymmetric)
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
import jwt, datetime
# Generate RSA key pair
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
# Get public key
public_key = private_key.public_key()
# Sign with private key
token = jwt.encode(
{"sub": "user-123"},
private_key,
algorithm="RS256"
)
# Verify with public key
decoded = jwt.decode(token, public_key, algorithms=["RS256"])
Code Example: ES256 (ECDSA)
from cryptography.hazmat.primitives.asymmetric import ec
import jwt, datetime
# Generate ECDSA key pair
private_key = ec.generate_private_key(
ec.SECP256R1(), # P-256 curve
default_backend()
)
public_key = private_key.public_key()
# Sign
token = jwt.encode({"sub": "user-123"}, private_key, algorithm="ES256")
# Verify
decoded = jwt.decode(token, public_key, algorithms=["ES256"])
Expected token sizes:
HS256: ~200 bytes (token)
RS256: ~500 bytes (larger signature)
ES256: ~250 bytes (compact signature)
Common Mistakes
1. Using HS256 Across Multiple Services
Every service that verifies the token has the secret. If one service is compromised, all tokens can be forged. Use RS256 for Distributed Systems.
2. Short RSA Keys
RSA keys under 2048 bits are vulnerable to factoring. Use 2048+ bits for RS256.
3. Not Setting Algorithm Allowlist
Always specify algorithms=["HS256"] when decoding. Without it, an attacker could change the algorithm in the header (algorithm confusion attack).
4. Reusing Keys Across Environments
Use different keys for development, staging, and production. A compromised dev key should not affect production.
5. Hardcoding Private Keys
Private keys must be stored securely (HSM, secret manager, encrypted file). Never commit private keys to version control.
Practice Questions
- What is the difference between symmetric and asymmetric signing?
- When should you use RS256 instead of HS256?
- Why is ES256 faster than RS256?
- What key size is recommended for RS256?
- What is the algorithm confusion attack?
Answers:
- Symmetric (HS256) uses one shared secret for sign and verify. Asymmetric (RS256, ES256) uses a private key to sign and a public key to verify.
- RS256 when multiple services need to verify tokens without sharing a secret. Each service only needs the public key.
- ES256 uses elliptic curve cryptography which provides equivalent security to RSA with smaller keys and faster operations.
- At least 2048 bits. 4096 bits is recommended for high-security applications.
- An attacker changes the
algheader from RS256 to HS256 and signs the token with the public key (which is public). The server, using the public key as an HMAC secret, accepts the forged token.
Challenge: Generate a key pair for RS256, sign a JWT with the private key, and verify it in a separate script using only the public key. Show that HS256 verification with the public key also works (demonstrating the algorithm confusion attack).
FAQ
Mini Project
Build a JWT signing utility that supports HS256, RS256, and ES256. Generate keys for each, sign tokens, and verify them. Measure and compare token sizes and signing/verification speeds.
What's Next
Now learn about JWT Access Tokens — short-lived tokens for API authorization.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro