← Back to PQC Security

PQC Performance Benchmarking

Compare NIST-standardized post-quantum algorithms against classical cryptography

Overview

These free benchmarking tools measure the real-world performance of ML-KEM, ML-DSA, and SLH-DSA compared to RSA and ECC. Use these results to plan your infrastructure capacity for PQC migration.

What Gets Benchmarked:
  • Key Generation: Time to create public/private key pairs
  • Encryption/Encapsulation: Time to encrypt data or encapsulate shared secrets
  • Decryption/Decapsulation: Time to decrypt or decapsulate
  • Signing: Time to generate digital signatures
  • Verification: Time to verify signatures
  • Key/Signature Sizes: Impact on bandwidth and storage

⚠️ Important Disclaimer

Educational Purpose Only: All code samples and scripts provided on this page are for educational and illustrative purposes. They are NOT production-ready and have NOT been thoroughly tested in all environments.

Recommendation: Treat these examples as starting points for learning and development. Adapt, test, and validate according to your specific requirements and security standards.

Setup: Install liboqs (Open Quantum Safe)

liboqs is the reference implementation library for NIST PQC algorithms.

# Install build dependencies (Ubuntu/Debian)
sudo apt-get update
sudo apt-get install cmake gcc ninja-build libssl-dev python3-pytest python3-pytest-xdist unzip xsltproc doxygen graphviz

# Clone and build liboqs
git clone https://github.com/open-quantum-safe/liboqs.git
cd liboqs
mkdir build && cd build
cmake -GNinja -DCMAKE_INSTALL_PREFIX=/usr/local ..
ninja
sudo ninja install

# Install Python bindings
pip install liboqs-python

Benchmarking Scripts to Run

Script 1: ML-KEM (Kyber) Key Encapsulation Benchmark

Compare Kyber512/768/1024 against RSA-2048/3072/4096

#!/usr/bin/env python3
"""ML-KEM (Kyber) Performance Benchmark"""

import oqs
import time
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP

def benchmark_kem(algorithm, iterations=1000):
    """Benchmark a KEM algorithm"""
    with oqs.KeyEncapsulation(algorithm) as kem:
        # Key generation
        start = time.perf_counter()
        for _ in range(iterations):
            public_key = kem.generate_keypair()
        keygen_time = (time.perf_counter() - start) / iterations * 1000

        # Encapsulation
        start = time.perf_counter()
        for _ in range(iterations):
            ciphertext, shared_secret = kem.encap_secret(public_key)
        encap_time = (time.perf_counter() - start) / iterations * 1000

        # Decapsulation
        start = time.perf_counter()
        for _ in range(iterations):
            shared_secret_dec = kem.decap_secret(ciphertext)
        decap_time = (time.perf_counter() - start) / iterations * 1000

        # Sizes
        pk_size = len(public_key)
        ct_size = len(ciphertext)
        ss_size = len(shared_secret)

        return {
            "keygen_ms": keygen_time,
            "encap_ms": encap_time,
            "decap_ms": decap_time,
            "pk_bytes": pk_size,
            "ct_bytes": ct_size,
            "ss_bytes": ss_size
        }

def benchmark_rsa(key_size, iterations=100):
    """Benchmark RSA"""
    # Key generation
    start = time.perf_counter()
    for _ in range(iterations):
        key = RSA.generate(key_size)
    keygen_time = (time.perf_counter() - start) / iterations * 1000

    # Encryption
    key = RSA.generate(key_size)
    cipher = PKCS1_OAEP.new(key)
    message = b"0" * 32  # 32 bytes like a symmetric key

    start = time.perf_counter()
    for _ in range(iterations * 10):  # More iterations since RSA encrypt is fast
        ciphertext = cipher.encrypt(message)
    encrypt_time = (time.perf_counter() - start) / (iterations * 10) * 1000

    # Decryption
    start = time.perf_counter()
    for _ in range(iterations):
        plaintext = cipher.decrypt(ciphertext)
    decrypt_time = (time.perf_counter() - start) / iterations * 1000

    return {
        "keygen_ms": keygen_time,
        "encap_ms": encrypt_time,
        "decap_ms": decrypt_time,
        "pk_bytes": len(key.export_key()),
        "ct_bytes": len(ciphertext),
        "ss_bytes": 32
    }

