# Chrome TLS extension permutation: what moves and what stays

> Chrome TLS extension permutation reorders the ClientHello on every connection. Only the extension order moves, so a client that repeats its order is not Chrome.

Source: https://mosaicbrowser.com/blog/chrome-tls-extension-permutation

## Chrome shuffles its ClientHello, so compare sets

Chrome TLS extension permutation reorders the ClientHello on every connection. Only the extension order moves, so a client that repeats its order is not Chrome.

![12!: Twelve extensions have 479,001,600 orders, beside a screenshot: routing Mosaic launches through Proxyman, Charles or mitmproxy to read their traffic](https://mosaicbrowser.com/covers/chrome-tls-extension-permutation.webp)

Chrome's TLS extension permutation means the browser sends the extensions in its ClientHello in a fresh random order on every connection. The extensions themselves do not change, nor do their contents; only the sequence does. So two handshakes from one Chrome, a second apart, look different when you diff them line by line and identical when you compare the extensions as a set.

We met it the plain way, on the first run of our own handshake probe, when two captures that should have matched did not.

## What Chrome's TLS extension permutation moves, and what it leaves

The ClientHello carries several lists. Only one of them is shuffled.

| Part of the ClientHello | Shuffled per connection? |
| --- | --- |
| Extension order | Yes |
| Which extensions are present | No |
| Cipher suite order | No |
| Supported groups, signature algorithms, versions | No, the order is a preference |
| ALPN (`h2` before `http/1.1`) | No |
| GREASE values | Drawn at random each time |
| `pre_shared_key` | Always last when present |

The last row comes from the protocol. RFC 8446, the TLS 1.3 specification, says extensions may appear in any order except `pre_shared_key`, which must be the last extension in the ClientHello. That one sentence is what makes the shuffle legal, and also what limits it. BoringSSL, the TLS library Chrome is built on, has a switch for exactly this, and Chrome turns it on.

GREASE is the other moving part, and it moves differently. Chrome fills a few slots in several lists with reserved values such as `0x0a0a`, `0x2a2a` or `0xdada`, picked at random per connection. A probe that prints GREASE as raw hex will show a "difference" in a list nobody shuffled.

Mosaic is a desktop app

Email yourself the link and open it on your computer.

[How it works: Its own Chromium](https://mosaicbrowser.com/features/engine)

## Why Chrome shuffles on purpose

The reason is ossification. When one browser with a large share of traffic always sends one exact order, servers, load balancers and middleboxes start to rely on it, sometimes by accident and sometimes by fingerprinting Chrome and assuming what it will do next. Then the order can no longer change without breaking somebody, and a protocol that says "any order" in its specification has a single order in practice.

Shuffling makes the specification's promise true on the wire. Equipment that cannot cope with a new order breaks the day it meets Chrome, not years later when Chrome needs to add something. GREASE does the same job for unknown values: a server that chokes on a code point it has never seen chokes on GREASE first.

## What one handshake can no longer tell you

For anyone reading handshakes, the order of extensions stopped being a trait of the client. JA3, which hashes the extensions in the order they arrive, changes on almost every Chrome connection; JA4 sorts them before hashing and holds still. [Why Chrome's JA3 changes](https://mosaicbrowser.com/blog/why-chromes-ja3-changes) works through both with our measured values.

The more interesting consequence runs the other way. A Chrome ClientHello carries well over a dozen extensions besides GREASE. Not all of them move freely, so take a deliberately low figure: if only twelve can move, there are 479,001,600 possible orders. A real Chrome repeating its exact order on two connections is roughly a one-in-half-a-billion event.

So the order is still a signal, just not inside one handshake. A client that copies Chrome's extension list exactly and sends it in the same sequence every time has made a perfect single ClientHello and an impossible pair of them. Tools that imitate Chrome's handshake have had to add the shuffle for this reason, and a copy made before the shuffle existed stands out to anyone who compares two connections.

## The two differences we found that were not there

Our engine is Chromium built from stock source, so its handshake should be Chrome's. We checked with a raw TCP listener that reads the first TLS record each browser sends and hangs up, which took about a minute to write and needed no rebuild ([the listener, and how to parse the record](https://mosaicbrowser.com/blog/reading-a-client-hello-in-a-minute)). We pointed Tesseract Chrome and branded Google Chrome at it on the same Mac.

The first run printed two differing fields. The extension list differed, and that was the permutation. The first signature algorithm differed too, `0xdada` against `0x2a2a`, and that was GREASE, missed only because that one line printed raw hex while the extension list labelled GREASE. Label GREASE everywhere, compare the extensions as a sorted set and the other lists in order, and every field matched: version, cipher suites, extension set and count, supported groups, supported versions, signature algorithms and ALPN. The field-by-field method is in [comparing two TLS handshakes](https://mosaicbrowser.com/blog/compare-tls-fingerprints-as-sets).

We kept the shuffle as it is. It comes from the Chromium code we build, and a profile that sent a fixed order, or any order Chrome would not send, would be the only browser with that habit. That is also why [our engine has no TLS fingerprint setting](https://mosaicbrowser.com/blog/no-tls-fingerprint-setting).

## Questions

### What is TLS extension permutation in Chrome?

Chrome sends the extensions in its TLS ClientHello in a random order, drawn again for every new connection. The set of extensions stays the same; only their sequence changes.

### Why does Chrome randomise the extension order?

To stop servers and middleboxes from depending on one fixed order. TLS 1.3 allows extensions in any order, so equipment that assumed a single sequence would break when Chrome changed anything. Shuffling keeps that freedom real.

### Which parts of Chrome's ClientHello are not shuffled?

The cipher suites, supported groups, signature algorithms, versions and ALPN keep their order, because that order is a preference. pre_shared_key, when it is sent, is always the last extension, as TLS 1.3 requires.

### Can a site tell a Chrome impersonator from real Chrome by extension order?

Across connections, yes. A real Chrome almost never sends the same order twice, so a client that repeats one order on every connection has shown it is something else, however exact each single handshake is.

## Related

[Comparing two TLS handshakes: compare the set, not the order](https://mosaicbrowser.com/blog/compare-tls-fingerprints-as-sets): To compare two TLS fingerprints, label GREASE and compare the extension set sorted. Our first diff against Chrome showed two differences, both noise.

[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.

[Reading a browser's ClientHello with a one-minute listener](https://mosaicbrowser.com/blog/reading-a-client-hello-in-a-minute): 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.

[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.









## 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.
