# Capture a TLS ClientHello with a one-minute listener

> A raw TCP listener that reads the first TLS record and hangs up captures a browser's ClientHello. Ours and branded Chrome's matched in every field.

Source: https://mosaicbrowser.com/blog/reading-a-client-hello-in-a-minute

## Reading a browser's ClientHello with a one-minute listener

A raw TCP listener that reads the first TLS record and hangs up captures a browser's ClientHello. Ours and branded Chrome's matched in every field.

![Reading a ClientHello with a one-minute listener, beside a screenshot: routing Mosaic launches through Proxyman, Charles or mitmproxy to read their traffic](https://mosaicbrowser.com/covers/reading-a-client-hello-in-a-minute.webp)

The quickest way to capture a TLS ClientHello is not a packet capture. Open a plain TCP socket, point the browser at it as if it were an HTTPS site, and read the first record it sends. That record is the ClientHello. The handshake never finishes, because the listener has no certificate, and it does not need to. We did this to compare our engine's handshake with Google Chrome's, a question we had put off as hours of rebuilds, and it took about a minute.

## Three ways to get the bytes

A packet capture works and is what most guides show. In Wireshark the display filter `tls.handshake.type == 1` keeps only ClientHellos; with tcpdump you filter on the record type `0x16` and the handshake type `0x01` at the start of the TCP payload. It needs capture permissions, and you are reading the browser's traffic to a real site, with everything else on the interface mixed in.

A TLS server that logs the hello works too. Go hands it to you in `GetConfigForClient`, and Node libraries can read it off an incoming socket before the handshake carries on. That route needs a certificate the browser will accept, or a flag telling it not to care.

The third route needs neither. The ClientHello is the first thing a client sends, before the server has said a word. So a listener that does nothing except read and hang up has everything the other two have.

Mosaic is a desktop app

Email yourself the link and open it on your computer.

[How it works: Machines that exist](https://mosaicbrowser.com/features/real-fingerprints)

## The listener

This is the whole of the capture half of our probe, `scripts/probe-clienthello.mjs`, cut down:

```
import { createServer } from 'node:net'

createServer((sock) => {
  sock.once('data', (buf) => {
    if (buf[0] !== 0x16) return sock.destroy()  // not a TLS handshake record
    if (buf[5] !== 0x01) return sock.destroy()  // not a ClientHello
    console.log(parseHello(buf))
    sock.destroy()
  })
}).listen(39444, '127.0.0.1')

```

Start a browser at `https://127.0.0.1:39444/`, with a fresh profile folder so nothing is cached, and the first connection arrives as the page starts to load. The browser shows a connection error. The terminal shows the hello.

Parsing it is a walk through a fixed layout. Skip the 5-byte record header and the 4-byte handshake header. Read the 2-byte legacy version, skip the 32 random bytes and the session id. Then come the cipher suites, the compression methods, and the extensions, each a 2-byte type and a 2-byte length. Four extensions carry most of what a fingerprint is built from: supported groups (`0x000a`), signature algorithms (`0x000d`), ALPN (`0x0010`) and supported versions (`0x002b`). Our parser reads those four bodies and records every extension type.

## Compare the set, not the order

Run it once against [Tesseract Chrome, our patched Chromium](https://mosaicbrowser.com/blog/introducing-mosaic), and once against branded Chrome, and two fields differ on the first try. Both are Chrome being Chrome.

Chrome permutes the order of its ClientHello extensions on every connection. It is deliberate, a guard against servers that come to depend on one order, and Chrome has done it since version 110. So comparing the ordered list compares two shuffles. The thing to compare is the set, sorted.

Chrome also sends GREASE values: reserved code points of the form `0x?a?a` (`0x0a0a`, `0x1a1a` and so on up to `0xfafa`), placed in several lists and picked at random per connection. Our first run showed the first signature-algorithm entry differing, `0xdada` against `0x2a2a`. That was GREASE too. We had missed it only because that line printed raw hex while the extension list already labelled GREASE. The parser now maps all sixteen values to one label everywhere before comparing.

With both rules applied, our engine against branded Chrome on the same machine:

```
legacy version    same      cipher suites       same
extension set     same      extension count     same
supported groups  same      supported versions  same
signature algs    same      ALPN                same

```

## What a minute bought

We had treated the handshake as a question that cost a rebuild per guess. It never did: a rebuild is only needed to change the binary, not to ask it something. The listener answered it outright, and the handshake joined the page's JavaScript surface on the list of things that are identical to Chrome and so are not where a detector finds the difference.

That fits the decision behind it. We do not offer a TLS setting per profile, because every real Chrome of one version sends this same shape, and [why our engine has no TLS fingerprint setting](https://mosaicbrowser.com/blog/no-tls-fingerprint-setting) sets out that argument with JA4 strings. The shuffling that makes the ordered list useless is also why JA3 hashes move on every connection, which [why Chrome's JA3 changes](https://mosaicbrowser.com/blog/why-chromes-ja3-changes) works through.

Two limits worth knowing if you use this yourself. Compare a browser only against a Chrome of the same major, because Chrome's TLS stack changes between majors. And a debugging proxy that decrypts HTTPS sends its own ClientHello to the site, not the browser's, so capture with nothing in between. After the handshake, the next layer down is the HTTP/2 preamble, which needs a server that completes TLS to read.

## Questions

### How do I capture a TLS ClientHello?

Three ways: a packet capture filtered to handshake type 1 (Wireshark's tls.handshake.type == 1), a TLS server that logs what it receives, or a plain TCP listener that reads the first record a client sends. The last needs no certificate and no root.

### Do I need a certificate to read a ClientHello?

No. The ClientHello is the first thing the client sends, before the server has said anything. A listener can read it and close the connection; the handshake fails, which does not matter.

### Why does Chrome's ClientHello look different on every connection?

Chrome shuffles the order of its TLS extensions per connection, on purpose, and fills a few slots with random GREASE values. Compare the set of extensions with GREASE removed, not the ordered list.

### Does Mosaic change the TLS ClientHello?

No. Tesseract Chrome sends the handshake Chromium sends. Measured against branded Google Chrome on the same machine, every field we compared was the same.

## Related

[Why our engine has no TLS fingerprint setting](https://mosaicbrowser.com/blog/no-tls-fingerprint-setting): You can't change Chrome's TLS fingerprint from a setting, and we chose not to patch it: our engine's JA4 matched Google's own build byte for byte.

[Why Chrome's JA3 hash changes on every connection](https://mosaicbrowser.com/blog/why-chromes-ja3-changes): Chrome's JA3 changes because Chrome shuffles its TLS extension order on every connection, and JA3 hashes that order. JA4 sorts first and holds still.

[Introducing Mosaic, an anti-detect browser that lives on your machine](https://mosaicbrowser.com/blog/introducing-mosaic): Mosaic runs its own Chromium, draws each profile from a real machine and matches it to the proxy exit. Profiles stay on disk. One plan, no per-profile fees.

[Machines that exist: fingerprints drawn from real hardware](https://mosaicbrowser.com/features/real-fingerprints): Mosaic draws each profile's GPU, screen, CPU, memory and fonts from one real device, so the parts of a fingerprint agree with each other and with the host.









## Mosaic, on your own computer

The browser this blog is about: its own Chromium, fingerprints from real machines and a proxy exit per profile. Free holds 3 profiles. Pro is $49 a month for any number.

Mosaic is a desktop app

Email yourself the link and open it on your computer.