if __name__ == "__main__":
    print("="*70)
    print("ML-KEM (KYBER) vs RSA PERFORMANCE BENCHMARK")
    print("="*70)

    # ML-KEM variants
    kyber_algs = ["Kyber512", "Kyber768", "Kyber1024"]
    print("\\n--- ML-KEM (Kyber) Performance ---")
    for alg in kyber_algs:
        result = benchmark_kem(alg)
        print(f"\\n{alg}:")
        print(f"  Key Generation:  {result['keygen_ms']:.3f} ms")
        print(f"  Encapsulation:   {result['encap_ms']:.3f} ms")
        print(f"  Decapsulation:   {result['decap_ms']:.3f} ms")
        print(f"  Public Key Size: {result['pk_bytes']} bytes")
        print(f"  Ciphertext Size: {result['ct_bytes']} bytes")

    # RSA comparison
    rsa_sizes = [2048, 3072, 4096]
    print("\\n--- RSA Performance (for comparison) ---")
    for size in rsa_sizes:
        result = benchmark_rsa(size, iterations=50)
        print(f"\\nRSA-{size}:")
        print(f"  Key Generation:  {result['keygen_ms']:.3f} ms")
        print(f"  Encryption:      {result['encap_ms']:.3f} ms")
        print(f"  Decryption:      {result['decap_ms']:.3f} ms")
        print(f"  Public Key Size: {result['pk_bytes']} bytes")
        print(f"  Ciphertext Size: {result['ct_bytes']} bytes")

    print("\\n" + "="*70)
    print("SUMMARY: Kyber is typically 10-40x faster than RSA for key generation")
    print("and 50x faster for decryption, with smaller ciphertexts.")
    print("="*70)

Run: python benchmark_kem.py

Script 2: ML-DSA (Dilithium) Digital Signature Benchmark

Compare Dilithium2/3/5 against RSA and ECDSA signatures

#!/usr/bin/env python3
"""ML-DSA (Dilithium) Performance Benchmark"""

import oqs
import time
from Crypto.PublicKey import RSA, ECC
from Crypto.Signature import pkcs1_15, DSS
from Crypto.Hash import SHA256

def benchmark_signature(algorithm, iterations=1000):
    """Benchmark a signature algorithm"""
    with oqs.Signature(algorithm) as sig:
        # Key generation
        start = time.perf_counter()
        for _ in range(iterations):
            public_key = sig.generate_keypair()
        keygen_time = (time.perf_counter() - start) / iterations * 1000

        # Signing
        message = b"Benchmark message for signing" * 10
        start = time.perf_counter()
        for _ in range(iterations):
            signature = sig.sign(message)
        sign_time = (time.perf_counter() - start) / iterations * 1000

        # Verification
        start = time.perf_counter()
        for _ in range(iterations):
            is_valid = sig.verify(message, signature, public_key)
        verify_time = (time.perf_counter() - start) / iterations * 1000

        return {
            "keygen_ms": keygen_time,
            "sign_ms": sign_time,
            "verify_ms": verify_time,
            "pk_bytes": len(public_key),
            "sig_bytes": len(signature)
        }

def benchmark_rsa_signature(key_size, iterations=100):
    """Benchmark RSA signatures"""
    # Key generation
    start = time.perf_counter()
    for _ in range(iterations):
        key = RSA.generate(key_size)
    keygen_time = (time.perf_counter() - start) / iterations * 1000

    # Signing
    key = RSA.generate(key_size)
    message = b"Benchmark message for signing" * 10
    h = SHA256.new(message)

    start = time.perf_counter()
    for _ in range(iterations):
        signature = pkcs1_15.new(key).sign(h)
    sign_time = (time.perf_counter() - start) / iterations * 1000

    # Verification
    start = time.perf_counter()
    for _ in range(iterations * 10):  # RSA verify is fast
        pkcs1_15.new(key).verify(h, signature)
    verify_time = (time.perf_counter() - start) / (iterations * 10) * 1000

    return {
        "keygen_ms": keygen_time,
        "sign_ms": sign_time,
        "verify_ms": verify_time,
        "pk_bytes": len(key.export_key()),
        "sig_bytes": len(signature)
    }

def benchmark_ecdsa(curve, iterations=1000):
    """Benchmark ECDSA"""
    # Key generation
    start = time.perf_counter()
    for _ in range(iterations):
        key = ECC.generate(curve=curve)
    keygen_time = (time.perf_counter() - start) / iterations * 1000

    # Signing
    key = ECC.generate(curve=curve)
    message = b"Benchmark message for signing" * 10
    h = SHA256.new(message)

    start = time.perf_counter()
    for _ in range(iterations):
        signature = DSS.new(key, 'fips-186-3').sign(h)
    sign_time = (time.perf_counter() - start) / iterations * 1000

    # Verification
    verifier = DSS.new(key, 'fips-186-3')
    start = time.perf_counter()
    for _ in range(iterations):
        verifier.verify(h, signature)
    verify_time = (time.perf_counter() - start) / iterations * 1000

    return {
        "keygen_ms": keygen_time,
        "sign_ms": sign_time,
        "verify_ms": verify_time,
        "pk_bytes": len(key.export_key(format='DER')),
        "sig_bytes": len(signature)
    }

