Skip to content

Cryptography

Ken Tobias edited this page Jun 16, 2026 · 2 revisions

Cryptography

etr uses well-audited Rust crates for all cryptographic primitives (RustCrypto project). No custom crypto implementations.

Cipher suites

ID KEM AEAD KDF Notes
1 ML-KEM-1024 AES-256-GCM HKDF-SHA3-256 Default — post-quantum
2 ML-KEM-768 AES-256-GCM HKDF-SHA-256 Post-quantum, smaller keys
3 X25519 AES-256-GCM HKDF-SHA-256 Classical
4 X25519 ChaCha20-Poly1305 HKDF-SHA-256 Classical

The client advertises all supported suites in preference order (1 first). The server picks the strongest mutual option. With default builds on both sides, ML-KEM-1024 is always negotiated.

Why post-quantum?

Classical key exchange algorithms like X25519 are vulnerable to a future sufficiently powerful quantum computer via Shor's algorithm. While such computers don't exist today, traffic recorded now could be decrypted later ("harvest now, decrypt later"). ML-KEM (formerly Kyber, standardised as FIPS 203) is a post-quantum KEM that resists quantum attacks.

etr enables PQC by default because there is no meaningful performance cost on modern hardware — ML-KEM is fast. If you need a smaller binary or are targeting constrained environments, build with --no-default-features.

Key derivation

Passkey (bootstrap authentication)

Each session generates a random 32-character passkey sent to the server via the SSH-encrypted bootstrap channel. This provides pre-authentication: only the holder of the passkey can complete the handshake.

Hello key

Used to encrypt ServerHello before the session key is established:

hello_key = HKDF-SHA-256(ikm=passkey, salt=client_nonce, info="etr-hello-v1")

Session key

Derived after KEM exchange. Folds in both the passkey and the KEM shared secret:

session_key = KDF(
    ikm  = passkey ‖ kem_shared_secret,
    salt = client_nonce ‖ server_nonce,
    info = "etr-session-v1"
)

KDF is HKDF-SHA3-256 for suite 1, HKDF-SHA-256 for suites 2–4.

The session key provides forward secrecy: a new ephemeral KEM keypair is generated for every connection and every reconnect. Compromising the passkey alone is not enough to decrypt recorded traffic.

Nonce construction

Both AES-256-GCM and ChaCha20-Poly1305 use a 12-byte nonce derived from the packet sequence number:

nonce = [0x00, 0x00, 0x00, 0x00] ‖ packet_seq (8 bytes, big-endian)

Sequence numbers are strictly monotonically increasing, guaranteeing nonce uniqueness for the lifetime of a session key. A new session key is derived on every reconnect.

Crates used

Primitive Crate
AES-256-GCM aes-gcm
ChaCha20-Poly1305 chacha20poly1305
X25519 x25519-dalek
ML-KEM-768 / ML-KEM-1024 ml-kem
HKDF hkdf
SHA-256 sha2
SHA3-256 sha3

Clone this wiki locally