X25519
Header: #include <cryptopp/xed25519.h> | Namespace: CryptoPP
Since: Crypto++ 8.0
X25519 is a modern Diffie-Hellman key exchange algorithm built on Curve25519. It allows two parties to establish a shared secret over an insecure channel. It’s fast, simple, and resistant to many cryptographic attacks.
Quick Example
#include <cryptopp/xed25519.h>
#include <cryptopp/osrng.h>
#include <cryptopp/hex.h>
#include <iostream>
int main() {
using namespace CryptoPP;
AutoSeededRandomPool rng;
// Alice generates her key pair
x25519 alice;
SecByteBlock alicePrivate(x25519::SECRET_KEYLENGTH);
SecByteBlock alicePublic(x25519::PUBLIC_KEYLENGTH);
alice.GenerateKeyPair(rng, alicePrivate, alicePublic);
// Bob generates his key pair
x25519 bob;
SecByteBlock bobPrivate(x25519::SECRET_KEYLENGTH);
SecByteBlock bobPublic(x25519::PUBLIC_KEYLENGTH);
bob.GenerateKeyPair(rng, bobPrivate, bobPublic);
// Both compute the same shared secret
SecByteBlock aliceShared(x25519::SHARED_KEYLENGTH);
SecByteBlock bobShared(x25519::SHARED_KEYLENGTH);
if (!alice.Agree(aliceShared, alicePrivate, bobPublic))
throw std::runtime_error("Alice's key agreement failed");
if (!bob.Agree(bobShared, bobPrivate, alicePublic))
throw std::runtime_error("Bob's key agreement failed");
// Verify both parties have the same shared secret
if (std::memcmp(aliceShared, bobShared, x25519::SHARED_KEYLENGTH) == 0) {
std::cout << "Key exchange successful!" << std::endl;
}
return 0;
}Usage Guidelines
Do:
- Use X25519 for modern key exchange (recommended over traditional DH)
- Generate keys using
AutoSeededRandomPool - Derive encryption keys from shared secret using HKDF
- Verify that
Agree()returnstrue(detects small-order attacks) - Use ephemeral keys for forward secrecy
Avoid:
- Reusing private keys across multiple exchanges (weakens forward secrecy)
- Using shared secret directly as encryption key (use HKDF instead)
- Ignoring
Agree()return value—small-order or invalid keys cause it to returnfalse
Class: x25519
Elliptic curve Diffie-Hellman key exchange using Curve25519.
Constants
static const int SECRET_KEYLENGTH = 32; // Private key size (256 bits)
static const int PUBLIC_KEYLENGTH = 32; // Public key size (256 bits)
static const int SHARED_KEYLENGTH = 32; // Shared secret size (256 bits)
Constructors
Default Constructor
x25519();Create an empty X25519 object (for loading existing keys).
Example:
x25519 kex;
// Load keys later...
Constructor with RNG
x25519(RandomNumberGenerator& rng);Create X25519 object and generate a new key pair.
Parameters:
rng- Random number generator (useAutoSeededRandomPool)
Example:
AutoSeededRandomPool rng;
x25519 kex(rng); // Keys generated automatically
Constructor with Private Key
x25519(const byte x[SECRET_KEYLENGTH]);Create X25519 object from existing private key. Public key is computed.
Parameters:
x- 32-byte private key
Example:
byte privateKey[32];
// ... load private key ...
x25519 kex(privateKey); // Public key computed from private
Constructor with Both Keys
x25519(const byte y[PUBLIC_KEYLENGTH], const byte x[SECRET_KEYLENGTH]);Create X25519 object from existing key pair.
Parameters:
y- 32-byte public keyx- 32-byte private key
Note: Public key is not validated in constructor.
Example:
byte publicKey[32], privateKey[32];
// ... load keys ...
x25519 kex(publicKey, privateKey);Methods
GenerateKeyPair()
void GenerateKeyPair(RandomNumberGenerator& rng,
byte* privateKey, byte* publicKey) const;Generate a new key pair.
Parameters:
rng- Random number generatorprivateKey- Output buffer (32 bytes)publicKey- Output buffer (32 bytes)
Example:
AutoSeededRandomPool rng;
byte privateKey[x25519::SECRET_KEYLENGTH];
byte publicKey[x25519::PUBLIC_KEYLENGTH];
x25519 kex;
kex.GenerateKeyPair(rng, privateKey, publicKey);GeneratePrivateKey()
void GeneratePrivateKey(RandomNumberGenerator& rng,
byte* privateKey) const;Generate a new private key only.
Parameters:
rng- Random number generatorprivateKey- Output buffer (32 bytes)
Example:
AutoSeededRandomPool rng;
SecByteBlock privateKey(x25519::SECRET_KEYLENGTH);
x25519 kex;
kex.GeneratePrivateKey(rng, privateKey);GeneratePublicKey()
void GeneratePublicKey(RandomNumberGenerator& rng,
const byte* privateKey,
byte* publicKey) const;Derive public key from private key.
Parameters:
rng- Random number generator (unused but required by interface)privateKey- 32-byte private keypublicKey- Output buffer (32 bytes)
Example:
AutoSeededRandomPool rng;
byte privateKey[32], publicKey[32];
x25519 kex;
kex.GeneratePrivateKey(rng, privateKey);
kex.GeneratePublicKey(rng, privateKey, publicKey);Agree()
bool Agree(byte* agreedValue,
const byte* privateKey,
const byte* otherPublicKey,
bool validateOtherPublicKey = true) const;Perform key exchange to compute shared secret.
Parameters:
agreedValue- Output shared secret (32 bytes)privateKey- Your private key (32 bytes)otherPublicKey- Other party’s public key (32 bytes)validateOtherPublicKey- Validate public key (default: true)
Returns: true if successful, false if validation fails (e.g., small-order or invalid public key)
Important: Always check return value. A false return indicates the peer’s public key failed validation.
Example:
SecByteBlock sharedSecret(x25519::SHARED_KEYLENGTH);
if (!kex.Agree(sharedSecret, myPrivate, theirPublic)) {
std::cerr << "Key exchange failed - invalid public key!" << std::endl;
return 1;
}
// Use sharedSecret with HKDF to derive encryption keys
ClampKey()
void ClampKey(byte x[SECRET_KEYLENGTH]) const;Clamp a private key to meet X25519 requirements.
Parameters:
x- Private key to clamp (modified in-place)
Details: Sets specific bits: x[0] &= 248, x[31] &= 127, x[31] |= 64
Note: Keys generated by the library are already clamped.
Example:
byte privateKey[32];
// ... load raw key ...
x25519 kex;
kex.ClampKey(privateKey); // Ensure key is properly formatted
IsClamped()
bool IsClamped(const byte x[SECRET_KEYLENGTH]) const;Check if a private key is properly clamped.
Parameters:
x- Private key to check
Returns: true if key is clamped, false otherwise
IsSmallOrder()
bool IsSmallOrder(const byte y[PUBLIC_KEYLENGTH]) const;Check if a public key has small order (security vulnerability).
Parameters:
y- Public key to check
Returns: true if key has small order (reject it!), false if safe
Note: Agree() validates automatically if validateOtherPublicKey = true.
Validate()
bool Validate(RandomNumberGenerator& rng, unsigned int level) const;Validate the key pair.
Parameters:
rng- Random number generatorlevel- Validation level (0-3, higher is more thorough)
Returns: true if keys are valid
Complete Example: Secure Messaging
#include <cryptopp/xed25519.h>
#include <cryptopp/osrng.h>
#include <cryptopp/hkdf.h>
#include <cryptopp/sha.h>
#include <cryptopp/aes.h>
#include <cryptopp/gcm.h>
#include <cryptopp/secblock.h>
#include <iostream>
using namespace CryptoPP;
// Derive encryption and MAC keys from shared secret
void deriveKeys(const SecByteBlock& sharedSecret,
SecByteBlock& encKey,
SecByteBlock& macKey) {
HKDF<SHA256> hkdf;
// Info string to bind keys to application
byte info[] = "SecureMessaging v1.0";
// Derive 32-byte encryption key
hkdf.DeriveKey(encKey, encKey.size(),
sharedSecret, sharedSecret.size(),
nullptr, 0, // No salt
info, sizeof(info) - 1);
// Derive 32-byte MAC key (different info)
byte macInfo[] = "SecureMessaging v1.0 - MAC";
hkdf.DeriveKey(macKey, macKey.size(),
sharedSecret, sharedSecret.size(),
nullptr, 0,
macInfo, sizeof(macInfo) - 1);
}
// Encrypt message using keys derived from X25519
std::string encryptMessage(const std::string& plaintext,
const SecByteBlock& encKey) {
AutoSeededRandomPool rng;
byte iv[12];
rng.GenerateBlock(iv, sizeof(iv));
std::string ciphertext;
GCM<AES>::Encryption enc;
enc.SetKeyWithIV(encKey, encKey.size(), iv, sizeof(iv));
StringSource(plaintext, true,
new AuthenticatedEncryptionFilter(enc,
new StringSink(ciphertext)
)
);
// Prepend IV to ciphertext
std::string result;
result.append((const char*)iv, sizeof(iv));
result.append(ciphertext);
return result;
}
int main() {
AutoSeededRandomPool rng;
// Alice generates ephemeral key pair
x25519 alice(rng);
SecByteBlock alicePrivate(x25519::SECRET_KEYLENGTH);
SecByteBlock alicePublic(x25519::PUBLIC_KEYLENGTH);
alice.GenerateKeyPair(rng, alicePrivate, alicePublic);
// Bob generates ephemeral key pair
x25519 bob(rng);
SecByteBlock bobPrivate(x25519::SECRET_KEYLENGTH);
SecByteBlock bobPublic(x25519::PUBLIC_KEYLENGTH);
bob.GenerateKeyPair(rng, bobPrivate, bobPublic);
// Both perform key exchange
SecByteBlock aliceShared(x25519::SHARED_KEYLENGTH);
SecByteBlock bobShared(x25519::SHARED_KEYLENGTH);
if (!alice.Agree(aliceShared, alicePrivate, bobPublic)) {
std::cerr << "Alice: Key exchange failed" << std::endl;
return 1;
}
if (!bob.Agree(bobShared, bobPrivate, alicePublic)) {
std::cerr << "Bob: Key exchange failed" << std::endl;
return 1;
}
// Derive encryption keys from shared secret
SecByteBlock encKey(32), macKey(32);
deriveKeys(aliceShared, encKey, macKey);
// Alice encrypts a message
std::string plaintext = "Hello Bob, this is Alice!";
std::string ciphertext = encryptMessage(plaintext, encKey);
std::cout << "Message encrypted successfully" << std::endl;
std::cout << "Ciphertext size: " << ciphertext.size() << " bytes" << std::endl;
return 0;
}Complete Example: Forward Secrecy
#include <cryptopp/xed25519.h>
#include <cryptopp/osrng.h>
#include <iostream>
using namespace CryptoPP;
// Simulate ephemeral key exchange (new keys each session)
void forwardSecretSession(int sessionNumber) {
AutoSeededRandomPool rng;
// Generate NEW ephemeral keys for THIS session
x25519 alice(rng);
x25519 bob(rng);
SecByteBlock alicePrivate(x25519::SECRET_KEYLENGTH);
SecByteBlock alicePublic(x25519::PUBLIC_KEYLENGTH);
alice.GenerateKeyPair(rng, alicePrivate, alicePublic);
SecByteBlock bobPrivate(x25519::SECRET_KEYLENGTH);
SecByteBlock bobPublic(x25519::PUBLIC_KEYLENGTH);
bob.GenerateKeyPair(rng, bobPrivate, bobPublic);
// Perform key exchange
SecByteBlock sharedSecret(x25519::SHARED_KEYLENGTH);
alice.Agree(sharedSecret, alicePrivate, bobPublic);
std::cout << "Session " << sessionNumber << ": Key exchange complete"
<< std::endl;
// Use sharedSecret for encryption...
// Keys are destroyed at end of scope (forward secrecy)
}
int main() {
// Each session uses NEW ephemeral keys
forwardSecretSession(1);
forwardSecretSession(2);
forwardSecretSession(3);
// Past sessions cannot be decrypted even if current keys compromised
std::cout << "Forward secrecy maintained across sessions" << std::endl;
return 0;
}Security
Security Properties
- Security level: 128 bits (equivalent to AES-128)
- Key size: 256 bits (32 bytes)
- Shared secret size: 256 bits (32 bytes)
- Algorithm: Elliptic Curve Diffie-Hellman on Curve25519
- Standard: RFC 7748
Security Best Practices
Always Validate Public Keys:
// CORRECT - validates by default if (!kex.Agree(shared, myPrivate, theirPublic)) { // Reject - small-order attack detected } // WRONG - skips validation (vulnerable!) kex.Agree(shared, myPrivate, theirPublic, false);Use HKDF to Derive Keys:
// WRONG - using shared secret directly AES::Encryption enc; enc.SetKey(sharedSecret, sharedSecret.size()); // CORRECT - derive keys with HKDF HKDF<SHA256> hkdf; SecByteBlock key(32); hkdf.DeriveKey(key, key.size(), sharedSecret, ...);Forward Secrecy - Use Ephemeral Keys:
// WRONG - reusing static keys x25519 static_kex(rng); // Generated once // ... use for multiple sessions ... // CORRECT - new keys per session void newSession() { x25519 ephemeral_kex(rng); // Fresh keys // ... perform key exchange ... // Keys destroyed at end of scope }Secure Key Storage:
SecByteBlock privateKey(x25519::SECRET_KEYLENGTH); // Auto-zeroing // NOT: byte privateKey[32]; // Leaves key in memory
Test Vectors (RFC 7748)
// Test Vector 1
byte alicePrivate[] = {
0x77, 0x07, 0x6d, 0x0a, 0x73, 0x18, 0xa5, 0x7d,
0x3c, 0x16, 0xc1, 0x72, 0x51, 0xb2, 0x66, 0x45,
0xdf, 0x4c, 0x2f, 0x87, 0xeb, 0xc0, 0x99, 0x2a,
0xb1, 0x77, 0xfb, 0xa5, 0x1d, 0xb9, 0x2c, 0x2a
};
byte bobPublic[] = {
0xde, 0x9e, 0xdb, 0x7d, 0x7b, 0x7d, 0xc1, 0xb4,
0xd3, 0x5b, 0x61, 0xc2, 0xec, 0xe4, 0x35, 0x37,
0x3f, 0x83, 0x43, 0xc8, 0x5b, 0x78, 0x67, 0x4d,
0xad, 0xfc, 0x7e, 0x14, 0x6f, 0x88, 0x2b, 0x4f
};
// Expected shared secret
byte expectedShared[] = {
0x4a, 0x5d, 0x9d, 0x5b, 0xa4, 0xce, 0x2d, 0xe1,
0x72, 0x8e, 0x3b, 0xf4, 0x80, 0x35, 0x0f, 0x25,
0xe0, 0x7e, 0x21, 0xc9, 0x47, 0xd1, 0x9e, 0x33,
0x76, 0xf0, 0x9b, 0x3c, 0x1e, 0x16, 0x17, 0x42
};
// Verify
x25519 kex;
byte sharedSecret[32];
bool ok = kex.Agree(sharedSecret, alicePrivate, bobPublic);
assert(ok); // Should succeed with valid test vectors
assert(std::memcmp(sharedSecret, expectedShared, 32) == 0);Performance
Benchmarks (Approximate)
| Operation | Time (µs) | Notes |
|---|---|---|
| Key generation | 40-80 | Generate key pair |
| Key exchange | 40-80 | Compute shared secret |
| Public key derivation | 40-80 | Derive public from private |
Platform: Modern x86-64 CPU, software implementation
Comparison with RSA
| Feature | X25519 | RSA-2048 |
|---|---|---|
| Key gen speed | ⚡⚡⚡⚡⚡ Very fast | ⚡ Slow |
| Key exchange speed | ⚡⚡⚡⚡⚡ Very fast | ⚡ Slow |
| Public key size | 32 bytes | 256+ bytes |
| Private key size | 32 bytes | 1024+ bytes |
| Security | 128-bit | 112-bit |
| Forward secrecy | ✅ Natural | ⚠️ Requires ephemeral |
When to Use X25519
✅ Use X25519 for:
- Modern Key Exchange - Preferred over traditional DH/RSA key exchange
- Forward Secrecy - Ephemeral keys for each session
- VPN/TLS - Secure channel establishment (WireGuard, TLS 1.3)
- Secure Messaging - Signal, WhatsApp, etc.
- Any Diffie-Hellman Need - Faster and simpler than traditional DH
❌ Don’t use X25519 for:
- Digital Signatures - Use Ed25519 instead
- Encryption - X25519 is key exchange, not encryption
- Authentication - Use Ed25519 signatures instead
X25519 vs Alternatives
| Algorithm | Speed | Key Size | Security | Use Case |
|---|---|---|---|---|
| X25519 | ⚡⚡⚡⚡⚡ | 32 bytes | 128-bit | Key exchange ⭐ |
| Traditional DH | ⚡⚡ | 256+ bytes | 112-bit | Legacy |
| RSA key exchange | ⚡ | 256+ bytes | 112-bit | Legacy |
| ECDH P-256 | ⚡⚡⚡⚡ | 64 bytes | 128-bit | NIST required |
Thread Safety
Thread-safe for read operations after construction. Key generation and agreement are safe to call from multiple threads if using separate objects.
// Safe - separate objects per thread
void threadFunc() {
AutoSeededRandomPool rng;
x25519 kex(rng); // Thread-local
// ... use kex ...
}Exceptions
InvalidArgument- Invalid key lengthInvalidDataFormat- Malformed key data
Algorithm Details
- Curve: Curve25519 (Montgomery curve)
- Field: Prime field with p = 2^255 - 19
- Coordinate system: Montgomery coordinates
- Cofactor: 8 (handled automatically)
- Standard: RFC 7748
- Implementation: Based on curve25519-donna by Andrew Moon
See Also
- Public-Key Cryptography Guide - Conceptual overview
- Ed25519 - Digital signatures
- HKDF - Key derivation for shared secrets
- Public-Key Cryptography - Overview of asymmetric algorithms
- Security Concepts - Understanding public-key crypto