if __name__ == "__main__":
    print("="*70)
    print("ML-DSA (DILITHIUM) vs RSA/ECDSA SIGNATURE BENCHMARK")
    print("="*70)

    # ML-DSA variants
    dilithium_algs = ["Dilithium2", "Dilithium3", "Dilithium5"]
    print("\\n--- ML-DSA (Dilithium) Performance ---")
    for alg in dilithium_algs:
        result = benchmark_signature(alg)
        print(f"\\n{alg}:")
        print(f"  Key Generation:  {result['keygen_ms']:.3f} ms")
        print(f"  Signing:         {result['sign_ms']:.3f} ms")
        print(f"  Verification:    {result['verify_ms']:.3f} ms")
        print(f"  Public Key Size: {result['pk_bytes']} bytes")
        print(f"  Signature Size:  {result['sig_bytes']} bytes")

    # RSA comparison
    print("\\n--- RSA Signatures ---")
    for size in [2048, 3072]:
        result = benchmark_rsa_signature(size, iterations=50)
        print(f"\\nRSA-{size}:")
        print(f"  Key Generation:  {result['keygen_ms']:.3f} ms")
        print(f"  Signing:         {result['sign_ms']:.3f} ms")
        print(f"  Verification:    {result['verify_ms']:.3f} ms")
        print(f"  Public Key Size: {result['pk_bytes']} bytes")
        print(f"  Signature Size:  {result['sig_bytes']} bytes")

    # ECDSA comparison
    print("\\n--- ECDSA Signatures ---")
    for curve in ['P-256', 'P-384']:
        result = benchmark_ecdsa(curve)
        print(f"\\nECDSA-{curve}:")
        print(f"  Key Generation:  {result['keygen_ms']:.3f} ms")
        print(f"  Signing:         {result['sign_ms']:.3f} ms")
        print(f"  Verification:    {result['verify_ms']:.3f} ms")
        print(f"  Public Key Size: {result['pk_bytes']} bytes")
        print(f"  Signature Size:  {result['sig_bytes']} bytes")

    print("\\n" + "="*70)

Script 3: Run All Benchmarks and Generate Report

#!/bin/bash
# comprehensive-benchmark.sh

python3 benchmark_kem.py > results/kem-benchmark.txt
python3 benchmark_signatures.py > results/sig-benchmark.txt

# Generate HTML report
python3 generate_benchmark_report.py \
  --input results/ \
  --output reports/pqc-performance-report.html

Typical Benchmark Results

Key Encapsulation (KEM)

Algorithm Security Level KeyGen (ms) Encap (ms) Decap (ms) PK Size CT Size
Kyber512 NIST L1 (AES-128) 0.02 0.03 0.03 800 B 768 B
Kyber768 NIST L3 (AES-192) 0.03 0.04 0.05 1,184 B 1,088 B
Kyber1024 NIST L5 (AES-256) 0.05 0.06 0.06 1,568 B 1,568 B
RSA-2048 ~NIST L1 45 0.10 2.5 294 B 256 B
RSA-3072 ~NIST L3 150 0.15 7.0 422 B 384 B

Digital Signatures

Algorithm Security Level KeyGen (ms) Sign (ms) Verify (ms) PK Size Sig Size
Dilithium2 NIST L2 0.08 0.30 0.10 1,312 B 2,420 B
Dilithium3 NIST L3 0.12 0.50 0.15 1,952 B 3,293 B
Dilithium5 NIST L5 0.18 0.70 0.22 2,592 B 4,595 B
RSA-2048 ~NIST L1 45 3.0 0.08 294 B 256 B
ECDSA P-256 NIST L1 0.50 0.60 0.70 91 B 64 B
Key Takeaways:
  • ML-KEM (Kyber): 10-40x faster key generation than RSA, 50x faster decryption
  • ML-DSA (Dilithium): Faster than RSA for signing, competitive with ECDSA
  • Trade-off: PQC algorithms have larger key/signature sizes (2-5x)
  • Impact: Minimal latency increase (<1ms) for most applications
  • Bandwidth: Consider larger TLS handshakes and certificate sizes

Additional Resources

100% Free & Open Source Educational Resource

SpinDynamics.io - Making Quantum Security Accessible to All

← Return to Homepage