Internet Protocols · TLS

Encrypted Client Hello and the Limits of Hiding a Hostname

How the outer and inner ClientHello split works, what the ECHConfig commits the server to, and which parts of a connection remain visible after ECH.

The ClientHello is the first thing a TLS client sends and the last thing an observer can read in full. It carries the server name, the cipher suite list, the key shares, and the extension set, all in the clear, because the server must read them before any shared secret exists. Encrypted Client Hello changes the shape of that message so that the parts identifying the destination travel inside an encrypted envelope, while a small outer message still satisfies everything that must be readable before encryption can begin.

The split, and why it needs two messages

The inner ClientHello is the real one: it carries the actual server_name, the actual key shares, and the actual extension list. It is encrypted with a key that the client obtained from the server’s published configuration, before the connection began. The outer ClientHello carries a public name that the client is willing to reveal, a set of extensions sufficient for the server to find the right configuration, and the encrypted inner message.

The server reads the outer message, identifies the configuration from the outer name, decrypts the inner one, and replies with a ServerHello that is bound to the inner message. From the client’s perspective the handshake then proceeds as normal, with the inner ClientHello’s contents as the effective offer. An observer sees the outer name and the outer extension set and learns nothing about the inner one.

The reason two messages are necessary rather than one is the bootstrapping problem. To encrypt the inner message the client needs the server’s public key, which it must have obtained somehow: from a DNS record, from a previously cached configuration, or from a separate fetch. That retrieval is itself visible unless it is protected, which is why ECH is usually discussed together with encrypted DNS. A client that fetches the configuration over plaintext DNS has revealed the name it later hides.

The configuration object is the trust anchor

The ECHConfig is a structure the server publishes, containing an identifier for the configuration, an identifier for the key encapsulation mechanism, the public key, a list of symmetric cipher suites acceptable for the inner message, a maximum name length, a public name, and an extension block. The client selects a configuration, generates an ephemeral key pair for the hybrid public key encryption scheme, and encapsulates a shared secret to the configuration’s public key. The resulting ciphertext, plus the client’s ephemeral public key, goes into the outer ClientHello.

The binding between the configuration and the server’s identity is the weak point. A client that accepts a configuration from an unauthenticated channel can be given one whose private key is held by an attacker, who can then decrypt the inner ClientHello and learn the name that was supposed to be hidden. The configuration must therefore arrive over an authenticated channel, which in practice means DNS protected by a mechanism that authenticates the response. Fetching the configuration over the same TLS connection it protects is circular for a first connection, so the workable arrangement is a configuration obtained separately and cached, with the certificate check on the resulting handshake confirming after the fact that the server was the intended one.

The ECHConfigList allows several configurations so that a server can rotate keys without a flag day. A client that holds an old configuration will fail to decrypt with the current one, and the failure must be signalled so that the client can retry with a freshly fetched configuration. The retry reveals the name on the second attempt, which means a key rotation weakens the guarantee for exactly the clients that had cached the previous key.

What the server must do to keep the connection valid

When the server decrypts the inner ClientHello, it must treat that message, not the outer one, as the start of the transcript. The transcript hash that all subsequent secrets depend on includes the inner message. If the server hashed the outer message instead, the client and server would derive different secrets and the handshake would fail at the first encrypted record.

The retry path adds a wrinkle. A HelloRetryRequest must be bound to the inner message as well, and the client must be able to tell that the retry is genuine rather than an attempt to force it to reveal the inner message. A retry that the client cannot verify is treated as a failure, and the client may then fall back to a plain handshake with the name in the clear. That fallback is the mechanism’s central weakness: an attacker who can cause retries can cause the name to be revealed, so ECH does not resist an active attacker who is willing to break connections.

The public name in the outer message must be one the server is prepared to serve a certificate for. If the outer name is a name the server does not hold a certificate for, the handshake cannot complete for a client that does not support ECH and falls back. The configuration therefore typically names a public name that the server’s infrastructure serves broadly, and the inner name selects the actual virtual host.

What remains visible

ECH hides the inner server_name and the extension set that travels with it. It does not hide the destination IP address, which is the most direct signal of which service is being reached, and it does not hide the timing or the volume of the connection. It does not hide the outer name, which is chosen by the server’s configuration and is therefore shared across many inner names. It also does not make the server’s identity secret in any general sense: the certificate is encrypted on the wire under the handshake traffic keys, so a passive observer does not read its subject, but the same certificate is public data that any client can obtain by connecting, and it is published in the transparency logs that issuance feeds.

The last point is what separates ECH from a claim that a connection reveals nothing. The mechanism is aimed at a specific observer with a specific view: a passive one that sees the ClientHello and nothing after it, and would otherwise learn the exact hostname being visited. An observer who can open its own connection to the same address, or who can read the logs, learns the certificate regardless. The privacy gain is real but bounded by what the server publishes elsewhere.

The size of the outer message also leaks. The inner ClientHello can be padded, and the padding extension lets a client inflate it, but the outer message’s total size still varies with the number and size of the key shares the client offers. A client offering a post-quantum hybrid share produces a much larger message than one offering only X25519, and the difference is visible. Padding the outer message to a fixed size reduces the leak at a bandwidth cost, and the padding policy is a client decision that the server cannot enforce.

Where the mechanism sits relative to the rest of the handshake

ECH is not a replacement for the handshake; it is a wrapper around the first message. Everything downstream, including the key schedule and the record layer, is unchanged. The inner ClientHello is the transcript’s starting point, so the handshake message flow is the same sequence with a different first element. The configuration’s public key is used once per connection to encrypt one message, and it plays no part in the traffic keys.

The deployment shape follows from that. A server needs to publish configurations, serve a certificate for the public name, and decrypt the inner message before it can decide which virtual host is being addressed. A reverse proxy that terminates TLS must therefore know the configuration’s private key, which puts that key in the same trust position as the server’s certificate key and raises the same questions about rotation and compromise. A proxy that does not support ECH sees only the outer name and routes on it, which is why the public name is usually one that the proxy already serves.

The mechanism also interacts with resumption. A resumed connection can carry ECH, and the inner ClientHello is the one that carries the PSK binders, as the resumption mechanism describes, so the binder computation covers the hidden name. A server that accepts the PSK has learned the inner name and the client’s identity for that session, which is unavoidable: the server must know whom it is serving. The guarantee is against the observer, not against the server. Nor does ECH hide the size of the messages that follow; the lengths of the records after the handshake remain visible, which is the concern record length analysis addresses.

References

  1. RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3 — IETF
  2. RFC 9180: Hybrid Public Key Encryption — IETF
  3. RFC 8744: Issues and Requirements for Server Name Identification (SNI) Encryption in TLS — IETF