pq-jwt 0.1.2

Post-Quantum JWT implementation using ML-DSA (FIPS 204) signatures for quantum-resistant authentication
Documentation

๐Ÿ” pq-jwt

Crates.io Documentation CircleCI CI License

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.

๐ŸŒŸ Features

  • โœ… 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
  • โœ… 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

๐Ÿ“ฆ Installation

Add this to your Cargo.toml:

[dependencies]
pq-jwt = "0.1.0"

๐Ÿš€ Quick Start

use pq_jwt::{generate_keypair, sign, verify, MlDsaAlgo};

fn main() -> Result<(), String> {
    // 1. Generate a keypair
    let (private_key, public_key) = generate_keypair(MlDsaAlgo::Dsa65)?;

    // 2. Create and sign a JWT
    let payload = r#"{"user_id": 42, "role": "admin", "exp": 1735689600}"#;
    let (jwt, _) = sign(MlDsaAlgo::Dsa65, payload, &private_key)?;

    println!("JWT: {}", jwt);

    // 3. Verify the JWT
    let verified_payload = verify(&jwt, &public_key)?;
    assert_eq!(payload, verified_payload);

    println!("โœ“ JWT verified successfully!");
    Ok(())
}

๐Ÿ“š Usage Examples

Basic Authentication Token

use pq_jwt::{generate_keypair, sign, verify, MlDsaAlgo};

// Generate long-term keypair (store securely!)
let (private_key, public_key) = generate_keypair(MlDsaAlgo::Dsa65)?;

// Create user session token
let user_claims = r#"{
    "sub": "user123",
    "name": "Alice",
    "role": "admin",
    "iat": 1704067200,
    "exp": 1704153600
}"#;

// Sign the token
let (jwt, _) = sign(MlDsaAlgo::Dsa65, user_claims, &private_key)?;

// Later: verify the token
let payload = verify(&jwt, &public_key)?;
println!("Authenticated user: {}", payload);

API Authentication

use pq_jwt::{generate_keypair, sign, verify, MlDsaAlgo};

// Server initialization
let (server_private_key, server_public_key) =
    generate_keypair(MlDsaAlgo::Dsa65)?;

// Issue API token
let api_claims = r#"{
    "api_key": "ak_live_123456",
    "scope": ["read", "write"],
    "rate_limit": 1000
}"#;

let (api_token, _) = sign(MlDsaAlgo::Dsa65, api_claims, &server_private_key)?;

// Client sends: Authorization: Bearer <api_token>
// Server verifies:
match verify(&api_token, &server_public_key) {
    Ok(claims) => println!("Valid API token: {}", claims),
    Err(e) => println!("Invalid token: {}", e),
}

Custom Payload

use pq_jwt::{sign, verify, MlDsaAlgo};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct CustomClaims {
    user_id: u64,
    role: String,
    permissions: Vec<String>,
    exp: u64,
}

let claims = CustomClaims {
    user_id: 42,
    role: "admin".to_string(),
    permissions: vec!["read".to_string(), "write".to_string()],
    exp: 1735689600,
};

let payload_json = serde_json::to_string(&claims)?;
let (jwt, _) = sign(MlDsaAlgo::Dsa65, &payload_json, &private_key)?;

// Later...
let verified = verify(&jwt, &public_key)?;
let decoded: CustomClaims = serde_json::from_str(&verified)?;

๐Ÿ”‘ Security Levels

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

Security Level Comparison

  • NIST Category 2 โ‰ˆ AES-128 security
  • NIST Category 3 โ‰ˆ AES-192 security (Recommended)
  • NIST Category 5 โ‰ˆ AES-256 security

Choosing an Algorithm

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;

๐ŸŽฏ Performance

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)

Performance Tips

  1. Cache Keys: Generate keypairs once and reuse them
  2. Pre-verify Format: Check JWT structure before cryptographic verification
  3. Use ML-DSA-44: If bandwidth is critical and security level 2 is acceptable
  4. Batch Operations: Verify multiple tokens in parallel for better throughput

๐Ÿ“Š Size Comparison

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.

๐Ÿ› ๏ธ API Reference

Functions

generate_keypair(algo: MlDsaAlgo) -> Result<(String, String), String>

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, payload: &str, private_key_hex: &str) -> Result<(String, String), String>

Signs a payload and returns a JWT.

Returns: (jwt, public_key_hex)

let (jwt, pub_key) = sign(MlDsaAlgo::Dsa65, payload, &private_key)?;

verify(jwt: &str, public_key_hex: &str) -> Result<String, String>

Verifies a JWT and returns the decoded payload.

Returns: payload if valid, error otherwise

let payload = verify(&jwt, &public_key)?;

Enums

MlDsaAlgo

Available algorithm variants:

  • MlDsaAlgo::Dsa44 - NIST Category 2
  • MlDsaAlgo::Dsa65 - NIST Category 3 (Recommended)
  • MlDsaAlgo::Dsa87 - NIST Category 5

๐Ÿ”’ Security Considerations

Key Management

  • 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
// โœ“ Good
let private_key = std::env::var("JWT_PRIVATE_KEY")?;

// โœ— Bad
let private_key = "4343e9e24838dbd8..."; // hardcoded

Token Best Practices

  1. Always include expiration (exp claim)
  2. Use short lifetimes for sensitive operations (15 min - 1 hour)
  3. Implement token revocation if needed
  4. Validate claims after verification
  5. Use HTTPS for token transmission

Example with Expiration

use std::time::{SystemTime, UNIX_EPOCH};

let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
let exp = now + 3600; // 1 hour from now

let payload = format!(r#"{{
    "sub": "user123",
    "iat": {},
    "exp": {}
}}"#, now, exp);

let (jwt, _) = sign(MlDsaAlgo::Dsa65, &payload, &private_key)?;

๐Ÿค” Why Post-Quantum?

The Quantum Threat

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

Timeline

  • 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

"Harvest Now, Decrypt Later"

Attackers can:

  1. Intercept and store encrypted data today
  2. Wait for quantum computers to become available
  3. Decrypt the data retroactively

Solution: Start using post-quantum crypto NOW to protect long-term secrets.

๐Ÿ†š Comparison with Classical JWT

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

๐Ÿ”ง Integration Examples

With Actix Web

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) {
        Ok(payload) => Ok(format!("Authenticated: {}", payload)),
        Err(_) => Err(actix_web::error::ErrorUnauthorized("Invalid token")),
    }
}

With Axum

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)
        .map_err(|_| StatusCode::UNAUTHORIZED)?;

    Ok(next.run(request).await)
}

๐Ÿงช Testing

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

๐Ÿ“– Further Reading

๐Ÿค Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Development Setup

git clone https://github.com/MKSinghDev/pq-jwt-rust.git
cd pq-jwt-rust
cargo build
cargo test

๐Ÿ“„ License

This project is dual-licensed under:

You may choose either license for your use.

๐Ÿ‘จโ€๐Ÿ’ป Author

MKSingh (@MKSingh_Dev)

โญ Star History

If you find this project useful, please consider giving it a star! โญ


Made with โค๏ธ for a quantum-safe future