Asymmetric / Public Key
Asymmetric (public-key) crypto uses a key pair: encrypt with the public key, decrypt with the private; sign with private, verify with public. Modern choices: ECDSA / EdDSA for signatures, X25519 / RSA-OAEP for key exchange / encryption.
RSA, ECDSA, Ed25519, X25519
EXAMPLE
// 1) Generate an RSA key pair — Node
import crypto from 'node:crypto';
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 4096,
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
});
// Encrypt with PUBLIC — only PRIVATE can decrypt
const ct = crypto.publicEncrypt({
key: publicKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256',
}, Buffer.from('hello'));
const pt = crypto.privateDecrypt({
key: privateKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256',
}, ct);
// 2) Sign with PRIVATE — verify with PUBLIC (RSA-PSS preferred over PKCS1v1.5)
const sig = crypto.sign('sha256', Buffer.from(message), {
key: privateKey, padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
});
const ok = crypto.verify('sha256', Buffer.from(message), {
key: publicKey, padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
}, sig);
// 3) Elliptic-curve — ECDSA (P-256) is the de-facto default
const { publicKey: ecPub, privateKey: ecPriv } = crypto.generateKeyPairSync('ec', {
namedCurve: 'P-256',
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
});
const ecSig = crypto.sign('sha256', Buffer.from(message), ecPriv);
const ecOk = crypto.verify('sha256', Buffer.from(message), ecPub, ecSig);
// 4) Ed25519 — modern signature standard, fast, small keys
const { publicKey: edPub, privateKey: edPriv } = crypto.generateKeyPairSync('ed25519');
const edSig = crypto.sign(null, Buffer.from(message), edPriv); // hash baked in
const edOk = crypto.verify(null, Buffer.from(message), edPub, edSig);
// 5) X25519 — key agreement (Diffie-Hellman), pair with ChaCha20-Poly1305 for messaging
const alice = crypto.generateKeyPairSync('x25519');
const bob = crypto.generateKeyPairSync('x25519');
const sharedA = crypto.diffieHellman({ privateKey: alice.privateKey, publicKey: bob.publicKey });
const sharedB = crypto.diffieHellman({ privateKey: bob.privateKey, publicKey: alice.publicKey });
// sharedA == sharedB
// Pass through HKDF before using as a symmetric key
// 6) Hybrid encryption — encrypt symmetric key with RSA / ECIES,
// encrypt data with AES-GCM
// This is what TLS / signal / JWE essentially do.
// 7) Python — cryptography library
from cryptography.hazmat.primitives.asymmetric import rsa, padding, ed25519, x25519
from cryptography.hazmat.primitives import serialization, hashes
priv = rsa.generate_private_key(public_exponent=65537, key_size=4096)
pub = priv.public_key()
ciphertext = pub.encrypt(b'hello', padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(), label=None,
))
plaintext = priv.decrypt(ciphertext, padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(), label=None,
))
# Ed25519
sk = ed25519.Ed25519PrivateKey.generate()
vk = sk.public_key()
sig = sk.sign(b'message')
vk.verify(sig, b'message')
// 8) Choose by use case
// Signing — small, fast → Ed25519
// Signing — must use FIPS → ECDSA P-256
// Encrypting small data → RSA-OAEP (or hybrid)
// Encrypting large data → AES-GCM with key from key exchange
// Key exchange (modern) → X25519 + HKDF
// Long-term legacy interop → RSA-OAEP / RSA-PSS
// 9) Storage + transport
// PEM (text) for files
// PKCS#8 for private keys
// SPKI / X.509 SubjectPublicKeyInfo for public keys
// JWK / JWKS for HTTP API exposure
// 10) Common mistakes
// • Using public to encrypt big payloads — RSA can encrypt at most ~250 bytes (4096-bit key)
// • Using PKCS1v1.5 padding instead of OAEP / PSS — known weaknesses
// • Reusing the same key for signing AND encryption — separate the keys by use
// • Storing private keys in env vars — use KMS / HSM / OS keychain
// • Rolling your own elliptic curve code — use vetted libraries
// 11) JWT example — RS256 / ES256 / EdDSA
import jwt from 'jsonwebtoken';
const token = jwt.sign({ uid: 42 }, privateKey, { algorithm: 'EdDSA', expiresIn: '15m' });
const payload = jwt.verify(token, publicKey, { algorithms: ['EdDSA'] });
// 12) Post-quantum (PQ) — start watching
// • NIST has selected ML-KEM (Kyber), ML-DSA (Dilithium), SLH-DSA (SPHINCS+)
// • Hybrid schemes (X25519 + ML-KEM) are rolling out in TLS 1.3 (e.g. X25519MLKEM768)
// • Migrating long-term signed data (PDFs, contracts) is a 5-10 year project — plan now
Why it matters
Ed25519 + X25519 + ChaCha20-Poly1305 is the modern toolchain — fast, small keys, no padding-oracle pitfalls. Reach for RSA only when you need legacy interop or FIPS compliance.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// Two keys: public (share) and private (keep secret). // Use cases: digital signatures, key exchange, sealing messages to a recipient. // Modern picks: Ed25519 (sign), X25519 (key exchange), RSA-2048+ (legacy).Try it Yourself »
Discussion
Loading…