← System Design Simulator

TLS Handshake

By Rahul Kumar · Senior Software Engineer · Updated · Category: Networking + Web Security

TLS 1.2 (2 RTT) vs 1.3 (1 RTT) vs 1.3 resume (0 RTT).

This interactive explanation is built for system design interview prep: step through TLS Handshake, watch the internal state change, and connect the concept to real distributed-system trade-offs.

Overview

TLS is the cryptographic layer that turns a plain TCP socket into a confidential, authenticated, integrity-protected channel. Modern web traffic runs over TLS 1.3, which is simpler, faster, and more secure than the 1.2 it replaces. A 1.3 handshake takes one RTT for a new connection and zero RTTs for a resumed session, versus two RTTs for 1.2. The handshake proves the server's identity via a certificate chain rooted at a CA the client trusts, agrees on a shared symmetric key via ephemeral Diffie-Hellman, and negotiates a cipher suite that defines the AEAD algorithm (AES-GCM or ChaCha20-Poly1305) for bulk encryption. Once the handshake completes, every record is encrypted and authenticated. Forward secrecy — guaranteed by ephemeral DH — means that even if the server's long-term key is stolen later, past sessions cannot be decrypted. Getting TLS right is the foundation of every secure web API; getting it wrong means leaking credentials in plaintext.

TLS Handshake — Interactive Simulator

Runs fully client-side in your browser; no sign-up. Or open full screen →

Launch the interactive TLS Handshake widget — step through the algorithm or protocol and observe the internal state updating in real time.

How it works

A TLS 1.3 handshake starts with the client sending ClientHello containing its supported cipher suites, a list of supported groups (for key exchange), a key_share with its ephemeral DH public key, and the server_name extension (SNI) so a server hosting multiple domains can pick the right cert. The server replies with ServerHello picking the cipher and group, its own key_share, its certificate chain, a CertificateVerify signed with the cert's private key, and Finished. The client verifies the certificate chain up to a trusted root, verifies the signature, derives the symmetric keys from the combined DH secret using HKDF, and sends its Finished. Application data starts flowing on the next packet — 1-RTT total. A resumed session (0-RTT) can piggyback application data on the ClientHello using a pre-shared key ticket from a previous session; this is fast but replayable, so only idempotent requests should use it. Cipher suites in 1.3 are fixed to a small AEAD set: TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256. Certificate validation checks the hostname against the cert's Subject Alternative Name, the chain back to a trusted CA, expiry, and revocation (via OCSP stapling to avoid a separate round-trip). Java exposes TLS through SSLContext and SSLEngine (or the higher-level HttpClient), which negotiate transparently when the system truststore has the right roots.

Implementation

SSLContext for TLS 1.3 with cipher selection
public class TlsContextFactory {
    public static SSLContext tls13() throws Exception {
        SSLContext ctx = SSLContext.getInstance("TLSv1.3");

        // Default truststore (cacerts) + no client cert for server auth only.
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(
            TrustManagerFactory.getDefaultAlgorithm());
        tmf.init((KeyStore) null);
        ctx.init(null, tmf.getTrustManagers(), new SecureRandom());

        return ctx;
    }

    /** Enable only TLS 1.3 and AEAD ciphers on a socket. */
    public static void harden(SSLSocket s) {
        s.setEnabledProtocols(new String[]{"TLSv1.3"});
        s.setEnabledCipherSuites(new String[]{
            "TLS_AES_128_GCM_SHA256",
            "TLS_AES_256_GCM_SHA384",
            "TLS_CHACHA20_POLY1305_SHA256"
        });
        SSLParameters p = s.getSSLParameters();
        p.setEndpointIdentificationAlgorithm("HTTPS"); // enforce hostname verification
        s.setSSLParameters(p);
    }
}
HttpClient over TLS 1.3
public class TlsHttpClient {
    public static String getHttps(String url) throws Exception {
        HttpClient client = HttpClient.newBuilder()
            .sslContext(TlsContextFactory.tls13())
            .connectTimeout(Duration.ofSeconds(5))
            .build();

        HttpRequest req = HttpRequest.newBuilder(URI.create(url))
            .header("User-Agent", "tls-demo/1.0")
            .GET()
            .build();

        HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());
        System.out.println("negotiated protocol: " + resp.version());
        return resp.body();
    }

    public static void main(String[] args) throws Exception {
        String body = getHttps("https://example.com/");
        System.out.println(body.substring(0, Math.min(200, body.length())));
    }
}

Complexity

Key design decisions & trade-offs

Common pitfalls

Interview follow-ups

Recommended reading

Related