🔄 X25519 Elliptic Curve Key Exchange
📋 Quick Navigation
📖 Overview
X25519 is an elliptic curve Diffie-Hellman (ECDH) key exchange protocol using Curve25519. It enables two parties to establish a shared secret over an insecure channel without prior communication, designed for high security and performance with built-in protection against implementation pitfalls.
✨ Key Features
High Security
128-bit security level against classical attacks
Fast Performance
Optimized for modern processors and architectures
Simple Implementation
Reduced complexity compared to other curves
Side-Channel Resistance
Designed to resist timing and cache attacks
Wide Adoption
Used in TLS 1.3, Signal, SSH, WireGuard
RFC Standardized
RFC 7748 specification and IETF approved
🎯 Common Use Cases
🌐 Network Protocols
- TLS 1.3 key exchange in HTTPS connections
- SSH secure shell key exchange
- WireGuard and modern VPN implementations
- QUIC transport protocol security
📱 Messaging & Communication
- Signal Protocol end-to-end encryption
- WhatsApp and Telegram secure messaging
- Real-time communication protocols
- IoT device secure pairing and communication
🔧 Algorithm Details
📊 X25519 Parameters
🔐 Security Properties
Discrete Log Security
Based on elliptic curve discrete logarithm problem
Twist Security
Secure against invalid curve attacks
Small Subgroup Protection
Protected against small subgroup attacks
Quantum Vulnerability
Vulnerable to Shor's algorithm (like all ECDH)
🧮 Algorithm Structure
X25519 performs scalar multiplication on Curve25519:
shared_secret = private_key × public_key (mod p)
The Montgomery ladder ensures constant-time execution and security.
💻 Implementation
X25519 key exchange is straightforward to implement and use. Both parties generate key pairs and exchange public keys to derive a shared secret.
Basic Key Exchange
🔒 Security Considerations
⚠️ Critical Security Guidelines
- Always validate public keys before use
- Use proper key derivation functions (KDF) on shared secrets
- Never reuse private keys across sessions
- Implement proper forward secrecy protocols
- Consider post-quantum alternatives for long-term security
⚡ Performance Analysis
Operation Benchmarks
| Operation | X25519 | ECDH P-256 | ECDH P-521 |
|---|---|---|---|
| Key Generation | 25 μs | 200 μs | 800 μs |
| Public Key Computation | 25 μs | 200 μs | 800 μs |
| Shared Secret | 25 μs | 200 μs | 800 μs |
| Key Size | 32 bytes | 32 bytes | 66 bytes |
🎯 Use Cases and Examples
TLS 1.3 Key Exchange
Standard key exchange mechanism in modern TLS connections for secure HTTPS.
Signal Protocol
End-to-end encryption in messaging apps with perfect forward secrecy.
WireGuard VPN
High-performance VPN protocol using X25519 for key establishment.
📚 References
- RFC 7748 - Elliptic Curves for Security
- Curve25519 - Original curve specification
- Security Analysis - X25519 security properties
- RFC 8446 - TLS 1.3 specification
🔗 Related Algorithms
def derive_keys(shared_secret: bytes, context: bytes) -> dict: “"”Derive encryption and authentication keys””” hkdf = HKDF(hash_function=SHA256())
# Derive 64 bytes: 32 for encryption, 32 for authentication
key_material = hkdf.derive(
ikm=shared_secret,
info=context,
length=64
)
return {
'encryption_key': key_material[:32],
'authentication_key': key_material[32:64]
}
Derive session keys
context = b”secure_chat_v1.0” keys = derive_keys(alice_shared, context)
### Advanced Usage
```python
# Ephemeral key exchange for forward secrecy
class EphemeralX25519:
def __init__(self):
self.private_key = None
self.public_key = None
self.generate_keypair()
def generate_keypair(self):
"""Generate new ephemeral keypair"""
self.private_key = X25519.generate_private_key()
self.public_key = X25519.compute_public_key(self.private_key)
def exchange(self, peer_public_key: bytes) -> bytes:
"""Perform key exchange and rotate keys"""
shared_secret = X25519.compute_shared_secret(
self.private_key,
peer_public_key
)
# Immediately generate new keypair for forward secrecy
self.generate_keypair()
return shared_secret
def get_public_key(self) -> bytes:
"""Get current public key"""
return self.public_key
# Secure channel establishment
class X25519SecureChannel:
def __init__(self, identity_private_key: bytes):
self.identity_private = identity_private_key
self.identity_public = X25519.compute_public_key(identity_private_key)
self.ephemeral = EphemeralX25519()
def initiate_handshake(self, peer_identity_public: bytes) -> dict:
"""Initiate secure channel handshake"""
# Generate ephemeral keypair for this session
ephemeral_private = X25519.generate_private_key()
ephemeral_public = X25519.compute_public_key(ephemeral_private)
# Compute shared secrets
# DH1: Our identity key with their identity key
dh1 = X25519.compute_shared_secret(
self.identity_private,
peer_identity_public
)
# DH2: Our ephemeral key with their identity key
dh2 = X25519.compute_shared_secret(
ephemeral_private,
peer_identity_public
)
# Combine shared secrets
combined_secret = dh1 + dh2
# Derive session keys
session_keys = self.derive_session_keys(combined_secret)
return {
'ephemeral_public': ephemeral_public,
'session_keys': session_keys,
'ephemeral_private': ephemeral_private # Keep for next phase
}
def complete_handshake(self, peer_ephemeral_public: bytes,
our_ephemeral_private: bytes,
peer_identity_public: bytes) -> dict:
"""Complete handshake with peer's ephemeral key"""
# Additional DH computations
# DH3: Our identity key with their ephemeral key
dh3 = X25519.compute_shared_secret(
self.identity_private,
peer_ephemeral_public
)
# DH4: Our ephemeral key with their ephemeral key
dh4 = X25519.compute_shared_secret(
our_ephemeral_private,
peer_ephemeral_public
)
# Combine all shared secrets for maximum security
combined_secret = dh3 + dh4
# Derive final session keys
final_keys = self.derive_session_keys(combined_secret)
return {'session_keys': final_keys}
def derive_session_keys(self, shared_secret: bytes) -> dict:
"""Derive session keys from shared secret"""
from metamui_crypto import HKDF, SHA256
hkdf = HKDF(hash_function=SHA256())
# Derive multiple keys
key_material = hkdf.derive(
ikm=shared_secret,
info=b"X25519_SESSION_KEYS_V1",
length=96 # 3 × 32-byte keys
)
return {
'send_key': key_material[0:32],
'receive_key': key_material[32:64],
'next_header_key': key_material[64:96]
}
# Double Ratchet implementation (simplified Signal Protocol)
class X25519DoubleRatchet:
def __init__(self, shared_secret: bytes, sending: bool = True):
self.root_key = shared_secret
self.sending = sending
if sending:
# Generate initial sending chain
self.dh_keypair = self.generate_dh_keypair()
self.chain_key = None
else:
# Initialize for receiving
self.dh_keypair = None
self.chain_key = None
def generate_dh_keypair(self) -> dict:
"""Generate DH keypair for ratchet"""
private_key = X25519.generate_private_key()
public_key = X25519.compute_public_key(private_key)
return {
'private': private_key,
'public': public_key
}
def ratchet_encrypt(self, plaintext: bytes,
peer_public_key: bytes) -> dict:
"""Encrypt message with ratcheting"""
# DH ratchet step
if self.chain_key is None:
shared_secret = X25519.compute_shared_secret(
self.dh_keypair['private'],
peer_public_key
)
# Root key ratchet
self.root_key, self.chain_key = self.kdf_rk(
self.root_key,
shared_secret
)
# Chain key ratchet
self.chain_key, message_key = self.kdf_ck(self.chain_key)
# Encrypt message
from metamui_crypto import ChaCha20Poly1305
cipher = ChaCha20Poly1305(message_key)
nonce = os.urandom(12)
ciphertext = cipher.encrypt(plaintext, nonce)
return {
'dh_public': self.dh_keypair['public'],
'nonce': nonce,
'ciphertext': ciphertext
}
def ratchet_decrypt(self, encrypted_message: dict) -> bytes:
"""Decrypt message with ratcheting"""
peer_dh_public = encrypted_message['dh_public']
# Check if we need to perform DH ratchet
if self.dh_keypair is None or peer_dh_public != self.last_peer_public:
# Generate new DH keypair
self.dh_keypair = self.generate_dh_keypair()
# Compute shared secret
shared_secret = X25519.compute_shared_secret(
self.dh_keypair['private'],
peer_dh_public
)
# Root key ratchet
self.root_key, self.chain_key = self.kdf_rk(
self.root_key,
shared_secret
)
self.last_peer_public = peer_dh_public
# Chain key ratchet
self.chain_key, message_key = self.kdf_ck(self.chain_key)
# Decrypt message
from metamui_crypto import ChaCha20Poly1305
cipher = ChaCha20Poly1305(message_key)
plaintext = cipher.decrypt(
encrypted_message['ciphertext'],
encrypted_message['nonce']
)
return plaintext
def kdf_rk(self, root_key: bytes, dh_output: bytes) -> tuple:
"""Root key derivation function"""
from metamui_crypto import HKDF, SHA256
hkdf = HKDF(hash_function=SHA256())
key_material = hkdf.derive(
ikm=dh_output,
salt=root_key,
info=b"SIGNAL_ROOT_KEY",
length=64
)
return key_material[:32], key_material[32:64]
def kdf_ck(self, chain_key: bytes) -> tuple:
"""Chain key derivation function"""
from metamui_crypto import HMAC, SHA256
# Message key
hmac1 = HMAC(chain_key, SHA256())
message_key = hmac1.compute(b"\x01")
# Next chain key
hmac2 = HMAC(chain_key, SHA256())
next_chain_key = hmac2.compute(b"\x02")
return next_chain_key, message_key
# VPN key exchange
class X25519VPN:
def __init__(self, server_mode: bool = False):
self.server_mode = server_mode
self.sessions = {}
def create_session(self, peer_id: str) -> dict:
"""Create VPN session with peer"""
# Generate session keypair
session_private = X25519.generate_private_key()
session_public = X25519.compute_public_key(session_private)
session = {
'peer_id': peer_id,
'private_key': session_private,
'public_key': session_public,
'shared_secret': None,
'tunnel_keys': None,
'created_at': time.time()
}
self.sessions[peer_id] = session
return session
def establish_tunnel(self, peer_id: str,
peer_public_key: bytes) -> dict:
"""Establish VPN tunnel"""
if peer_id not in self.sessions:
raise ValueError("No session for peer")
session = self.sessions[peer_id]
# Compute shared secret
shared_secret = X25519.compute_shared_secret(
session['private_key'],
peer_public_key
)
session['shared_secret'] = shared_secret
# Derive tunnel keys
tunnel_keys = self.derive_tunnel_keys(shared_secret, peer_id)
session['tunnel_keys'] = tunnel_keys
return tunnel_keys
def derive_tunnel_keys(self, shared_secret: bytes, peer_id: str) -> dict:
"""Derive VPN tunnel keys"""
from metamui_crypto import HKDF, SHA256
hkdf = HKDF(hash_function=SHA256())
# Context includes peer ID and role
role = "server" if self.server_mode else "client"
context = f"vpn_tunnel_{role}_{peer_id}".encode()
# Derive key material
key_material = hkdf.derive(
ikm=shared_secret,
info=context,
length=128 # 4 × 32-byte keys
)
return {
'encryption_key': key_material[0:32],
'authentication_key': key_material[32:64],
'iv_seed': key_material[64:96],
'rekey_material': key_material[96:128]
}
Implementation Details
# X25519 core operations (simplified)
class X25519Core:
def __init__(self):
# Curve25519 parameters
self.p = 2**255 - 19 # Field prime
self.a24 = 121665 # (A-2)/4 where A=486662
def clamp_private_key(self, private_key: bytes) -> bytes:
"""Clamp private key according to X25519 spec"""
key = bytearray(private_key)
key[0] &= 248 # Clear bottom 3 bits
key[31] &= 127 # Clear top bit
key[31] |= 64 # Set second-highest bit
return bytes(key)
def scalar_mult(self, scalar: bytes, point: bytes) -> bytes:
"""Montgomery ladder scalar multiplication"""
# Clamp scalar
k = self.clamp_private_key(scalar)
# Convert point to field element
u = int.from_bytes(point, 'little') % self.p
# Montgomery ladder
x1, x2, x3 = u, 1, u
z2, z3 = 0, 1
for i in range(254, -1, -1):
bit = (int.from_bytes(k, 'little') >> i) & 1
# Conditional swap
if bit:
x2, x3 = x3, x2
z2, z3 = z3, z2
# Montgomery ladder step
A = (x2 + z2) % self.p
AA = (A * A) % self.p
B = (x2 - z2) % self.p
BB = (B * B) % self.p
E = (AA - BB) % self.p
C = (x3 + z3) % self.p
D = (x3 - z3) % self.p
DA = (D * A) % self.p
CB = (C * B) % self.p
x3 = pow(DA + CB, 2, self.p)
z3 = (x1 * pow(DA - CB, 2, self.p)) % self.p
x2 = (AA * BB) % self.p
z2 = (E * (AA + self.a24 * E)) % self.p
# Conditional swap
if bit:
x2, x3 = x3, x2
z2, z3 = z3, z2
# Compute final result
result = (x2 * pow(z2, self.p - 2, self.p)) % self.p
return result.to_bytes(32, 'little')
def generate_private_key(self) -> bytes:
"""Generate random private key"""
return os.urandom(32)
def compute_public_key(self, private_key: bytes) -> bytes:
"""Compute public key from private key"""
basepoint = bytes([9] + [0] * 31) # Generator point
return self.scalar_mult(private_key, basepoint)
def compute_shared_secret(self, private_key: bytes,
public_key: bytes) -> bytes:
"""Compute shared secret"""
return self.scalar_mult(private_key, public_key)
Security Considerations
Best Practices
- Key Generation
- Use cryptographically secure random number generator
- Properly clamp private keys according to specification
- Generate fresh keys for each session (ephemeral keys)
- Validate public keys before use
- Implementation Security
- Use constant-time implementations
- Protect against side-channel attacks
- Clear private keys from memory after use
- Validate all inputs
- Protocol Design
- Use ephemeral keys for forward secrecy
- Combine with authentication (signatures)
- Implement proper key derivation
- Include context in key derivation
- Key Management
- Rotate keys regularly
- Use separate keys for different purposes
- Implement secure key storage
- Plan for key compromise scenarios
Common Pitfalls
# DON'T: Reuse static keys without authentication
static_private = X25519.generate_private_key()
# WRONG: Reusing static keys enables impersonation attacks
shared1 = X25519.compute_shared_secret(static_private, peer1_public)
shared2 = X25519.compute_shared_secret(static_private, peer2_public)
# DO: Use ephemeral keys or combine with authentication
ephemeral_private = X25519.generate_private_key() # Fresh for each exchange
shared = X25519.compute_shared_secret(ephemeral_private, peer_public)
# DON'T: Use shared secret directly as encryption key
shared_secret = X25519.compute_shared_secret(private_key, public_key)
# WRONG: Direct use without key derivation
encryption_key = shared_secret
# DO: Derive keys from shared secret
from metamui_crypto import HKDF, SHA256
hkdf = HKDF(hash_function=SHA256())
encryption_key = hkdf.derive(shared_secret, b"encryption", 32)
# DON'T: Skip public key validation
def unsafe_exchange(private_key, public_key):
# WRONG: No validation of public key
return X25519.compute_shared_secret(private_key, public_key)
# DO: Validate public keys
def safe_exchange(private_key, public_key):
# Validate public key is not zero or other invalid values
if public_key == b'\x00' * 32:
raise ValueError("Invalid public key")
return X25519.compute_shared_secret(private_key, public_key)
# DON'T: Ignore small subgroup attacks
# Some implementations may be vulnerable to small subgroup attacks
# Always use a well-tested, secure implementation
# DO: Use proper key clamping and validation
def secure_keygen():
private_key = os.urandom(32)
# Proper clamping is handled by the library
return private_key
Performance Characteristics
Benchmarks
| Operation | Time (μs) | Cycles (3GHz) | Throughput |
|---|---|---|---|
| Key Generation | 15 | 45,000 | 66,667 ops/sec |
| Public Key Computation | 45 | 135,000 | 22,222 ops/sec |
| Shared Secret Computation | 45 | 135,000 | 22,222 ops/sec |
Performance Comparison
| Algorithm | Key Exchange Time | Key Size | Security Level |
|---|---|---|---|
| X25519 | 45 μs | 32 bytes | 128-bit |
| ECDH P-256 | 180 μs | 32 bytes | 128-bit |
| ECDH P-384 | 420 μs | 48 bytes | 192-bit |
| DH-2048 | 8 ms | 256 bytes | 112-bit |
| ML-KEM-768 | 600 μs | 1184 bytes | 192-bit (PQ) |
Optimization Strategies
# Batch key generation
def generate_keypairs_batch(count: int) -> list:
"""Generate multiple keypairs efficiently"""
keypairs = []
for _ in range(count):
private_key = X25519.generate_private_key()
public_key = X25519.compute_public_key(private_key)
keypairs.append({
'private': private_key,
'public': public_key
})
return keypairs
# Pre-computed public keys
class PrecomputedX25519:
def __init__(self, private_key: bytes):
self.private_key = private_key
self.public_key = X25519.compute_public_key(private_key)
def exchange(self, peer_public_key: bytes) -> bytes:
"""Fast exchange with pre-computed public key"""
return X25519.compute_shared_secret(self.private_key, peer_public_key)
# Parallel key exchanges
from concurrent.futures import ThreadPoolExecutor
def parallel_key_exchanges(private_keys: list, public_keys: list) -> list:
"""Perform multiple key exchanges in parallel"""
def single_exchange(args):
private_key, public_key = args
return X25519.compute_shared_secret(private_key, public_key)
with ThreadPoolExecutor() as executor:
exchanges = list(zip(private_keys, public_keys))
results = list(executor.map(single_exchange, exchanges))
return results
Use Cases
1. TLS 1.3 Key Exchange
class TLS13KeyExchange:
def __init__(self):
self.client_private = None
self.client_public = None
self.server_public = None
self.shared_secret = None
def client_hello(self) -> bytes:
"""Generate client key share for TLS 1.3"""
self.client_private = X25519.generate_private_key()
self.client_public = X25519.compute_public_key(self.client_private)
return self.client_public
def server_hello(self, client_public: bytes) -> tuple:
"""Process client key share and generate server response"""
# Generate server keypair
server_private = X25519.generate_private_key()
server_public = X25519.compute_public_key(server_private)
# Compute shared secret
shared_secret = X25519.compute_shared_secret(
server_private,
client_public
)
return server_public, shared_secret
def client_finish(self, server_public: bytes) -> bytes:
"""Complete client-side key exchange"""
self.server_public = server_public
self.shared_secret = X25519.compute_shared_secret(
self.client_private,
server_public
)
return self.shared_secret
def derive_traffic_keys(self, handshake_hash: bytes) -> dict:
"""Derive TLS 1.3 traffic keys"""
from metamui_crypto import HKDF, SHA256
hkdf = HKDF(hash_function=SHA256())
# Early secret (empty for X25519-only)
early_secret = hkdf.extract(b"", b"")
# Handshake secret
handshake_secret = hkdf.extract(self.shared_secret, early_secret)
# Derive traffic secrets
client_secret = hkdf.expand(
handshake_secret,
b"tls13 c hs traffic" + handshake_hash,
32
)
server_secret = hkdf.expand(
handshake_secret,
b"tls13 s hs traffic" + handshake_hash,
32
)
return {
'client_handshake_secret': client_secret,
'server_handshake_secret': server_secret
}
2. Signal Protocol Implementation
class SignalProtocol:
def __init__(self, identity_private: bytes):
self.identity_private = identity_private
self.identity_public = X25519.compute_public_key(identity_private)
self.prekeys = {}
self.sessions = {}
def generate_prekeys(self, count: int) -> dict:
"""Generate one-time prekeys"""
prekeys = {}
for i in range(count):
private_key = X25519.generate_private_key()
public_key = X25519.compute_public_key(private_key)
prekeys[i] = {
'private': private_key,
'public': public_key
}
self.prekeys.update(prekeys)
return {i: prekey['public'] for i, prekey in prekeys.items()}
def initiate_session(self, peer_identity: bytes,
peer_signed_prekey: bytes,
peer_onetime_prekey: bytes = None) -> dict:
"""Initiate Signal session (X3DH)"""
# Generate ephemeral key
ephemeral_private = X25519.generate_private_key()
ephemeral_public = X25519.compute_public_key(ephemeral_private)
# Compute shared secrets
dh1 = X25519.compute_shared_secret(
self.identity_private,
peer_signed_prekey
)
dh2 = X25519.compute_shared_secret(
ephemeral_private,
peer_identity
)
dh3 = X25519.compute_shared_secret(
ephemeral_private,
peer_signed_prekey
)
# Optional fourth DH if one-time prekey available
shared_secrets = [dh1, dh2, dh3]
if peer_onetime_prekey:
dh4 = X25519.compute_shared_secret(
ephemeral_private,
peer_onetime_prekey
)
shared_secrets.append(dh4)
# Derive master secret
master_secret = self.kdf(b''.join(shared_secrets))
# Initialize double ratchet
session = X25519DoubleRatchet(master_secret, sending=True)
return {
'session': session,
'ephemeral_public': ephemeral_public,
'master_secret': master_secret
}
def kdf(self, input_material: bytes) -> bytes:
"""Key derivation function for Signal"""
from metamui_crypto import HKDF, SHA256
hkdf = HKDF(hash_function=SHA256())
return hkdf.derive(
ikm=input_material,
info=b"Signal_KDF_v1",
length=32
)
3. WireGuard VPN
class WireGuardKeyExchange:
def __init__(self):
self.static_private = X25519.generate_private_key()
self.static_public = X25519.compute_public_key(self.static_private)
self.peers = {}
def add_peer(self, peer_public: bytes, preshared_key: bytes = None):
"""Add peer configuration"""
self.peers[peer_public] = {
'public_key': peer_public,
'preshared_key': preshared_key,
'sessions': {}
}
def initiate_handshake(self, peer_public: bytes) -> dict:
"""Initiate WireGuard handshake"""
if peer_public not in self.peers:
raise ValueError("Unknown peer")
peer = self.peers[peer_public]
# Generate ephemeral keypair
ephemeral_private = X25519.generate_private_key()
ephemeral_public = X25519.compute_public_key(ephemeral_private)
# Compute shared secrets
# DH(static_private, peer_static_public)
dh1 = X25519.compute_shared_secret(self.static_private, peer_public)
# DH(ephemeral_private, peer_static_public)
dh2 = X25519.compute_shared_secret(ephemeral_private, peer_public)
# Derive session keys
session_keys = self.derive_session_keys(
dh1, dh2, peer.get('preshared_key')
)
# Store session
session_id = os.urandom(16)
peer['sessions'][session_id] = {
'keys': session_keys,
'ephemeral_private': ephemeral_private,
'created_at': time.time()
}
return {
'session_id': session_id,
'ephemeral_public': ephemeral_public,
'session_keys': session_keys
}
def respond_handshake(self, initiator_ephemeral: bytes,
peer_public: bytes) -> dict:
"""Respond to WireGuard handshake"""
if peer_public not in self.peers:
raise ValueError("Unknown peer")
peer = self.peers[peer_public]
# Generate our ephemeral keypair
ephemeral_private = X25519.generate_private_key()
ephemeral_public = X25519.compute_public_key(ephemeral_private)
# Compute shared secrets
# DH(static_private, peer_static_public)
dh1 = X25519.compute_shared_secret(self.static_private, peer_public)
# DH(static_private, initiator_ephemeral_public)
dh2 = X25519.compute_shared_secret(
self.static_private,
initiator_ephemeral
)
# DH(ephemeral_private, initiator_ephemeral_public)
dh3 = X25519.compute_shared_secret(
ephemeral_private,
initiator_ephemeral
)
# Derive session keys
session_keys = self.derive_session_keys(
dh1, dh2, peer.get('preshared_key'), dh3
)
return {
'ephemeral_public': ephemeral_public,
'session_keys': session_keys
}
def derive_session_keys(self, *shared_secrets) -> dict:
"""Derive WireGuard session keys"""
from metamui_crypto import HKDF, BLAKE2s
# Combine all shared secrets
combined = b''.join(filter(None, shared_secrets))
# Use BLAKE2s for key derivation (WireGuard uses BLAKE2s)
hkdf = HKDF(hash_function=BLAKE2s())
key_material = hkdf.derive(
ikm=combined,
info=b"WireGuard session keys",
length=96 # 3 × 32-byte keys
)
return {
'sending_key': key_material[0:32],
'receiving_key': key_material[32:64],
'confirmation_key': key_material[64:96]
}
Comparison with Other Key Exchange
X25519 vs Other ECDH Curves
| Curve | Security | Performance | Key Size | Standardization |
|---|---|---|---|---|
| X25519 | 128-bit | Very Fast | 32 bytes | RFC 7748 |
| P-256 | 128-bit | Fast | 32 bytes | NIST/FIPS |
| P-384 | 192-bit | Moderate | 48 bytes | NIST/FIPS |
| P-521 | 256-bit | Slow | 66 bytes | NIST/FIPS |
X25519 vs Post-Quantum
| Algorithm | Security (Classical) | Security (Quantum) | Key Size | Performance |
|---|---|---|---|---|
| X25519 | 128-bit | ~0 bits | 32 bytes | Very Fast |
| ML-KEM-768 | 256-bit | 192-bit | 1184 bytes | Fast |
| Hybrid | 128-bit | 192-bit | 1216 bytes | Moderate |
When to Use X25519
# Use X25519 for current applications
if not_post_quantum_required:
key_exchange = X25519
# Use X25519 in hybrid mode for transition
if preparing_for_post_quantum:
classical_secret = X25519.compute_shared_secret(x25519_private, x25519_public)
pq_secret = MLKEM768.decapsulate(mlkem_ciphertext, mlkem_private)
combined_secret = classical_secret + pq_secret
# Use X25519 for performance-critical applications
if performance_critical:
key_exchange = X25519 # Fastest classical option
Migration Guide
From ECDH P-256 to X25519
# Before: ECDH P-256
# from cryptography.hazmat.primitives.asymmetric import ec
# private_key = ec.generate_private_key(ec.SECP256R1())
# public_key = private_key.public_key()
# After: X25519
from metamui_crypto import X25519
private_key = X25519.generate_private_key()
public_key = X25519.compute_public_key(private_key)
# Benefits:
# - Faster performance
# - Simpler implementation
# - Better side-channel resistance
# - Smaller attack surface
From DH to X25519
# Before: Traditional DH (slow, large keys)
# from cryptography.hazmat.primitives.asymmetric import dh
# parameters = dh.generate_parameters(generator=2, key_size=2048)
# After: X25519 (fast, small keys)
from metamui_crypto import X25519
# Much simpler and faster
private_key = X25519.generate_private_key()
public_key = X25519.compute_public_key(private_key)
Test Vectors
RFC 7748 Test Vectors
# Test Vector 1
alice_private = bytes.fromhex("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a")
alice_public = bytes.fromhex("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a")
bob_private = bytes.fromhex("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb")
bob_public = bytes.fromhex("de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f")
# Verify public key computation
computed_alice_public = X25519.compute_public_key(alice_private)
assert computed_alice_public == alice_public
computed_bob_public = X25519.compute_public_key(bob_private)
assert computed_bob_public == bob_public
# Verify shared secret computation
alice_shared = X25519.compute_shared_secret(alice_private, bob_public)
bob_shared = X25519.compute_shared_secret(bob_private, alice_public)
assert alice_shared == bob_shared
expected_shared = bytes.fromhex("4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742")
assert alice_shared == expected_shared
Wycheproof Test Vectors
# Test edge cases and invalid inputs
def test_invalid_public_keys():
private_key = X25519.generate_private_key()
# Test all-zero public key
zero_public = b'\x00' * 32
try:
X25519.compute_shared_secret(private_key, zero_public)
# Should handle gracefully or reject
except ValueError:
pass # Expected for some implementations
# Test all-ones public key
ones_public = b'\xff' * 32
try:
X25519.compute_shared_secret(private_key, ones_public)
except ValueError:
pass
Related Algorithms
- Ed25519 - EdDSA signatures using the same curve
- ML-KEM-768 - Post-quantum key encapsulation
- ChaCha20-Poly1305 - Often used with X25519 for AEAD
- HKDF - Key derivation from shared secrets
References
- RFC 7748 - Elliptic Curves for Security
- Curve25519 Paper - Original Curve25519 specification
- SafeCurves - Security analysis of elliptic curves
- Signal Protocol - Uses X25519 for key exchange
- WireGuard Protocol - Modern VPN using X25519