Glossary

X25519

X25519 is an elliptic curve Diffie-Hellman function on Curve25519 that combines a 32-byte private key with a 32-byte public value to produce a 32-byte shared secret. It is defined in RFC 7748 and targets roughly 128-bit security. You meet it in TLS 1.3 key exchange, where RFC 8446 says implementations should support it. A 32-byte private key, a 32-byte public key and a 32-byte shared secret are the sizes people search for.

How it works

X25519 works on the x-coordinate only, which RFC 7748 calls the u-coordinate. The field is the integers modulo the prime 2^255 - 19, and the curve has a cofactor of 8. Every value is a 32-byte string in little-endian order, and the standard base point is the byte 9 followed by 31 zero bytes.

Each side picks 32 random bytes as a private key. Before use, the key is "clamped" by decodeScalar25519:

  • Clear the 3 lowest bits of the first byte (AND with 248), so the scalar is a multiple of 8.
  • Clear the top bit of the last byte (AND with 127).
  • Set the second-highest bit of the last byte (OR with 64), which fixes the scalar at 2^254 plus a multiple of 8.

The public key is X25519(private, 9). Alice sends hers to Bob, Bob sends his to Alice, and each computes X25519(own private, other public). Both arrive at the same 32 bytes. RFC 7748 says the receiver masks the top bit of the final byte of an incoming u-coordinate, and must accept non-canonical values by reducing them modulo the prime.

The shared secret is not a key yet. RFC 7748 tells you to feed it, together with both public keys, into a key derivation function such as HKDF. The example below runs the RFC 7748 test vector with Node.js.

const c = require('crypto');
const pk = h => c.createPrivateKey({ key: Buffer.concat([Buffer.from('302e020100300506032b656e04220420', 'hex'), Buffer.from(h, 'hex')]), format: 'der', type: 'pkcs8' });
const pub = k => c.createPublicKey(k).export({ format: 'der', type: 'spki' }).subarray(-32).toString('hex');
const a = pk('77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a');
const b = pk('5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb');
console.log('alice pub', pub(a));
console.log('bob pub  ', pub(b));
const pubk = h => c.createPublicKey({ key: Buffer.concat([Buffer.from('302a300506032b656e032100', 'hex'), Buffer.from(h, 'hex')]), format: 'der', type: 'spki' });
console.log('shared', c.diffieHellman({ privateKey: a, publicKey: pubk(pub(b)) }).toString('hex'));
alice pub 8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a
bob pub   de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f
shared 4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742

All three values match the RFC 7748 section 6.1 vectors. Swapping the roles gives the same shared secret.

How many bytes is an X25519 key?

An X25519 private key, public key and shared secret are each 32 bytes, which is 64 hex characters. The curve behind it is Curve25519, and the larger sibling X448 uses 56-byte values instead. Clamping changes the private key in memory, but you still store and transmit all 32 bytes.

Is X25519 the same as Ed25519?

No. X25519 agrees on a shared secret, and Ed25519 signs messages. Both use the same underlying curve family, but they are separate algorithms with different key uses, so do not sign with an X25519 key or exchange with a signing key.

Common pitfalls

  • Using the raw output as a key: the 32-byte result is a curve coordinate, not uniformly random bytes. Run it through HKDF with both public keys in the context.
  • Ignoring an all-zero result: inputs of small order make X25519 return 32 zero bytes. RFC 7748 allows the receiver to check for this and abort. Node.js rejects such an input, so a public key of 32 zero bytes failed with "failed during derivation".
  • Reusing a static key without authentication: X25519 gives no proof of who holds the other key, so an active attacker can sit in the middle. Authenticate the exchange with certificates or signatures.
  • Assuming the bytes are big-endian: values are little-endian. Reversing them yields a different, wrong shared secret.
  • Rolling your own implementation: the constant-time field arithmetic is easy to get wrong. Use the library function.

Related terms

  • TLS — the protocol where X25519 is used as a key exchange group.
  • HKDF — the key derivation function that turns the shared secret into usable keys.
  • ChaCha20 — a cipher often keyed from an X25519 secret.
  • AES-GCM — an authenticated cipher also keyed from the derived secret.
  • SSH key — another place where public key cryptography meets everyday developer work.

See also