Post-Quantum JWT - A quantum-resistant JWT implementation using ML-DSA (Module-Lattice Digital Signature Algorithm) signatures.
🛡️ Future-proof your authentication - Protect your JWTs against quantum computer attacks with NIST-standardized post-quantum cryptography.
- ✅ Quantum-Resistant - Uses ML-DSA (FIPS 204) signatures that remain secure even against quantum attacks
- ✅ Multiple Security Levels - Choose from ML-DSA-44, ML-DSA-65, or ML-DSA-87 based on your needs
- ✅ Standards Compliant - JWT format following RFC 7519
- ✅ Flexible API - Simple functions and advanced Builder patterns
- ✅ Key Management - Built-in support for saving keys to files
- ✅ Key Rotation - Support for
kid(Key ID) in JWT headers - ✅ Zero Dependencies Bloat - Minimal, focused dependencies
- ✅ Easy to Use - Simple, intuitive API
- ✅ Well Tested - Comprehensive test coverage with unit and integration tests
- ✅ Pure Rust - Memory-safe implementation with no unsafe code
|
Operations
Standard Claims
|
Claim Validation
Custom Claims
|
| Algorithm | NIST Level | Status | Use Case |
|---|---|---|---|
| ML-DSA-44 | Category 2 | ✅ Supported | IoT, constrained devices |
| ML-DSA-65 | Category 3 | ✅ Supported (Recommended) | General purpose applications |
| ML-DSA-87 | Category 5 | ✅ Supported | High-security requirements |
Note: This library does NOT support classical algorithms (HS256, RS256, ES256, PS256, EdDSA) as they are vulnerable to quantum attacks. For classical JWT algorithms, use other libraries like jsonwebtoken.
Add this to your Cargo.toml:
[dependencies]
pq-jwt = "0.1.0"use pq_jwt::{generate_keypair, sign, verify, MlDsaAlgo};
use std::time::{SystemTime, UNIX_EPOCH};
fn main() -> Result<(), String> {
// 1. Generate a keypair
let (private_key, public_key) = generate_keypair(MlDsaAlgo::Dsa65)?;
// 2. Create and sign a JWT with issuer and expiration
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
let (jwt, _) = sign(
MlDsaAlgo::Dsa65,
"https://myapp.com", // Issuer
now + 3600, // Expires in 1 hour
&private_key
)?;
println!("JWT: {}", jwt);
// 3. Verify the JWT
let verified_payload = verify(&jwt, &public_key, "https://myapp.com")?;
println!("Verified payload: {}", verified_payload);
println!("✓ JWT verified successfully!");
Ok(())
}use pq_jwt::{generate_keypair, sign, verify, MlDsaAlgo};
use std::time::{SystemTime, UNIX_EPOCH};
// Generate long-term keypair (store securely!)
let (private_key, public_key) = generate_keypair(MlDsaAlgo::Dsa65)?;
// Create user session token
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
let (jwt, _) = sign(
MlDsaAlgo::Dsa65,
"https://myapp.com", // Issuer
now + 3600, // Expires in 1 hour
&private_key
)?;
// Later: verify the token
let payload = verify(&jwt, &public_key, "https://myapp.com")?;
println!("Authenticated user: {}", payload);use pq_jwt::signer::Builder;
use pq_jwt::MlDsaAlgo;
use std::time::{SystemTime, UNIX_EPOCH};
use serde_json::json;
let (private_key, public_key) = generate_keypair(MlDsaAlgo::Dsa65)?;
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
// Create signer with all standard claims and custom data
let signer = Builder::new()
.algorithm(MlDsaAlgo::Dsa65)
.private_key(&private_key)
.issuer("https://myapp.com")
.expiration(now + 3600)
.subject("user123")
.audience("https://api.myapp.com")
.custom_claims(json!({
"name": "Alice",
"role": "admin",
"permissions": ["read", "write", "delete"]
}))
.build()?;
let (jwt, _) = signer.sign()?;
// Verify
let payload = verify(&jwt, &public_key, "https://myapp.com")?;
println!("Token payload: {}", payload);use pq_jwt::keygen::Builder;
use pq_jwt::MlDsaAlgo;
// Generate and save to default location (keys/)
let (private_key, public_key) = Builder::new()
.algorithm(MlDsaAlgo::Dsa65)
.save_to_file()
.generate()?;
// Or save to custom location
let (private_key, public_key) = Builder::new()
.algorithm(MlDsaAlgo::Dsa65)
.save_to_file_at("./my-secure-keys")
.generate()?;
// Files created:
// - ml_dsa_65_1704139200_private.key
// - ml_dsa_65_1704139200_public.key (derived from private key)use pq_jwt::keygen::{Builder, KeySource};
use pq_jwt::MlDsaAlgo;
// Load from default location (keys/) - picks latest by timestamp
let (private_key, public_key, source) = Builder::from(MlDsaAlgo::Dsa65)
.file()?;
// Load from custom location
let (private_key, public_key, source) = Builder::from(MlDsaAlgo::Dsa65)
.file_at("./my-secure-keys")?;
// Public key is automatically derived from private key
assert_eq!(source, KeySource::Loaded);use pq_jwt::keygen::{Builder, KeySource};
use pq_jwt::MlDsaAlgo;
// Try to load existing key, generate if missing
let (private_key, public_key, source) = Builder::load_or_generate(MlDsaAlgo::Dsa65)
.file()?;
match source {
KeySource::Loaded => println!("Using existing key"),
KeySource::Generated => println!("Generated new key and saved"),
}
// Custom location
let (private_key, public_key, source) = Builder::load_or_generate(MlDsaAlgo::Dsa65)
.file_at("./my-secure-keys")?;
// Perfect for server initialization - always has a valid key!use pq_jwt::keygen::{Builder, KeySource};
use pq_jwt::MlDsaAlgo;
// Load private key from database or environment
let private_key_from_db = std::env::var("JWT_PRIVATE_KEY")?;
// Derive public key from private key
let (private_key, public_key, source) = Builder::from(MlDsaAlgo::Dsa65)
.private_key_str(&private_key_from_db)?;
assert_eq!(source, KeySource::Loaded);
// Use the keys for signing/verificationThe Key ID (kid) is automatically generated from the public key using SHA-256, ensuring consistent identification across key rotations.
use pq_jwt::signer::Builder as SignerBuilder;
use pq_jwt::verifier::Builder as VerifierBuilder;
use pq_jwt::{generate_keypair, MlDsaAlgo};
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
// Generate keypair
let (priv_key_v2, pub_key_v2) = generate_keypair(MlDsaAlgo::Dsa65)?;
// Create signer (kid is auto-generated from public key)
let signer = SignerBuilder::new()
.algorithm(MlDsaAlgo::Dsa65)
.private_key(&priv_key_v2)
.issuer("https://myapp.com")
.expiration(now + 3600)
.build()?;
let (jwt, _) = signer.sign()?;
// Verify (kid from JWT header can be used to identify which key to use)
let verifier = VerifierBuilder::new()
.public_key(&pub_key_v2)
.issuer("https://myapp.com")
.build()?;
let payload = verifier.verify(&jwt)?;use pq_jwt::signer::Builder as SignerBuilder;
use pq_jwt::verifier::Builder as VerifierBuilder;
use pq_jwt::MlDsaAlgo;
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
// Create once, use many times
let signer = SignerBuilder::new()
.algorithm(MlDsaAlgo::Dsa65)
.private_key(&private_key)
.issuer("https://myapp.com")
.expiration(now + 3600)
.build()?;
// Sign (no parameters needed - uses configured claims)
let (jwt1, _) = signer.sign()?;
let (jwt2, _) = signer.sign()?;
let (jwt3, _) = signer.sign()?;
// Create reusable verifier
let verifier = VerifierBuilder::new()
.public_key(&public_key)
.issuer("https://myapp.com")
.build()?;
// Verify multiple tokens
for jwt in [jwt1, jwt2, jwt3] {
match verifier.verify(&jwt) {
Ok(payload) => println!("Valid: {}", payload),
Err(e) => println!("Invalid: {}", e),
}
}use pq_jwt::{generate_keypair, MlDsaAlgo};
use pq_jwt::signer::Builder;
use pq_jwt::verifier;
use std::time::{SystemTime, UNIX_EPOCH};
use serde_json::json;
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
// Server initialization
let (server_private_key, server_public_key) =
generate_keypair(MlDsaAlgo::Dsa65)?;
// Issue API token with custom claims
let signer = Builder::new()
.algorithm(MlDsaAlgo::Dsa65)
.private_key(&server_private_key)
.issuer("https://api.myapp.com")
.expiration(now + 86400) // 24 hours
.subject("ak_live_123456")
.custom_claims(json!({
"scope": ["read", "write"],
"rate_limit": 1000
}))
.build()?;
let (api_token, _) = signer.sign()?;
// Client sends: Authorization: Bearer <api_token>
// Server verifies:
match verifier::verify(&api_token, &server_public_key, "https://api.myapp.com") {
Ok(claims) => println!("Valid API token: {}", claims),
Err(e) => println!("Invalid token: {}", e),
}use pq_jwt::signer::Builder;
use pq_jwt::{verify, MlDsaAlgo};
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Serialize, Deserialize)]
struct CustomData {
user_id: u64,
role: String,
permissions: Vec<String>,
}
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
let custom_data = CustomData {
user_id: 42,
role: "admin".to_string(),
permissions: vec!["read".to_string(), "write".to_string()],
};
// Build JWT with standard claims + custom data
let signer = Builder::new()
.algorithm(MlDsaAlgo::Dsa65)
.private_key(&private_key)
.issuer("https://myapp.com")
.expiration(now + 3600)
.subject("user_42")
.custom_claims(serde_json::to_value(&custom_data)?)
.build()?;
let (jwt, _) = signer.sign()?;
// Later... verify and extract
let verified = verify(&jwt, &public_key, "https://myapp.com")?;
let payload: serde_json::Value = serde_json::from_str(&verified)?;
let custom: CustomData = serde_json::from_value(payload)?;
println!("User {} has role: {}", custom.user_id, custom.role);Choose the right security level for your use case:
| Variant | NIST Level | Signature Size | Key Gen | Sign | Verify | Use Case |
|---|---|---|---|---|---|---|
| ML-DSA-44 | Category 2 | ~2.4 KB | ~200 µs | ~460 µs | ~140 µs | IoT devices, low-power systems |
| ML-DSA-65 | Category 3 | ~3.3 KB | ~350 µs | ~930 µs | ~220 µs | Recommended for most applications |
| ML-DSA-87 | Category 5 | ~4.6 KB | ~440 µs | ~550 µs | ~315 µs | High-security requirements, long-term secrets |
- NIST Category 2 ≈ AES-128 security
- NIST Category 3 ≈ AES-192 security (Recommended)
- NIST Category 5 ≈ AES-256 security
use pq_jwt::MlDsaAlgo;
// For most web applications (recommended)
let algo = MlDsaAlgo::Dsa65;
// For IoT or bandwidth-constrained environments
let algo = MlDsaAlgo::Dsa44;
// For maximum security (government, financial)
let algo = MlDsaAlgo::Dsa87;Benchmarked on Apple M1 Pro (release build):
ML-DSA-65 Performance:
├─ Key Generation: ~350 µs (2,857 ops/sec)
├─ Signing: ~930 µs (1,075 ops/sec)
└─ Verification: ~220 µs (4,545 ops/sec)
Token Size: ~4.5 KB (vs ~300 bytes for ECDSA)
- Cache Keys: Generate keypairs once and reuse them
- Pre-verify Format: Check JWT structure before cryptographic verification
- Use ML-DSA-44: If bandwidth is critical and security level 2 is acceptable
- Batch Operations: Verify multiple tokens in parallel for better throughput
| Algorithm | Private Key | Public Key | Signature | Total JWT |
|---|---|---|---|---|
| ECDSA P-256 | 32 bytes | 64 bytes | 64 bytes | ~300 bytes |
| RSA-2048 | 1.2 KB | 270 bytes | 256 bytes | ~800 bytes |
| ML-DSA-44 | 2.5 KB | 1.3 KB | 2.4 KB | ~3.3 KB |
| ML-DSA-65 | 4 KB | 1.9 KB | 3.3 KB | ~4.5 KB |
| ML-DSA-87 | 4.9 KB | 2.6 KB | 4.6 KB | ~6.2 KB |
⚠️ Trade-off: Post-quantum signatures are larger, but provide quantum resistance. The size increase is the price of security against quantum attacks.
Generates a new keypair for the specified algorithm.
Returns: (private_key_hex, public_key_hex)
let (private_key, public_key) = generate_keypair(MlDsaAlgo::Dsa65)?;sign(algo: MlDsaAlgo, iss: &str, exp: u64, private_key_hex: &str) -> Result<(String, String), String>
Signs JWT claims and returns a JWT with the public key.
Parameters:
algo- ML-DSA algorithm variantiss- Issuer (REQUIRED)exp- Expiration time as Unix timestamp in seconds (REQUIRED)private_key_hex- Hex-encoded private key
Returns: (jwt, public_key_hex)
Note: The iat (issued at) claim defaults to the current time.
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
let (jwt, pub_key) = sign(
MlDsaAlgo::Dsa65,
"https://myapp.com",
now + 3600,
&private_key
)?;Verifies a JWT and returns the decoded payload.
Parameters:
jwt- The JWT string to verifypublic_key_hex- Hex-encoded public keyexpected_issuer- Expected issuer that must match the JWT'sissclaim
Returns: payload if valid, error otherwise
let payload = verify(&jwt, &public_key, "https://myapp.com")?;Generation Methods:
Builder::new()- Create builder for generation.algorithm(MlDsaAlgo)- Set the algorithm variant.save_to_file()- Save keys to default location (keys/).save_to_file_at(path)- Save keys to custom path.generate()- Generate keypair (and save if configured)- Returns:
(private_key_hex, public_key_hex)
Loading Methods:
Builder::from(algo)- Create builder for loading (error if missing)Builder::load_or_generate(algo)- Load or auto-generate if missing.file()- Load from default location (keys/), picks latest by timestamp.file_at(path)- Load from custom path, picks latest by timestamp.private_key_str(hex)- Load from hex string, derives public key- Returns:
(private_key_hex, public_key_hex, KeySource)
use pq_jwt::keygen::{Builder, KeySource};
// Generate and save
let (priv_key, pub_key) = Builder::new()
.algorithm(MlDsaAlgo::Dsa65)
.save_to_file_at("./secure-keys")
.generate()?;
// Load from file (error if missing)
let (priv_key, pub_key, source) = Builder::from(MlDsaAlgo::Dsa65)
.file_at("./secure-keys")?;
// Load or generate (auto-fallback)
let (priv_key, pub_key, source) = Builder::load_or_generate(MlDsaAlgo::Dsa65)
.file_at("./secure-keys")?;
// Load from string
let (priv_key, pub_key, source) = Builder::from(MlDsaAlgo::Dsa65)
.private_key_str(&hex_string)?;Configuration Methods:
.algorithm(MlDsaAlgo)- Set the algorithm variant (REQUIRED).private_key(&str)- Set the private key (REQUIRED)
Standard JWT Claims Methods:
.issuer(&str)- Setissclaim (REQUIRED).expiration(u64)- Setexpclaim as Unix timestamp (REQUIRED).subject(&str)- Setsubclaim (optional).audience(&str)- Setaudclaim (optional).issued_at(Option<u64>)- Setiatclaim, defaults to signing time if not set (optional).not_before(u64)- Setnbfclaim as Unix timestamp (optional).jwt_id(&str)- Setjticlaim (optional).custom_claims(serde_json::Value)- Add custom claims (optional)
Build Method:
.build()- Build Signer instance, returnsResult<Signer, String>
Signer Methods:
.sign()- Sign the configured claims, returnsResult<(String, String), String>
Notes:
- The Key ID (kid) is automatically generated from the public key using SHA-256
- The
iat(issued at) defaults to the current signing time if not explicitly set - Claims are validated before signing (
exp > iat,nbf <= iat) - Custom claims that duplicate standard claim keys are ignored
use pq_jwt::signer::Builder;
use std::time::{SystemTime, UNIX_EPOCH};
use serde_json::json;
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
let signer = Builder::new()
.algorithm(MlDsaAlgo::Dsa65)
.private_key(&priv_key)
.issuer("https://myapp.com")
.expiration(now + 3600)
.subject("user@example.com")
.custom_claims(json!({
"role": "admin",
"permissions": ["read", "write"]
}))
.build()?;
let (jwt, pub_key) = signer.sign()?;Required Configuration:
.public_key(&str)- Set the public key (REQUIRED).issuer(&str)- Set expected issuer for validation (REQUIRED)
Optional Claim Validations:
.audience(&str)- Set expected audience for validation.subject(&str)- Set expected subject for validation.leeway(u64)- Set time leeway in seconds for clock skew (default: 0)
Build Method:
.build()- Build Verifier instance, returnsResult<Verifier, String>
Verifier Methods:
.verify(&str)- Verify JWT and return payload, returnsResult<String, String>
Automatic Validations (Always Performed):
- ✅ Signature verification (cryptographic)
- ✅ Expiration check (
expmust be in the future) - ✅ Issuer matching (
issclaim must match expected issuer)
Optional Validations (Configured via Builder):
- Expected audience matching (if
.audience()is called) - Expected subject matching (if
.subject()is called) - Not before time (
nbfif present in token)
use pq_jwt::verifier::Builder;
// Basic verification - issuer is REQUIRED
let verifier = Builder::new()
.public_key(&pub_key)
.issuer("https://myapp.com") // REQUIRED
.build()?;
let payload = verifier.verify(&jwt)?;
// Advanced verification with additional optional validations
let verifier = Builder::new()
.public_key(&pub_key)
.issuer("https://myapp.com") // REQUIRED
.audience("https://api.myapp.com") // Optional: validate audience matches
.subject("user@example.com") // Optional: validate subject matches
.leeway(60) // Optional: allow 60s clock skew
.build()?;
let payload = verifier.verify(&jwt)?;Available algorithm variants:
MlDsaAlgo::Dsa44- NIST Category 2MlDsaAlgo::Dsa65- NIST Category 3 (Recommended)MlDsaAlgo::Dsa87- NIST Category 5
Traits: Debug, Clone, Copy, PartialEq, Eq
Indicates the source of a keypair when using load_or_generate:
KeySource::Loaded- Successfully loaded existing key from file or stringKeySource::Generated- Generated new key (file was missing or corrupt)
Traits: Debug, Clone, PartialEq, Eq
use pq_jwt::keygen::{Builder, KeySource};
let (priv_key, pub_key, source) = Builder::load_or_generate(MlDsaAlgo::Dsa65)
.file()?;
match source {
KeySource::Loaded => println!("Reusing existing key"),
KeySource::Generated => println!("Created new key"),
}Breaking Change: The sign() function signature has changed to require iss and exp parameters.
Old API (v0.1.x):
let payload = r#"{"sub": "user123", "exp": 1735689600}"#;
let (jwt, _) = sign(MlDsaAlgo::Dsa65, payload, &priv_key)?;New API (v0.2.x):
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
let (jwt, _) = sign(
MlDsaAlgo::Dsa65,
"https://myapp.com", // issuer (required)
now + 3600, // expiration (required)
&priv_key
)?;For more complex claims, use the Builder API:
use pq_jwt::signer::Builder;
use serde_json::json;
let signer = Builder::new()
.algorithm(MlDsaAlgo::Dsa65)
.private_key(&priv_key)
.issuer("https://myapp.com")
.expiration(now + 3600)
.subject("user123")
.custom_claims(json!({
"role": "admin",
"permissions": ["read", "write"]
}))
.build()?;
let (jwt, _) = signer.sign()?;Key File Management:
// Old way - manual file handling
let (priv_key, pub_key) = generate_keypair(MlDsaAlgo::Dsa65)?;
std::fs::write("private.key", &priv_key)?;
std::fs::write("public.key", &pub_key)?;
// New way - built-in
use pq_jwt::keygen::Builder;
let (priv_key, pub_key) = Builder::new()
.algorithm(MlDsaAlgo::Dsa65)
.save_to_file()
.generate()?;Key Rotation:
// New: kid is automatically generated for key rotation
use pq_jwt::signer::Builder;
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
let signer = Builder::new()
.algorithm(MlDsaAlgo::Dsa65)
.private_key(&priv_key)
.issuer("https://myapp.com")
.expiration(now + 3600)
.build()?;
// The kid in the JWT header can be used to identify which public key to useReusable Instances:
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
// New: Create once, use multiple times
let signer = signer::Builder::new()
.algorithm(MlDsaAlgo::Dsa65)
.private_key(&priv_key)
.issuer("https://myapp.com")
.expiration(now + 3600)
.build()?;
// Sign (no parameters needed - uses configured claims)
let (jwt1, _) = signer.sign()?;
let (jwt2, _) = signer.sign()?;JWT Claims Validation:
// New: Automatic validation of JWT claims
// - exp > iat (expiration must be after issued at)
// - nbf <= iat (not before must be before or equal to issued at)
// Validation happens automatically when calling sign()- Never commit private keys to version control
- Rotate keys regularly (every 90 days recommended)
- Use environment variables or secret management systems
- Store keys encrypted at rest
- Use file storage with proper permissions (0600 for private keys)
// ✓ Good - Environment variables
let private_key = std::env::var("JWT_PRIVATE_KEY")?;
// ✓ Good - Secure file storage
use pq_jwt::keygen::Builder;
let (priv_key, pub_key) = Builder::new()
.algorithm(MlDsaAlgo::Dsa65)
.save_to_file_at("/secure/keys")
.generate()?;
// ✗ Bad - Hardcoded
let private_key = "4343e9e24838dbd8..."; // Never do thisuse std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
// Step 1: Generate new keypair (kid will be auto-generated)
let (new_priv, new_pub) = keygen::Builder::new()
.algorithm(MlDsaAlgo::Dsa65)
.save_to_file_at("/keys/v3")
.generate()?;
// Step 2: Create new signer (kid is auto-generated from public key)
let signer = signer::Builder::new()
.algorithm(MlDsaAlgo::Dsa65)
.private_key(&new_priv)
.issuer("https://myapp.com")
.expiration(now + 3600)
.build()?;
// Step 3: Store the public key with its auto-generated kid for verification
// You can extract the kid from a signed JWT's header to identify which key to use
// Step 4: Keep old public keys for verification during transition period
// Step 5: Gradually phase out old keys- Always include expiration (
expclaim) - Use short lifetimes for sensitive operations (15 min - 1 hour)
- Implement token revocation if needed
- Validate claims after verification
- Use HTTPS for token transmission
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
// Sign with issuer and expiration
let (jwt, _) = sign(
MlDsaAlgo::Dsa65,
"https://example.com", // issuer
now + 3600, // expiration (1 hour from now)
&private_key
)?;Quantum computers, when fully developed, will break current cryptographic systems:
- RSA - Vulnerable to Shor's algorithm
- ECDSA - Vulnerable to Shor's algorithm
- Diffie-Hellman - Vulnerable to quantum attacks
- 2023: NIST standardizes post-quantum algorithms (ML-DSA = FIPS 204)
- 2025-2030: Quantum computers may break RSA-2048
- 2030+: All systems must use post-quantum crypto
Attackers can:
- Intercept and store encrypted data today
- Wait for quantum computers to become available
- Decrypt the data retroactively
Solution: Start using post-quantum crypto NOW to protect long-term secrets.
| Feature | pq-jwt (ML-DSA) | Classical (ECDSA) |
|---|---|---|
| Quantum Resistant | ✅ Yes | ❌ No |
| NIST Standardized | ✅ FIPS 204 | ✅ FIPS 186 |
| Token Size | 3-6 KB | ~300 bytes |
| Sign Speed | ~0.5-1 ms | ~0.05-0.1 ms |
| Verify Speed | ~0.2-0.3 ms | ~0.1-0.2 ms |
| Security Level | 128-256 bit | 128-256 bit |
| Future Proof | ✅ Yes | ❌ Vulnerable to quantum |
use actix_web::{web, App, HttpRequest, HttpServer, Result};
use pq_jwt::{verify, MlDsaAlgo};
async fn protected_route(req: HttpRequest) -> Result<String> {
let auth_header = req
.headers()
.get("Authorization")
.and_then(|h| h.to_str().ok())
.ok_or_else(|| actix_web::error::ErrorUnauthorized("Missing token"))?;
let token = auth_header.strip_prefix("Bearer ")
.ok_or_else(|| actix_web::error::ErrorUnauthorized("Invalid format"))?;
let public_key = std::env::var("JWT_PUBLIC_KEY")
.map_err(|_| actix_web::error::ErrorInternalServerError("Config error"))?;
match verify(token, &public_key, "https://myapp.com") {
Ok(payload) => Ok(format!("Authenticated: {}", payload)),
Err(_) => Err(actix_web::error::ErrorUnauthorized("Invalid token")),
}
}use axum::{
extract::Request,
http::{StatusCode, HeaderMap},
middleware::Next,
response::Response,
};
use pq_jwt::verify;
async fn auth_middleware(
headers: HeaderMap,
request: Request,
next: Next,
) -> Result<Response, StatusCode> {
let auth_header = headers
.get("Authorization")
.and_then(|h| h.to_str().ok())
.ok_or(StatusCode::UNAUTHORIZED)?;
let token = auth_header
.strip_prefix("Bearer ")
.ok_or(StatusCode::UNAUTHORIZED)?;
let public_key = std::env::var("JWT_PUBLIC_KEY")
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
verify(token, &public_key, "https://myapp.com")
.map_err(|_| StatusCode::UNAUTHORIZED)?;
Ok(next.run(request).await)
}Run the test suite:
# Run all tests
cargo test
# Run with output
cargo test -- --nocapture
# Run specific test
cargo test test_full_workflow
# Run benchmarks
cargo test --release- NIST FIPS 204 - ML-DSA Standard
- Post-Quantum Cryptography FAQ
- JWT RFC 7519
- NIST Post-Quantum Standards
Contributions are welcome! Please feel free to submit a Pull Request.
git clone https://github.com/MKSinghDev/pq-jwt-rust.git
cd pq-jwt-rust
cargo build
cargo testThis project is dual-licensed under:
- MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
You may choose either license for your use.
MKSingh (@MKSingh_Dev)
If you find this project useful, please consider giving it a star! ⭐
Made with ❤️ for a quantum-safe future