Loading content...
In 1985, Neal Koblitz and Victor Miller independently proposed using elliptic curves for cryptography. Their insight was revolutionary: the discrete logarithm problem in elliptic curve groups appears to be fundamentally harder than in traditional finite fields. This means equivalent security can be achieved with dramatically smaller keys.
Today, Elliptic Curve Diffie-Hellman (ECDH) has largely replaced traditional DH in modern systems. TLS 1.3, Signal, WireGuard, SSH, and countless other protocols default to elliptic curve variants. Understanding ECDH is essential for anyone working with contemporary cryptographic systems.
The benefits are compelling: a 256-bit elliptic curve key provides security comparable to a 3072-bit traditional DH key, with key exchanges running 10-20x faster.
By the end of this page, you will understand how elliptic curves work at a conceptual level, how ECDH mirrors traditional DH on a different mathematical structure, the security and efficiency advantages of ECDH, popular curves (P-256, X25519, X448) and their properties, and practical considerations for implementing ECDH securely.
To appreciate ECDH, we must first understand why elliptic curves offer a cryptographic advantage over traditional modular arithmetic.
The efficiency problem with traditional DH:
Recall that traditional DH security depends on the difficulty of the discrete logarithm problem (DLP) in Z*_p. The best attacks (Number Field Sieve) run in subexponential time:
$$L_p[1/3, 1.9] = \exp(1.9 \cdot (\ln p)^{1/3} \cdot (\ln \ln p)^{2/3})$$
This means that as we need more security, key sizes grow disproportionately:
| Security Level | Traditional DH (bits) | ECDH (bits) | Size Ratio |
|---|---|---|---|
| 80-bit | 1024 | 160 | 6.4x smaller |
| 112-bit | 2048 | 224 | 9.1x smaller |
| 128-bit | 3072 | 256 | 12x smaller |
| 192-bit | 7680 | 384 | 20x smaller |
| 256-bit | 15360 | 512 | 30x smaller |
Why is ECDH so efficient?
The discrete logarithm problem in elliptic curve groups resists subexponential attacks. The best known algorithms (Pollard's rho, baby-step giant-step) run in exponential time—approximately O(√n) where n is the group order.
This means:
The result: identical security with dramatically smaller keys and faster operations.
For traditional DH with a 3072-bit prime, the Number Field Sieve runs in ~2^128 operations. For a 256-bit elliptic curve, Pollard's rho also runs in ~2^128 operations. The curve is 12x smaller yet equally secure because generic EC attacks are inherently harder.
An elliptic curve for cryptography is not the same as an ellipse. It's a curve defined by a specific polynomial equation with remarkable algebraic properties.
Short Weierstrass Form:
The most common form is:
$$y^2 = x^3 + ax + b$$
where a and b are constants satisfying 4a³ + 27b² ≠ 0 (ensuring no singularities).
For cryptography, we work with elliptic curves over finite fields, typically:
Points on the curve:
The curve consists of all points (x, y) satisfying the equation, plus a special "point at infinity" denoted O (which serves as the identity element).
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
"""Elliptic Curve Fundamentals for Cryptography This demonstrates the core operations that make EC cryptography work.""" from dataclasses import dataclassfrom typing import Optional, Tuple @dataclass(frozen=True)class Point: """A point on an elliptic curve.""" x: Optional[int] # None for point at infinity y: Optional[int] def is_infinity(self) -> bool: return self.x is None and self.y is None def __repr__(self) -> str: if self.is_infinity(): return "O (point at infinity)" return f"({self.x}, {self.y})" # Point at infinity (identity element)INFINITY = Point(None, None) class EllipticCurve: """ Elliptic curve over a prime field: y² = x³ + ax + b (mod p) This is a simplified educational implementation. Production code must use constant-time operations. """ def __init__(self, a: int, b: int, p: int): """ Initialize curve with parameters. Args: a, b: Curve coefficients p: Prime modulus (size of the field) """ self.a = a self.b = b self.p = p # Verify curve is non-singular discriminant = (4 * pow(a, 3, p) + 27 * pow(b, 2, p)) % p assert discriminant != 0, "Singular curve (discriminant = 0)" def is_on_curve(self, point: Point) -> bool: """Check if a point lies on the curve.""" if point.is_infinity(): return True left = pow(point.y, 2, self.p) right = (pow(point.x, 3, self.p) + self.a * point.x + self.b) % self.p return left == right def add(self, P: Point, Q: Point) -> Point: """ Add two points on the elliptic curve. This is the core operation. Addition has these cases: 1. P = O or Q = O: Return the other point 2. P = -Q: Return O (point at infinity) 3. P ≠ Q: "Chord" rule - draw line through P,Q 4. P = Q: "Tangent" rule - use tangent at P In each case, the third intersection point (or its reflection) gives the result. """ # Case 1: Identity element if P.is_infinity(): return Q if Q.is_infinity(): return P # Case 2: Point and its inverse (P + (-P) = O) if P.x == Q.x and (P.y + Q.y) % self.p == 0: return INFINITY # Case 3 & 4: Compute slope if P.x == Q.x and P.y == Q.y: # Point doubling: tangent slope = (3x² + a) / (2y) numerator = (3 * pow(P.x, 2, self.p) + self.a) % self.p denominator = (2 * P.y) % self.p else: # Point addition: chord slope = (y2 - y1) / (x2 - x1) numerator = (Q.y - P.y) % self.p denominator = (Q.x - P.x) % self.p # Compute slope using modular inverse slope = (numerator * pow(denominator, -1, self.p)) % self.p # Compute new point x3 = (pow(slope, 2, self.p) - P.x - Q.x) % self.p y3 = (slope * (P.x - x3) - P.y) % self.p return Point(x3, y3) def multiply(self, k: int, P: Point) -> Point: """ Scalar multiplication: compute k * P = P + P + ... + P (k times) Uses double-and-add algorithm for efficiency. CRITICAL FOR ECDH: - Public key = k * G where k is private key, G is generator - k * P is easy to compute - Finding k given P and k*P is the EC discrete log problem """ if k == 0 or P.is_infinity(): return INFINITY if k < 0: k = -k P = Point(P.x, (-P.y) % self.p) result = INFINITY addend = P while k > 0: if k & 1: # If bit is set result = self.add(result, addend) addend = self.add(addend, addend) # Point doubling k >>= 1 return result # Example: secp256k1 parameters (used in Bitcoin)# This is a small demo - real secp256k1 uses 256-bit numbersdef demo_curve(): """Demonstrate elliptic curve operations on a small curve.""" # Small demonstration curve: y² = x³ + 7 (mod 67) curve = EllipticCurve(a=0, b=7, p=67) # Find points on the curve print("Points on curve y² = x³ + 7 (mod 67):") points = [] for x in range(67): y_squared = (pow(x, 3, 67) + 7) % 67 # Check if y_squared is a quadratic residue for y in range(67): if pow(y, 2, 67) == y_squared: point = Point(x, y) if curve.is_on_curve(point): points.append(point) print(f" Found {len(points)} points (plus point at infinity)") # Demonstrate addition if len(points) >= 2: P, Q = points[0], points[1] R = curve.add(P, Q) print(f" P = {P}") print(f" Q = {Q}") print(f" P + Q = {R}") print(f" R on curve: {curve.is_on_curve(R)}") # Demonstrate scalar multiplication G = points[0] # Use first point as "generator" k = 7 # Scalar kG = curve.multiply(k, G) print(f" G = {G}") print(f" 7 * G = {kG}") print(f" 7G on curve: {curve.is_on_curve(kG)}") demo_curve()The group law:
Points on an elliptic curve form a group under "addition":
This group has a crucial property for cryptography: scalar multiplication k × P (adding P to itself k times) is efficient, but finding k given P and k × P is believed to be computationally hard—the Elliptic Curve Discrete Logarithm Problem (ECDLP).
While EC addition has a beautiful geometric interpretation (chords and tangents), in practice we work entirely with algebraic formulas over finite fields. The 'visual' interpretation helps intuition but the actual computation uses modular arithmetic.
ECDH is structurally identical to traditional DH but operates on elliptic curve groups instead of multiplicative groups modulo primes. The protocol translation is elegant:
| Traditional DH | ECDH |
|---|---|
| Modular exponentiation g^k mod p | Scalar multiplication k × G |
| Group element in Z*_p | Point on elliptic curve |
| Private key: random exponent k | Private key: random scalar k |
| Public key: g^k mod p | Public key: k × G |
| Shared secret: (g^a)^b = g^(ab) | Shared secret: a × (b × G) = (ab) × G |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
"""Elliptic Curve Diffie-Hellman (ECDH) Implementation This demonstrates the complete ECDH key exchange protocol.For production, use established libraries like cryptography or libsodium.""" import secretsimport hashlibfrom dataclasses import dataclassfrom typing import Tuple # Reuse EllipticCurve and Point classes from earlier# (In practice, use a well-audited implementation) @dataclassclass ECDHParameters: """ECDH domain parameters.""" curve: 'EllipticCurve' # The curve G: 'Point' # Generator (base point) n: int # Order of G (number of points in subgroup) h: int # Cofactor @dataclassclass ECDHKeyPair: """An ECDH key pair.""" private_key: int # The secret scalar d public_key: 'Point' # Q = d × G class ECDH: """ Elliptic Curve Diffie-Hellman key exchange. Security relies on the Elliptic Curve Discrete Log Problem (ECDLP): Given G and Q = d × G, finding d is computationally infeasible. """ def __init__(self, params: ECDHParameters): self.params = params def generate_keypair(self) -> ECDHKeyPair: """ Generate a new ECDH key pair. Private key: Random integer d ∈ [1, n-1] Public key: Q = d × G """ # Generate private key: random scalar # CRITICAL: Must use cryptographically secure RNG d = secrets.randbelow(self.params.n - 1) + 1 # Compute public key: scalar multiplication Q = self.params.curve.multiply(d, self.params.G) return ECDHKeyPair(private_key=d, public_key=Q) def compute_shared_secret(self, my_private: int, other_public: 'Point') -> bytes: """ Compute the shared secret from my private key and peer's public key. S = my_private × other_public = d_A × Q_B = d_A × (d_B × G) = (d_A × d_B) × G Both parties compute the same point S. The x-coordinate of S is the raw shared secret. """ # Validate public key is on curve (CRITICAL!) if not self.params.curve.is_on_curve(other_public): raise ValueError("Invalid public key: not on curve") # Validate public key is in correct subgroup if other_public.is_infinity(): raise ValueError("Invalid public key: point at infinity") # Check that n × other_public = O (correct order) check = self.params.curve.multiply(self.params.n, other_public) if not check.is_infinity(): raise ValueError("Invalid public key: wrong order") # Compute shared secret point S = self.params.curve.multiply(my_private, other_public) if S.is_infinity(): raise ValueError("Computed shared secret is point at infinity") # Extract x-coordinate and derive key # NEVER use raw x directly; always apply KDF shared_x = S.x.to_bytes((S.x.bit_length() + 7) // 8, 'big') # Key derivation (simplified; use HKDF in production) derived_key = hashlib.sha256(shared_x).digest() return derived_key def ecdh_demo(): """ Demonstrate complete ECDH key exchange. Using a small curve for demonstration. Real systems use P-256, P-384, X25519, or X448. """ print("ECDH KEY EXCHANGE DEMONSTRATION") print("=" * 60) # Small demonstration curve: y² = x³ + 2x + 3 (mod 97) # In practice, use standardized curves like secp256r1 curve = EllipticCurve(a=2, b=3, p=97) # Find a generator point (simplified; real curves specify this) G = Point(3, 6) # Verified to be on this curve assert curve.is_on_curve(G) # Assuming order n = 89 (would need to verify for real curve) # Cofactor h = 1 params = ECDHParameters(curve=curve, G=G, n=89, h=1) # Create ECDH instances alice_ecdh = ECDH(params) bob_ecdh = ECDH(params) print(f"Curve: y² = x³ + {curve.a}x + {curve.b} (mod {curve.p})") print(f"Generator G = {G}") print(f"Order n = {params.n}") # Generate key pairs print("--- KEY GENERATION ---") alice_keys = alice_ecdh.generate_keypair() bob_keys = bob_ecdh.generate_keypair() print(f"Alice: private = {alice_keys.private_key}, public = {alice_keys.public_key}") print(f"Bob: private = {bob_keys.private_key}, public = {bob_keys.public_key}") # Exchange public keys and compute shared secrets print("--- SHARED SECRET COMPUTATION ---") alice_secret = alice_ecdh.compute_shared_secret( alice_keys.private_key, bob_keys.public_key ) bob_secret = bob_ecdh.compute_shared_secret( bob_keys.private_key, alice_keys.public_key ) print(f"Alice computes: {alice_keys.private_key} × {bob_keys.public_key}") print(f"Bob computes: {bob_keys.private_key} × {alice_keys.public_key}") print(f"Alice's derived key: {alice_secret.hex()[:32]}...") print(f"Bob's derived key: {bob_secret.hex()[:32]}...") # Verify they match assert alice_secret == bob_secret print("✓ Shared secrets match!") print("--- SECURITY NOTE ---") print("An eavesdropper sees only G, Alice's public key, and Bob's public key.") print("Computing the shared secret requires solving ECDLP.") print("=" * 60) # Run demo (requires EllipticCurve and Point classes from earlier)# ecdh_demo()Always validate received public keys: check that the point is on the curve, not the point at infinity, and has the correct order. Invalid curve attacks exploit sloppy validation to leak private key bits.
Not all elliptic curves are equally suitable for cryptography. The choice of curve affects security, performance, and implementation complexity. Here are the most important curves in use today.
NIST Curves (P-256, P-384, P-521):
Standardized by NIST in 2000. P-256 (also called secp256r1 or prime256v1) is the most widely deployed:
Controversy: The seed values' origins aren't fully documented, leading to (unproven) speculation about potential backdoors.
| Curve | Bits | Security | Performance | Primary Use |
|---|---|---|---|---|
| P-256 (secp256r1) | 256 | ~128-bit | Good | TLS, WebAuthn, FIDO2 |
| P-384 (secp384r1) | 384 | ~192-bit | Moderate | High-security TLS, government |
| P-521 (secp521r1) | 521 | ~256-bit | Slower | Maximum security requirements |
| Curve25519 (X25519) | 255 | ~128-bit | Excellent | TLS 1.3, Signal, WireGuard, SSH |
| Curve448 (X448) | 448 | ~224-bit | Good | Post-quantum transition, high security |
| secp256k1 | 256 | ~128-bit | Good | Bitcoin, Ethereum (blockchain) |
Curve25519 / X25519:
Designed by Daniel J. Bernstein in 2005. Now considered the gold standard for key exchange:
Curve448 / X448:
Also by Bernstein (with Mike Hamburg), providing higher security:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
"""Comparing Popular Elliptic Curves This illustrates the different characteristics and use casesof commonly used elliptic curves.""" # Note: These parameters are the actual values from standards# For real implementations, use cryptographic libraries curves = { "P-256 (secp256r1)": { "field_size": 256, "security_bits": 128, "form": "Short Weierstrass: y² = x³ - 3x + b", "field_prime": "2²⁵⁶ - 2²²⁴ + 2¹⁹² + 2⁹⁶ - 1", "advantages": [ "Widespread hardware support (Intel, ARM)", "NIST/FIPS approved", "Required for many compliance standards", ], "disadvantages": [ "Complex implementation", "Timing attack risks if coded carelessly", "Seed origin concerns (probably unfounded)", ], "use_cases": [ "TLS (ECDHE-ECDSA, ECDHE-RSA)", "FIDO2/WebAuthn", "Code signing (Apple, Microsoft)", "Government/military systems", ], }, "X25519 (Curve25519)": { "field_size": 255, "security_bits": 128, "form": "Montgomery: y² = x³ + 486662x² + x", "field_prime": "2²⁵⁵ - 19", "advantages": [ "Extremely fast implementation", "Naturally constant-time", "Twist-secure (invalid points leak nothing)", "Simple, less error-prone to implement", "Fully specified—no unexplained seeds", ], "disadvantages": [ "Not FIPS approved (until recently)", "No native signing (use Ed25519 for that)", ], "use_cases": [ "TLS 1.3 (default in many implementations)", "Signal Protocol / WhatsApp", "WireGuard VPN", "SSH (modern versions)", "Tor", ], }, "secp256k1": { "field_size": 256, "security_bits": 128, "form": "Short Weierstrass: y² = x³ + 7", "field_prime": "2²⁵⁶ - 2³² - 977", "advantages": [ "Simple equation (a = 0)", "Efficient implementation possible", "Well-studied in blockchain context", ], "disadvantages": [ "Chosen for efficiency, not security margin", "Not NIST approved", "Narrower use case", ], "use_cases": [ "Bitcoin signatures", "Ethereum and most blockchains", "Cryptocurrency wallets", ], },} def print_curve_info(): """Display curve comparison information.""" print("ELLIPTIC CURVE COMPARISON") print("=" * 70) for name, info in curves.items(): print(f"{'─' * 70}") print(f"📈 {name}") print(f"{'─' * 70}") print(f" Field size: {info['field_size']} bits") print(f" Security: ~{info['security_bits']}-bit") print(f" Form: {info['form']}") print(f" Prime: {info['field_prime']}") print(" ✓ Advantages:") for adv in info['advantages']: print(f" • {adv}") print(" ✗ Disadvantages:") for dis in info['disadvantages']: print(f" • {dis}") print(" 📋 Use Cases:") for use in info['use_cases']: print(f" • {use}") print_curve_info() print("" + "=" * 70)print("RECOMMENDATION:")print(" • New applications: Use X25519 (key exchange) + Ed25519 (signatures)")print(" • Compliance required: Use P-256 or P-384")print(" • Blockchain: Use secp256k1 (for compatibility)")print(" • Maximum security: Use X448 or P-384")print("=" * 70)For new systems without specific compliance requirements, X25519 is the recommended choice. It's fast, secure, easy to implement correctly, and widely supported. TLS 1.3 implementations typically prefer X25519 over P-256.
While ECDH is mathematically sound, implementation vulnerabilities have led to real-world attacks. Understanding these risks is essential for secure deployment.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
"""ECDH Security Considerations and Mitigations This demonstrates common vulnerabilities and how to prevent them.""" class SecureECDH: """ ECDH with security best practices. This is an educational skeleton showing what checks are needed. """ def __init__(self, curve_params): self.curve = curve_params['curve'] self.G = curve_params['G'] self.n = curve_params['n'] # Order self.h = curve_params['h'] # Cofactor def validate_public_key(self, Q: 'Point') -> bool: """ Comprehensive public key validation. CRITICAL: All of these checks must pass before using a public key. Skipping any check can lead to attacks. """ # CHECK 1: Not the point at infinity if Q.is_infinity(): raise ValueError("Public key is point at infinity") # CHECK 2: Coordinates are in valid range if not (0 <= Q.x < self.curve.p and 0 <= Q.y < self.curve.p): raise ValueError("Public key coordinates out of range") # CHECK 3: Point is on the curve if not self.curve.is_on_curve(Q): raise ValueError("Public key is not on curve (invalid curve attack)") # CHECK 4: Point has correct order (for curves with cofactor > 1) # Ensures Q is in the correct subgroup if self.h > 1: # Check that n * Q = O (point at infinity) nQ = self.curve.multiply(self.n, Q) if not nQ.is_infinity(): raise ValueError("Public key has incorrect order") # CHECK 5: For X25519, also verify Q is not a low-order point # This is handled by curve design but good to check return True def constant_time_multiply(self, k: int, P: 'Point') -> 'Point': """ Constant-time scalar multiplication to prevent timing attacks. Montgomery ladder is naturally constant-time. For Weierstrass curves, requires careful implementation. """ # Use Montgomery ladder or equivalent # The key principle: every iteration takes the same time # regardless of the bits of k # Pseudocode for Montgomery ladder: # R0 = O, R1 = P # for i in range(num_bits, -1, -1): # if bit(k, i) == 0: # R1 = R0 + R1 # R0 = 2 * R0 # else: # R0 = R0 + R1 # R1 = 2 * R1 # return R0 # In practice, use conditional swaps to avoid branching pass def cofactor_multiplication(self, S: 'Point') -> 'Point': """ Multiply by cofactor to ensure shared secret is in main subgroup. For curves with h > 1, compute h * S before deriving key. This prevents small subgroup attacks even without full validation. """ return self.curve.multiply(self.h, S) def compute_shared_secret_secure(self, my_private: int, other_public: 'Point') -> bytes: """ Secure shared secret computation with all mitigations. """ # Step 1: Validate public key (prevents invalid curve attacks) self.validate_public_key(other_public) # Step 2: Constant-time scalar multiplication (prevents timing) S = self.constant_time_multiply(my_private, other_public) # Step 3: Cofactor multiplication (prevents small subgroup) S = self.cofactor_multiplication(S) # Step 4: Check result is not identity if S.is_infinity(): raise ValueError("Shared secret is point at infinity") # Step 5: Apply KDF to extract uniform key material import hashlib import hmac # Use HKDF for proper key derivation raw_secret = S.x.to_bytes((S.x.bit_length() + 7) // 8, 'big') # HKDF-Extract salt = b'\x00' * 32 # Or random salt prk = hmac.new(salt, raw_secret, hashlib.sha256).digest() # HKDF-Expand (simplified) info = b'ECDH shared secret' okm = hmac.new(prk, info + b'\x01', hashlib.sha256).digest() return okm print("ECDH SECURITY CHECKLIST")print("=" * 60)print("""✓ Validate public keys before use: - Not point at infinity - Coordinates in range [0, p-1] - Point lies on correct curve - Point has correct order (for cofactor > 1) ✓ Use constant-time operations: - Montgomery ladder for scalar multiplication - No branching on secret values - Regular memory access patterns ✓ Apply cofactor multiplication: - Compute h × S before key derivation - Required for curves with h > 1 ✓ Use proper key derivation: - Never use raw x-coordinate directly - Apply HKDF or similar KDF - Include context/info in derivation ✓ Handle errors safely: - Don't leak which check failed - Clear sensitive data on error - Use secure memory erasure""")X25519 was designed to eliminate many of these pitfalls: it's twist-secure (invalid inputs don't leak secrets), naturally constant-time (Montgomery ladder), has cofactor 8 built into the scalar clamp, and produces valid output for any 32-byte input. This makes correct implementation much easier.
ECDH is the foundation of key exchange in virtually every modern secure protocol. Let's examine how it's used in practice.
| Protocol | Curve Used | Key Exchange Details |
|---|---|---|
| TLS 1.3 | X25519, P-256, X448, P-384 | Ephemeral ECDHE; client offers preferences |
| Signal Protocol | X25519 | Identity keys + ephemeral keys; Double Ratchet |
| WireGuard | X25519 | Noise IK pattern; static + ephemeral keys |
| SSH (modern) | X25519, NIST curves | ECDH key exchange; curve25519-sha256 |
| X25519 | Signal Protocol with X25519 | |
| FIDO2/WebAuthn | P-256 | Registration and authentication ceremonies |
| IPsec/IKEv2 | NIST curves, X25519 | Ephemeral ECDH with pre-shared or cert auth |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
"""TLS 1.3 ECDHE Key Exchange Flow This illustrates how ECDH fits into the TLS 1.3 handshake.""" def tls13_ecdhe_overview(): """ Overview of TLS 1.3 handshake with ECDHE. """ print("TLS 1.3 ECDHE KEY EXCHANGE") print("=" * 65) print("""┌─────────┐ ┌─────────┐│ Client │ │ Server │└────┬────┘ └────┬────┘ │ │ │ ClientHello │ │ • Supported versions: TLS 1.3 │ │ • Cipher suites: TLS_AES_128_GCM_SHA256, ... │ │ • Supported groups: x25519, secp256r1, ... │ │ • Key shares: x25519_public_key, p256_public_key │ │ ──────────────────────────────────────────────────► │ │ │ │ [ Server receives, validates ] │ │ [ Chooses x25519, generates key ] │ │ [ Computes shared secret ] │ │ │ │ ServerHello │ │ • Selected: x25519 │ │ • Key share: x25519_public_key (server's) │ │ ◄────────────────────────────────────────────────── │ │ │ │ [ Client computes shared secret ] │ │ [ Both derive handshake keys ] │ │ │ │ EncryptedExtensions │ │ Certificate │ │ CertificateVerify │ │ Finished │ │ ◄────────────────────────────────────────────────── │ │ │ │ Finished │ │ ──────────────────────────────────────────────────► │ │ │ │ [ APPLICATION DATA, encrypted with session keys ] │ │ ◄────────────────────────────────────────────────► │ │ │ KEY DERIVATION (simplified): 1. ECDHE Shared Secret = x25519(client_private, server_public) = x25519(server_private, client_public) 2. Early Secret = HKDF-Extract(salt=0, input=0) 3. Handshake Secret = HKDF-Extract(Early Secret, ECDHE Shared Secret) 4. Client/Server Handshake Keys = HKDF-Expand(Handshake Secret, ...) 5. Master Secret = HKDF-Extract(Handshake Secret, 0) 6. Client/Server Application Keys = HKDF-Expand(Master Secret, ...)""") print("\nKEY POINTS:") print(" • Client sends key share speculatively (1-RTT optimization)") print(" • Server chooses curve + sends its key share") print(" • Both independently compute same shared secret") print(" • Shared secret feeds into HKDF-based key schedule") print(" • Result: authenticated encryption keys for application data") print("=" * 65) def wireguard_ecdh_overview(): """ Overview of WireGuard's ECDH usage. """ print("\nWIREGUARD X25519 USAGE") print("=" * 65) print("""WireGuard uses X25519 in the Noise IK handshake pattern: Configuration (pre-shared out-of-band): • Each peer has long-term X25519 key pair (static) • Each peer knows the other's static public key Handshake (per-connection): 1. Initiator generates ephemeral X25519 key pair 2. Initiator computes: • DH1 = X25519(ephemeral_private, responder_static_public) • DH2 = X25519(initiator_static_private, responder_static_public) 3. These feed into Noise's key derivation 4. Responder generates ephemeral X25519 key pair 5. Responder computes: • DH3 = X25519(responder_static_private, initiator_ephemeral_public) • DH4 = X25519(responder_ephemeral_private, initiator_ephemeral_public) 6. Full session key derived from DH1 || DH2 || DH3 || DH4 Security Properties: • Forward secrecy: ephemeral keys provide PFS • Identity hiding: static keys encrypted under ephemeral DH • Key confirmation: both sides verify handshake""") tls13_ecdhe_overview()wireguard_ecdh_overview()ECDH is never used alone in practice. It's combined with: authentication (certificates, pre-shared keys, or static keys), key derivation (HKDF or similar), and authenticated encryption (AES-GCM, ChaCha20-Poly1305). Understanding these combinations is essential for protocol design.
A fully capable quantum computer would break both traditional DH and ECDH using Shor's algorithm, which solves discrete logarithm problems in polynomial time on a quantum computer.
Current status:
Transition strategies:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
"""Hybrid Key Exchange: ECDH + Post-Quantum This conceptually demonstrates hybrid key exchange combiningclassical ECDH with post-quantum key encapsulation.""" def hybrid_key_exchange_concept(): """ Hybrid key exchange provides 'best of both worlds' security. The shared secret is derived from BOTH: 1. Classical ECDH (proven, efficient, resistant to classical computers) 2. Post-quantum KEM (believed resistant to quantum computers) If either algorithm is secure, the combined key is secure. """ print("HYBRID KEY EXCHANGE (ECDH + ML-KEM)") print("=" * 65) print(""" ┌─────────┐ ┌─────────┐ │ Client │ │ Server │ └────┬────┘ └────┬────┘ │ │ │ Generate X25519 ephemeral key pair: │ │ x25519_private, x25519_public │ │ │ │ Generate ML-KEM key pair: │ │ mlkem_private, mlkem_public │ │ │ │ ClientHello: │ │ key_share: x25519_public || mlkem_public │ │ ──────────────────────────────────────────────────► │ │ │ │ Generate X25519 ephemeral key pair │ │ Compute x25519_shared_secret │ │ │ │ Encapsulate to mlkem_public: │ │ mlkem_ciphertext, mlkem_secret │ │ │ │ ServerHello: │ │ key_share: x25519_public │ │ mlkem_ciphertext │ │ ◄────────────────────────────────────────────────── │ │ │ │ Compute x25519_shared_secret │ │ Decapsulate mlkem_ciphertext → mlkem_secret │ │ │ │ Combined secret = HKDF(x25519_secret || mlkem_secret) │ │ ▼ ▼ COMBINED SHARED SECRET: ======================== If X25519 is broken (quantum), ML-KEM protects the key. If ML-KEM is broken (cryptanalysis), X25519 protects the key. Attacker must break BOTH to recover the shared secret. """) print("\nKEY SIZES:") print(f" X25519 public key: 32 bytes") print(f" ML-KEM-768 public key: ~1,184 bytes") print(f" ML-KEM-768 ciphertext: ~1,088 bytes") print(f" Combined overhead: ~2,300 extra bytes per handshake") print("\nCURRENT STATUS:") print(" • Chrome/Chromium: Experimenting with X25519Kyber768") print(" • IETF: Drafts for hybrid key exchange in TLS") print(" • Signal: Planning post-quantum upgrade (PQXDH)") print(" • Cloudflare: Deployed hybrid TLS experimentally") print("\n" + "=" * 65) hybrid_key_exchange_concept()The "harvest now, decrypt later" threat means adversaries may be recording encrypted traffic today for decryption when quantum computers arrive. For data that needs long-term confidentiality (medical records, financial data, state secrets), deploying post-quantum or hybrid encryption now is prudent.
Elliptic Curve Diffie-Hellman represents the modern evolution of key exchange—maintaining the elegant structure of classical DH while providing superior efficiency and security characteristics. Let's consolidate the key concepts:
Module Complete:
You have now completed a comprehensive study of the Diffie-Hellman key exchange, from its historical origins through modern elliptic curve variants. You understand the key exchange problem, the DH protocol, its mathematical foundations, forward secrecy, and the ECDH evolution.
This knowledge is foundational for understanding how secure communication works on the modern internet—from HTTPS connections to encrypted messaging to VPN tunnels. Every secure connection you make relies on the principles explored in this module.
Congratulations! You now possess a comprehensive understanding of Diffie-Hellman key exchange at world-class depth. From the 1976 breakthrough that made secure internet communication possible, through mathematical foundations, forward secrecy, and modern ECDH implementations—you have mastered one of the most important cryptographic protocols ever invented.