"""Benchmark: encryptietijd AES vs hybride (AES + RSA). Metingen per datagrootte (1 MB, 5 MB, 10 MB), telkens 10 herhalingen: a) AES-encryptietijd (alleen data-encryptie) b) RSA-encryptietijd van alleen de AES-sleutel (hybride sleuteltransport) c) Totale hybride encryptietijd (AES + RSA) """ from __future__ import annotations import os import statistics import time from dataclasses import dataclass from typing import Callable, Iterable, Sequence from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes # Vaste (vooraf gegenereerde) RSA publieke sleutel. Alleen deze key wordt gebruikt. RSA_PUBLIC_KEY_PEM = b"""-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjq1Zauh/fLk313ixWxP7 KE4WoETSl4GXwRMYDiv5xuFL6DKSVbzEJAJtl5kYu6/Y6U8Fevrbgi40cmUHaGWh Em+bOdNZXTV9XuYWuM0yrhI7iXnoyqd0CyRqLmSBgeLROUFgfwziJzqp/nLCLMs6 mR3Fk+s4qGBuLDHFy66AC1NjlG6C8odCMF1K/H23wnYVn2V2WGFQ0lfVv3okCAaP kVAYhoU6C3MoRJyG5GKO40b4Zqt6unt3MUh52T0A+UQtESxuTK4ol3QGIrw7AyTa J36CX5C0WNrmhNkt72dDJvwdk/EW5STsTJSaQ1alg25yRnTPfuRvt8NTajmoCuNI 8QIDAQAB -----END PUBLIC KEY----- """ AES_KEY_BYTES = 32 # 256-bit sleutel AES_GCM_NONCE_BYTES = 12 REPEATS = 1000 SIZES_MB = (1, 5, 10) @dataclass(frozen=True) class BenchmarkResult: size_mb: int aes_ms_avg: float rsa_key_ms_avg: float hybrid_total_ms_avg: float def generate_random_data(num_bytes: int) -> bytes: """Genereert willekeurige plaintext in-memory (geen I/O).""" return os.urandom(num_bytes) def aes_encrypt_gcm(plaintext: bytes, key: bytes, nonce: bytes) -> tuple[bytes, bytes]: """AES-256 encryptie (GCM). Meetbare operatie: alleen encryptie van de data. """ encryptor = Cipher(algorithms.AES(key), modes.GCM(nonce)).encryptor() ciphertext = encryptor.update(plaintext) + encryptor.finalize() return ciphertext, encryptor.tag def rsa_encrypt_aes_key(aes_key: bytes, rsa_public_key) -> bytes: """RSA-encryptie van alleen de AES-sleutel (hybride encryptie). """ return rsa_public_key.encrypt( aes_key, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None, ), ) def time_operation_ms(fn: Callable[[], object], repeats: int = REPEATS) -> float: """Meet de tijd van een operatie in milliseconden. """ durations_ms: list[float] = [] for _ in range(repeats): start = time.perf_counter() fn() end = time.perf_counter() durations_ms.append((end - start) * 1000.0) return statistics.fmean(durations_ms) def load_fixed_rsa_public_key(): """Laadt de vaste RSA publieke sleutel uit de hardcoded PEM.""" return serialization.load_pem_public_key(RSA_PUBLIC_KEY_PEM) def _prepare_keys_and_nonces(repeats: int) -> tuple[list[bytes], list[bytes]]: """Genereert key/nonce materiaal buiten de timing. """ keys = [os.urandom(AES_KEY_BYTES) for _ in range(repeats)] nonces = [os.urandom(AES_GCM_NONCE_BYTES) for _ in range(repeats)] return keys, nonces def benchmark_for_size(size_bytes: int, rsa_public_key, repeats: int = REPEATS) -> tuple[float, float, float]: """Voert alle metingen uit voor één datagrootte. Returns: (aes_avg_ms, rsa_key_avg_ms, hybrid_total_avg_ms) """ plaintext = generate_random_data(size_bytes) # buiten timing aes_keys, nonces = _prepare_keys_and_nonces(repeats) # buiten timing # a) AES-only: meet alleen AES-encryptie van de payload. aes_ms = time_operation_ms( fn=lambda: aes_encrypt_gcm(plaintext, aes_keys[_benchmark_index], nonces[_benchmark_index]), repeats=repeats, ) # b) RSA-only: meet alleen RSA-encryptie van de AES-sleutel (32 bytes). rsa_ms = time_operation_ms( fn=lambda: rsa_encrypt_aes_key(aes_keys[_benchmark_index], rsa_public_key), repeats=repeats, ) # c) Hybride: meet AES encryptie + RSA encryptie van dezelfde AES-sleutel. hybrid_ms = time_operation_ms( fn=lambda: ( aes_encrypt_gcm(plaintext, aes_keys[_benchmark_index], nonces[_benchmark_index]), rsa_encrypt_aes_key(aes_keys[_benchmark_index], rsa_public_key), ), repeats=repeats, ) return aes_ms, rsa_ms, hybrid_ms # Interne teller zodat we per repeat een ander (vooraf gemaakt) key/nonce paar gebruiken, # zonder key/nonce generatie te meten. _benchmark_index = 0 def _time_operation_ms_with_indexed_inputs(fn_builder: Callable[[int], Callable[[], object]], repeats: int) -> float: """Zoals time_operation_ms(), maar geeft per herhaling een index door. Dit maakt het mogelijk om per herhaling een andere key/nonce te gebruiken zonder random generatie in de timed sectie. """ durations_ms: list[float] = [] for i in range(repeats): fn = fn_builder(i) start = time.perf_counter() fn() end = time.perf_counter() durations_ms.append((end - start) * 1000.0) return statistics.fmean(durations_ms) def benchmark_for_size_clean(size_bytes: int, rsa_public_key, repeats: int = REPEATS) -> tuple[float, float, float]: """Zelfde als benchmark_for_size(), maar zonder globale state.""" plaintext = generate_random_data(size_bytes) # buiten timing aes_keys, nonces = _prepare_keys_and_nonces(repeats) # buiten timing aes_ms = _time_operation_ms_with_indexed_inputs( fn_builder=lambda i: (lambda: aes_encrypt_gcm(plaintext, aes_keys[i], nonces[i])), repeats=repeats, ) rsa_ms = _time_operation_ms_with_indexed_inputs( fn_builder=lambda i: (lambda: rsa_encrypt_aes_key(aes_keys[i], rsa_public_key)), repeats=repeats, ) hybrid_ms = _time_operation_ms_with_indexed_inputs( fn_builder=lambda i: ( lambda: ( aes_encrypt_gcm(plaintext, aes_keys[i], nonces[i]), rsa_encrypt_aes_key(aes_keys[i], rsa_public_key), ) ), repeats=repeats, ) return aes_ms, rsa_ms, hybrid_ms def run_benchmarks() -> list[BenchmarkResult]: """Run benchmarks voor 1MB/5MB/10MB en geef resultaten terug.""" rsa_public_key = load_fixed_rsa_public_key() results: list[BenchmarkResult] = [] for size_mb in SIZES_MB: size_bytes = size_mb * 1024 * 1024 aes_ms, rsa_ms, hybrid_ms = benchmark_for_size_clean(size_bytes, rsa_public_key, repeats=REPEATS) results.append( BenchmarkResult( size_mb=size_mb, aes_ms_avg=aes_ms, rsa_key_ms_avg=rsa_ms, hybrid_total_ms_avg=hybrid_ms, ) ) return results def print_results(results: Sequence[BenchmarkResult]) -> None: """Print resultaten overzichtelijk per datagrootte.""" print("Encryptie-benchmark (in-memory, alleen encryptie-operaties)") print(f"AES: 256-bit sleutel, blokgrootte 128-bit, modus: GCM") print(f"RSA: OAEP(SHA-256), uitsluitend voor AES-sleutel") print(f"Herhalingen per meting: {REPEATS}") print("-") for r in results: print(f"Datagrootte: {r.size_mb} MB") print(f" a) AES-only encryptie: {r.aes_ms_avg:.3f} ms (gemiddeld)") print(f" b) RSA encryptie van AES-sleutel: {r.rsa_key_ms_avg:.3f} ms (gemiddeld)") print(f" c) Hybride totaal (AES + RSA): {r.hybrid_total_ms_avg:.3f} ms (gemiddeld)") print("-") if __name__ == "__main__": print_results(run_benchmarks())