Cryptography: encryption, algorithms, PKI and post-quantum
Cryptography is the technical foundation of IT security. This article explains symmetric and asymmetric encryption (AES, RSA, ECC), hash functions and password hashing (bcrypt, Argon2), digital signatures, PKI hierarchies, TLS 1.3 with specific Nginx configurations, post-quantum cryptography (ML-KEM, ML-DSA), BSI TR-02102 recommendations, and common implementation errors in practice.
Table of Contents (13 sections)
Without cryptography, there is no IT security. HTTPS, password hashing, digital signatures, end-to-end encryption—all of these rely on cryptographic algorithms. "Encryption is secure" is one of the most common misconceptions in computer science: The mathematics behind AES, RSA, or ECC is often indeed secure. The problem lies in the implementation: incorrect modes, weak keys, insecure random numbers, incorrect padding methods.
The Three Fundamental Cryptographic Problems
Cryptography solves three fundamental security problems:
- Confidentiality: Only authorized parties can read data (encryption)
- Integrity: Data cannot be altered without detection (hash functions, MAC)
- Authenticity: The sender’s identity can be verified (digital signatures, certificates)
The three pillars of cryptography:
Symmetric cryptography:
→ Same key for encryption and decryption
→ Fast, but key exchange is the problem
→ Application: Data encryption (AES, ChaCha20)
Asymmetric cryptography (public-key):
→ Key pair: Public key (public) + Private key (secret)
→ Slower, but solves the key exchange problem
→ Application: TLS handshake, digital signatures, email encryption
Hash functions:
→ One-way function: from arbitrary data → fixed length
→ Collision-resistant: no two inputs should have the same hash
→ Applications: Password storage, integrity checks, signatures
Symmetric Encryption
In symmetric encryption, the sender and receiver use the same key for encryption and decryption.
Advantage: Very fast, efficient for large amounts of data. Disadvantage: Key exchange problem—how do you securely transmit the secret key?
AES (Advanced Encryption Standard)
AES is the global standard for symmetric encryption.
Key lengths:
AES-128: 128 bits—sufficient for most applications (until ~2030)
AES-192: 192 bits – rarely used
AES-256: 256 bits – recommended for highly sensitive data, post-quantum secure
Block modes – crucial for security:
ECB (Electronic Codebook) – NEVER USE:
→ Same plaintext → same ciphertext (deterministic)
→ Patterns visible in the plaintext are visible in the ciphertext!
→ Famous example: an encrypted Tux image remains recognizable
CBC (Cipher Block Chaining) – obsolete, prone to errors:
→ IV (Initialization Vector) must be random and unique
→ Vulnerable to padding oracle attacks (POODLE, Lucky Thirteen)
→ Not recommended for new implementations
CTR (Counter Mode) - good, but without authentication:
→ Converts block cipher to stream cipher
→ Nonce MUST be unique (nonce reuse = catastrophic!)
→ No integrity check → Man-in-the-middle possible
GCM (Galois/Counter Mode) - RECOMMENDED:
→ Authenticated encryption: AEAD (Encryption + Integrity)
→ Nonce MUST be unique (96 bits recommended)
→ Standard for TLS 1.3, Signal, WireGuard
→ Nonce reuse attack: If nonce is used twice → complete decryption!
ChaCha20-Poly1305:
→ Alternative to AES-GCM
→ Better on systems without AES hardware acceleration (IoT)
→ Used in TLS 1.3 and Signal Protocol
SIV (Synthetic IV) - for deterministic encryption:
→ When the same data always requires the same result (searchable)
→ Nonce-reuse-resistant
Obsolete/insecure algorithms - do not use anymore:
- DES / 3DES - Key length too short (56 bits), practically broken
- RC4 - Known severe weaknesses, prohibited in TLS
# AES-256-GCM in Python (correct usage)
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
def encrypt(key: bytes, plaintext: bytes) -> tuple[bytes, bytes]:
"""Encrypts using AES-256-GCM. Returns (nonce, ciphertext)."""
aesgcm = AESGCM(key)
nonce = os.urandom(12) # 12-byte nonce for GCM (NEVER reuse!)
ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data=None)
return nonce, ciphertext
def decrypt(key: bytes, nonce: bytes, ciphertext: bytes) -> bytes:
"""Decrypts and automatically verifies integrity (AuthTag)."""
aesgcm = AESGCM(key)
return aesgcm.decrypt(nonce, ciphertext, associated_data=None)
# Generate key (32 bytes = 256 bits)
key = os.urandom(32)
nonce, ct = encrypt(key, b"Secret message")
plaintext = decrypt(key, nonce, ct)
// Node.js (crypto built-in) - AES-256-GCM
const crypto = require('crypto');
const algorithm = 'aes-256-gcm';
function encrypt(plaintext, key) {
const nonce = crypto.randomBytes(12);
const cipher = crypto.createCipheriv(algorithm, key, nonce);
const encrypted = Buffer.concat([
cipher.update(plaintext, 'utf8'),
cipher.final()
]);
const tag = cipher.getAuthTag(); // Authentication tag!
return { nonce, encrypted, tag };
}
function decrypt(nonce, encrypted, tag, key) {
const decipher = crypto.createDecipheriv(algorithm, key, nonce);
decipher.setAuthTag(tag); // Tag MUST be verified!
return Buffer.concat([
decipher.update(encrypted),
decipher.final()
]).toString('utf8');
}
Asymmetric Encryption (Public-Key Cryptography)
In asymmetric encryption, there is a key pair:
- Public Key: Public—anyone can know it and use it to encrypt
- Private Key: Secret—only the owner has it; only they can decrypt
The genius of it: Both keys are mathematically linked, but the private key cannot be derived from the public key in a reasonable amount of time.
RSA
RSA is based on the difficulty of factoring large numbers.
Key lengths (BSI TR-02102 Recommendation 2024):
RSA-1024: BROKEN (do not use anymore!)
RSA-2048: Minimum – acceptable until ~2030
RSA-3072: Recommended until 2030+
RSA-4096: Secure in the long term; ECDSA is preferable for TLS!
Applications:
→ Digital signatures (e.g., code signing, PDF signatures, PKI certificates)
→ Key encapsulation (TLS – being replaced by ECDH)
RSA Padding - critical:
PKCS#1 v1.5: OBSOLETE, vulnerable to Bleichenbacher attack
OAEP (Optimal Asymmetric Encryption Padding): RECOMMENDED for encryption
PSS (Probabilistic Signature Scheme): RECOMMENDED for signatures
# Python - Encrypting with OAEP:
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes
ciphertext = public_key.encrypt(
plaintext,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
ECC (Elliptic Curve Cryptography)
ECC offers the same security as RSA with significantly smaller keys.
Comparison:
RSA-3072 ≈ ECDSA-P256 (256-bit curve)
RSA-7680 ≈ ECDSA-P384 (384-bit curve)
Much faster (10–100x), lower computational overhead → good for mobile/IoT
Curves and their properties:
P-256 (secp256r1):
→ Standard curve for TLS, FIDO2, code signing, Apple, Google
→ Recommended by BSI: "sufficiently secure"
P-384 (secp384r1):
→ Higher security, recommended for government systems
Curve25519 (Ed25519 / X25519):
→ By Daniel Bernstein, no NIST specification
→ Considered particularly trustworthy
→ Standard in: Signal, WireGuard, SSH keys
→ Recommended for new systems!
Applications:
ECDSA: Digital signatures (TLS, Bitcoin)
ECDH: Key Agreement (Diffie-Hellman on elliptic curves)
EdDSA: Modern signatures (Ed25519, Ed448)
Generate an SSH key with Ed25519:
ssh-keygen -t ed25519 -C "user@firma.de"
# Significantly better than the RSA-2048 default!
# ECDH Key Exchange (Python)
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
alice_private = X25519PrivateKey.generate()
alice_public = alice_private.public_key()
bob_private = X25519PrivateKey.generate()
bob_public = bob_private.public_key()
# Key Exchange: both derive the same shared secret
alice_shared = alice_private.exchange(bob_public)
bob_shared = bob_private.exchange(alice_public)
assert alice_shared == bob_shared # True! Without transferring private keys
# Ed25519 for signatures:
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key()
signature = private_key.sign(message)
public_key.verify(signature, message) # Raises an exception if invalid
Diffie-Hellman / ECDH
A key exchange protocol, not an encryption algorithm. Enables secure key exchange over an insecure channel.
Perfect Forward Secrecy (PFS): Ephemeral Diffie-Hellman – each session has its own keys. Compromising the long-term key does not compromise past sessions.
Hash Functions
A cryptographic hash function transforms inputs of any size into a fixed-length fingerprint:
Input: → Hash (SHA-256, 256 bits)
"Hello" → 185f8db32921bd46d35...
"Hello!" → d9414818c6cde70b1e2... (completely different!)
"The complete Faust epic..." → 3a2c981d7e4f8b9c...
Properties:
- Determinism: Same input → always the same hash
- One-way function: The input cannot be reconstructed from the hash
- Collision resistance: Practically impossible to find two different inputs with the same hash
- Avalanche effect: The slightest change in the input → completely different hash
| Algorithm | Output length | Status |
|---|---|---|
| MD5 | 128 bits | Broken - Collisions known, never use for security! |
| SHA-1 | 160 bits | Broken - Google SHAttered 2017 ("Shattered" attack: two PDFs with the same SHA-1) |
| SHA-256 | 256 bits | Secure, standard, recommended |
| SHA-384 | 384 bits | Secure, higher security requirements |
| SHA-512 | 512 bits | Secure, higher security requirements |
| SHA-3 / Keccak | variable | Secure, different construction (sponge function) – backup in case SHA-2 is compromised |
| Blake2 / Blake3 | variable | Very fast, secure; good for checksums |
Applications: File integrity, download checksums, HMAC, digital signatures (hash is signed, not the document), Git commit IDs.
Password Hashing
Cryptographic hashes like SHA-256 are too fast for passwords. An RTX 4090 can compute 100+ billion SHA-256 hashes per second. Instead: Password-specific algorithms with a built-in "work factor":
NEVER: Store passwords as MD5 or SHA-256!
→ Without a salt: Rainbow tables crack them in seconds
→ With a salt, but a fast hash: GPU cracking
Correct password hash algorithms:
bcrypt: Cost Factor (2^cost iterations), 72 bytes max, old but solid
Cost Factor 12 (min.) for new applications
scrypt: Memory-hard, resource-intensive, NIST SP 800-132 compliant
Argon2id: RECOMMENDED - OWASP 2024, PHC winner
Three variants: Argon2d (GPU-resistant), Argon2i (timing-safe), Argon2id (both)
PBKDF2: FIPS-140 compliant, good for compliance requirements
Iterations: at least 600,000 for SHA-256 (OWASP 2023)
OWASP recommendation for Argon2id:
Memory: 64 MB minimum (m=65536)
Time: 3 iterations minimum (t=3)
Threads: Number of CPU cores (p=4)
# Argon2id (Python - argon2-cffi)
from argon2 import PasswordHasher
ph = PasswordHasher(
time_cost=3, # Iterations
memory_cost=65536, # 64 MB
parallelism=4, # 4 threads
hash_len=32, # 256-bit output
salt_len=16 # 128-bit salt
)
hash = ph.hash("MyPassword123!")
try:
ph.verify(hash, "MyPassword123!") # True
if ph.check_needs_rehash(hash):
hash = ph.hash("MyPassword123!") # Re-hash with new parameters
except VerifyMismatchError:
print("Incorrect password!")
Message Authentication Code (MAC)
A MAC combines a message with a secret key to create a proof of integrity:
MAC = HMAC(key, message)
The recipient recalculates the MAC and compares it—if it matches, the message is unaltered and comes from someone with the key. Applications: API signatures (HMAC-SHA256 in OAuth, JWT), TLS record layer.
Digital Signatures
Digital signatures use asymmetric cryptography for authenticity and non-repudiation:
Signing: Hash(document) + private-key encryption = signature
Verifying: Hash(document) == public-key decryption(Signature) ?
Uses: Code signing, email signatures (S/MIME, PGP), TLS certificates, DKIM.
# GPG signature for software release
gpg --armor --detach-sign myapp-1.2.3-linux.tar.gz
# → myapp-1.2.3-linux.tar.gz.asc (Signature)
# Verification by user
gpg --verify myapp-1.2.3-linux.tar.gz.asc myapp-1.2.3-linux.tar.gz
# Windows Code Signing (signtool)
signtool sign /fd SHA256 /tr http://timestamp.digicert.com \
/td SHA256 /f mycert.pfx /p password myapp.exe
Public Key Infrastructure (PKI)
The Problem: Who owns a public key? How do I know that the public key of "Deutsche Bank" really belongs to Deutsche Bank?
PKI Solution: A trusted Certificate Authority (CA) issues digital certificates that link public keys to identities and sign them with the CA key.
Root CA (self-signed, offline! Locked in HSM)
│
Intermediate CA (signed by Root CA)
│
End-Entity Certificates (signed by Intermediate CA)
├── Web Server Certificate (TLS)
├── Code Signing Certificate
├── Email Certificate (S/MIME)
└── Client certificate (mTLS)
Why is the Root CA offline?
→ If the Root CA is compromised → all issued certificates are invalid
→ Root CA only for: issuing new Intermediate CAs
TLS process (simplified):
- Browser accesses https://bank.de
- Server sends certificate
- Browser checks: Is the certificate valid? Signed by a trusted CA? Issued for bank.de?
- Browser and server negotiate session key (ECDHE)
- All further communication encrypted with AES-256-GCM
Certificate types:
- DV (Domain Validation): Only domain ownership is verified—for small websites
- OV (Organization Validation): Organization is verified—for businesses
- Wildcard:
*.example.com—applies to all subdomains - SAN (Subject Alternative Names): Multiple domains in a single certificate
Certificate Transparency (CT): All TLS certificates must be logged in public CT logs. Monitoring via crt.sh.
TLS - Transport Layer Security
TLS versions:
SSL 2.0 / 3.0: Completely obsolete - never use
TLS 1.0: Obsolete - POODLE, BEAST attacks
TLS 1.1: Obsolete - deprecated since 2020
TLS 1.2: Acceptable with secure cipher suites
TLS 1.3: RECOMMENDED - faster, more secure, no legacy baggage
TLS 1.3 Improvements:
✓ Always forward secrecy (ECDHE/DHE mandatory)
✓ No more RSA key transport
✓ Weak cipher suites removed (RC4, 3DES, MD5, SHA1)
✓ Handshake in 1-RTT (instead of 2-RTT) - faster
✓ 0-RTT resumption (but note the risk of replay attacks!)
TLS 1.3 Cipher Suites (only 5 allowed):
TLS_AES_256_GCM_SHA384 ← Recommended
TLS_CHACHA20_POLY1305_SHA256 ← Recommended
TLS_AES_128_GCM_SHA256 ← Minimum
TLS_AES_128_CCM_8_SHA256 (IoT)
TLS_AES_128_CCM_SHA256 (IoT)
Secure TLS 1.2 Cipher Suites:
ECDHE-ECDSA-AES256-GCM-SHA384 ← Perfect
ECDHE-RSA-AES256-GCM-SHA384 ← Good
ECDHE-ECDSA-AES128-GCM-SHA256 ← Good
INSECURE (do not use anymore):
ECDHE-RSA-AES256-CBC-SHA ← CBC vulnerable to padding oracle
DH-RSA-AES256-GCM-SHA384 ← No "E" = no forward secrecy!
RC4-SHA ← RC4 completely broken
Nginx configuration (TLS 1.3 + secure headers):
ssl_protocols TLSv1.3 TLSv1.2;
ssl_prefer_server_ciphers off; # Client determines cipher in TLS 1.3
ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-RSA-AES256-GCM-SHA384';
ssl_ecdh_curve X25519:secp384r1;
ssl_session_tickets off; # Disable session tickets (PFS!)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header X-Content-Type-Options nosniff;
Test TLS:
testssl.sh --full https://firma.de
ssllabs.com/ssltest (Online, grades A through F)
BSI Recommendation (TR-02102-2):
- Prefer TLS 1.3, TLS 1.2 acceptable
- Disable TLS 1.0 and 1.1
- Secure cipher suites: TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256
Post-Quantum Cryptography
The Quantum Computer Problem:
Shor's algorithm can break RSA and ECC in polynomial time.
A sufficiently powerful quantum computer would:
→ Break RSA-2048 in hours/days (instead of billions of years)
→ Crack ECDSA-P256 similarly quickly
"Harvest Now, Decrypt Later":
→ Attackers collect encrypted communications today
→ Decrypt them as soon as quantum computers become available
→ Timeframe: cryptographically relevant quantum computers ca. 2030–2035
→ For data sensitive in the long term: Evaluate PQC NOW!
NIST PQC Standards (finalized in 2024):
ML-KEM (CRYSTALS-Kyber): Key Encapsulation Mechanism - replaces RSA/ECDH in TLS
ML-DSA (CRYSTALS-Dilithium): Digital signatures - replaces RSA/ECDSA
SLH-DSA (SPHINCS+): Hash-based signatures, conservative backup
BSI Recommendation (TR-02102-1):
→ Starting in 2025: Hybrid cryptography (classical + PQC)
→ By 2030 at the latest: Complete migration to PQC
→ Classified data: IMMEDIATELY use hybrid cryptography
→ Crypto-agility as a design principle: Interchangeable algorithms
TLS 1.3 + PQC (already supported by Chrome):
X25519MLKEM768 - Hybrid curve (X25519 + ML-KEM-768)
→ Already active in Chrome 131+ and Firefox 132+
OpenSSH and Kyber: hybrid ECDH + Kyber starting with OpenSSH 9.0!
BSI Recommendations for Algorithms and Key Lengths
The BSI regularly publishes Technical Guideline TR-02102 with specific recommendations:
| Method | Recommendation | Minimum Key Length |
|---|---|---|
| Symmetric (AES) | Recommended | AES-128 (normal), AES-256 (enhanced) |
| Hash | SHA-256 and above | 256 bits |
| RSA | Acceptable | 2000 bits (until 2026), 3000 bits thereafter |
| ECDH/ECDSA | Recommended | 256 bits (P-256 or Brainpool) |
| Post-Quantum | Recommended for new systems | ML-KEM-768+ |
Common Implementation Errors
Most Common Cryptography Errors (OWASP A02:2021):
Error 1: Developing Custom Cryptography
WRONG: Implementing a custom AES mode
RIGHT: Using established libraries (OpenSSL, libsodium, cryptography)
Error 2: Outdated Algorithms
MD5, SHA-1, DES, 3DES, RC4 → all broken or too weak
TLS 1.0, TLS 1.1 → disable!
Mistake 3: Poor key management
→ Hardcoded keys in the source code
→ Same key for encryption and signing
→ Keys never rotated
Solution: HSM, Vault, AWS KMS, Azure Key Vault
Mistake 4: Incorrect password hashing
→ MD5/SHA-1 with or without salt
→ PBKDF2 with too few iterations
Solution: Argon2id with OWASP parameters
Error 5: Nonce/IV reuse
→ Nonce used twice in AES-GCM → complete decryption possible!
Solution: os.urandom() for every encryption operation
Error 6: Ignoring side-channel attacks
→ Timing attacks during string comparison
WRONG: if stored_token == provided_token
CORRECT: hmac.compare_digest(stored_token, provided_token)
Error 7: Random number errors
Python: random.random() → NOT cryptographically secure!
Java: Math.random() → NOT cryptographically secure!
JS: Math.random() → NOT cryptographically secure!
Correct:
Python: secrets.token_bytes(32)
Java: SecureRandom.getInstanceStrong()
Node.js: crypto.randomBytes(32)
Go: crypto/rand.Read()
Conclusion
Cryptography is the foundation of every IT security architecture. Without properly implemented encryption, even perfectly configured firewalls and access systems are ineffective if the transmitted or stored data is in plain text. The most important practical implications: TLS 1.3 everywhere, AES-256-GCM for data storage, SHA-256 for integrity, Argon2id for passwords—and early planning for the migration to post-quantum cryptography.
Sources & References
- [1] NIST Post-Quantum Cryptography Standardization - NIST
- [2] BSI: Kryptographische Verfahren - Empfehlungen und Schlüssellängen TR-02102 - BSI
- [3] RFC 8446 - TLS 1.3 - IETF
- [4] OWASP Cryptographic Storage Cheat Sheet - OWASP
Questions about this topic?
Our experts advise you free of charge and without obligation.
About the Author
M.Sc. Internet-Sicherheit (if(is), Westfälische Hochschule). COO und Prokurist mit Expertise in Informationssicherheitsberatung und Security Awareness. Nachwuchsprofessor für Cyber Security an der FOM Hochschule, CISO-Referent bei der isits AG und Promovend am Graduierteninstitut NRW.
11 Publikationen
- Understanding Regional Filter Lists: Efficacy and Impact (2025)
- Privacy from 5 PM to 6 AM: Tracking and Transparency Mechanisms in the HbbTV Ecosystem (2025)
- A Platform for Physiological and Behavioral Security (2025)
- Different Seas, Different Phishes — Large-Scale Analysis of Phishing Simulations Across Different Industries (2025)
- Exploring the Effects of Cybersecurity Awareness and Decision-Making Under Risk (2024)
- Sharing is Caring: Towards Analyzing Attack Surfaces on Shared Hosting Providers (2024)
- On the Similarity of Web Measurements Under Different Experimental Setups (2023)
- People, Processes, Technology — The Cybersecurity Triad (2023)
- Social Media Scraper im Einsatz (2021)
- Digital Risk Management (DRM) (2020)
- New Work — Die Herausforderungen eines modernen ISMS (2024)