TLS-фингерпринтинг определяет программное обеспечение, которое открывает HTTPS-соединение, по тому, как оно его открывает — какие шифры предлагает и в каком порядке, какие расширения и эллиптические кривые перечисляет — еще до отправки какого-либо содержимого запроса. Каждая TLS-библиотека и каждая версия браузера приветствует сервер немного по-разному, и это приветствие видно серверу в открытом виде, даже если все последующее зашифровано. JA3 и его преемник JA4 превращают приветствие в короткий хэш. Антибот-системы сравнивают этот хэш с тем, что должен был бы произвести заявленный браузер. Вот почему Python-скрипт, представляющийся Chrome и подключающийся через безупречный резидентный IP, все равно обнаруживается: IP говорил “домашний пользователь в Берлине”, а TLS-рукопожатие говорило “OpenSSL в скрипте”.

Откуда берется отпечаток: ClientHello

HTTPS-соединение начинается с TLS-рукопожатия. Самое первое сообщение, отправляемое клиентом, — это ClientHello. Оно не зашифровано — и не может быть зашифровано, поскольку шифрование еще не согласовано, — и содержит, среди прочего:

  • версию TLS, которую поддерживает клиент;
  • список наборов шифров, которые он готов использовать, в порядке предпочтения;
  • список расширений — SNI (имя хоста), ALPN (какие прикладные протоколы, например h2, http/1.1), поддерживаемые группы (эллиптические кривые), алгоритмы подписи, общий ключ, билет сессии и так далее — опять же в определенном порядке;
  • эллиптические кривые и форматы точек, которые он поддерживает.

Ни одно из этих значений не является секретным и не выбирается для каждого сайта; они встроены в TLS-библиотеку и приложение, которое ее использует. Chrome 128 отправляет один конкретный ClientHello; Firefox отправляет другой; модуль ssl Python (OpenSSL) отправляет третий; crypto/tls в Go — четвертый; curl — пятый. Сервер видит ClientHello для каждого соединения в любом случае — он должен его увидеть, чтобы ответить. Запись и хэширование ничего не стоят.

JA3 и JA4: превращение рукопожатия в сигнатуру

JA3

Представленный исследователями Salesforce в 2017 году, JA3 объединяет пять полей ClientHello — версию TLS, наборы шифров, расширения, эллиптические кривые, форматы точек эллиптических кривых — в виде списков десятичных чисел, разделенных запятыми, а затем хэширует строку с помощью MD5. Результат — 32-символьный отпечаток, например cd08e31494f9531f560d64c695473da9. Любой может вычислить его из захвата пакетов, и списки, сопоставляющие хэши JA3 с известными клиентами (“этот хэш = Chrome 120 на Windows”, “этот хэш = python-requests”), широко распространены.

У JA3 есть слабость, которая стала фичей для обхода: поскольку он хэширует список расширений в порядке, а Chrome (начиная с версии 110) рандомизирует порядок расширений при каждом соединении, одна установка Chrome порождает множество различных хэшей JA3. Это было сделано намеренно, чтобы серверы не зацикливались на одном отпечатке — и это сделало JA3 гораздо менее полезным для идентификации браузеров.

JA4

JA4, выпущенный FoxIO в 2023 году, был разработан, чтобы пережить это. Он сортирует списки шифров и расширений перед хэшированием (так что перемешивание Chrome больше не имеет значения), сохраняет некоторые поля в читаемом виде и добавляет больше сигналов: идет ли соединение через TCP или QUIC, тип SNI, значение ALPN, версию TLS, а также количество шифров и расширений. Отпечаток JA4 выглядит как t13d1516h2_8daaf6152771_e5627efa2ab1: первый блок означает “TLS 1.3 через TCP, 15 шифров, 16 расширений, ALPN h2”, а два других — усеченные хэши отсортированного списка шифров и отсортированного списка расширений плюс алгоритмы подписи. JA4 — это целое семейство (JA4 для TLS, JA4H для HTTP-заголовков, JA4S для серверной стороны, JA4X для сертификатов и другие); когда вендоры антибот-систем говорят сегодня “TLS-отпечаток”, они обычно имеют в виду JA4 или проприетарный аналог, построенный тем же способом.

Почему прокси не может помочь

Прокси ретранслирует TLS-рукопожатие; он не переписывает его. При использовании HTTP-прокси клиент отправляет CONNECT host:443, прокси открывает TCP-туннель, и собственный ClientHello клиента проходит через туннель к месту назначения нетронутым. При использовании SOCKS5 происходит то же самое на уровень ниже. Таким образом, сервер назначения видит два независимых факта:

  • исходный IP — прокси, резидентный и безупречный;
  • ClientHello — клиента, точно указывающий, какая библиотека его создала.

Логика антибота затем задает простой вопрос: соответствует ли TLS-отпечаток User-Agent? Заголовок User-Agent утверждает “Chrome 128 на Windows”. JA4 говорит “OpenSSL 3.x через Python”. Настоящий Chrome никогда не создает такой JA4. Запрос помечается — не потому, что IP плохой, а потому, что история внутренне противоречива. Это самая распространенная причина, по которой “чистый” резидентный прокси все равно получает CAPTCHA на защищенных сайтах, и именно поэтому мы говорим, что прокси отвечает на вопрос откуда, а не кто; см. что прокси скрывает, а что нет.

Прокси может иметь значение в одном отношении: некоторые провайдеры сами завершают TLS (прокси типа “человек посередине”, который расшифровывает и повторно шифрует, требуя установки его сертификата). В этом случае место назначения видит ClientHello прокси, а не ваш — что обычно хуже, поскольку это серверная библиотека, а не браузер. Обычные прокси так не делают.

Как выглядит каждый тип клиента

КлиентЧто говорит его TLS-отпечатокСоответствует ли User-Agent браузера?
Настоящий Chrome / Firefox / SafariЭтот браузер и версия, на этой ОСДа — это и есть браузер
Python requests, httpx, aiohttpOpenSSL в конфигурации модуля ssl PythonНет — распознается как скрипт независимо от заголовков
Node.js fetch / axiosСборка OpenSSL/BoringSSL NodeНет
Go net/httpСобственный TLS-стек Go; отличительныйНет
curl (стандартная сборка)OpenSSL/LibreSSL в сборкеНет
Headless Chrome через Puppeteer/PlaywrightНастоящий Chrome — правильный TLSДа для TLS; другие сигналы (флаги автоматизации, отсутствующие плагины) могут выдать
“Имитирующие” клиенты (curl-impersonate, curl_cffi, tls-client)ClientHello выбранного браузера, воспроизведенный байт в байтДа, если вы также согласуете заголовки с тем же браузером

Как получить согласованный отпечаток

Есть три честных пути, в порядке возрастания стоимости.

  1. Используйте настоящий браузер. Автоматизируйте Chrome или Firefox с помощью Playwright или Puppeteer; тогда TLS действительно будет браузерным. Медленнее и тяжелее, чем сырой HTTP, но для защищенных сайтов это базовый уровень. Сопоставьте каждый браузерный контекст с отдельным прокси — см. прокси в Playwright, Puppeteer и Selenium.
  2. Используйте имитирующий HTTP-клиент. Библиотеки, такие как curl_cffi (Python, построена на curl-impersonate) и tls-client, воспроизводят ClientHello и настройки HTTP/2 конкретных браузеров, так что вы получаете скорость скрейпера с отпечатком браузера. При этом вы должны поддерживать все остальные заголовки согласованными с той же версией браузера — User-Agent, Accept-Language, клиентские подсказки sec-ch-ua — иначе вы просто замените одно противоречие другим. Наше руководство по прокси для Python показывает переход с requests на curl_cffi.
  3. Перестаньте притворяться. Если у цели есть официальный API, используйте его со своей реальной идентичностью клиента. TLS-фингерпринтинг становится проблемой только тогда, когда отпечаток противоречит заявлению.

Что не работает: установка User-Agent Chrome в requests. Это меняет один заголовок и оставляет рукопожатие нетронутым — несоответствие становится еще более явным, потому что обычный python-requests UA с отпечатком OpenSSL хотя бы самосогласован.

Более широкая картина: отпечатки складываются

TLS — это один слой. Современные антибот-вендоры также снимают отпечатки HTTP/2-соединения (значения кадра SETTINGS, размеры окон, порядок заголовков и псевдозаголовков — иногда это называют фингерпринтингом Akamai), стека TCP/IP (размер окна, TTL, опции; классический p0f) и, конечно, самого браузера через JavaScript, который HTTP-клиент вообще не может выполнять. Каждый слой должен рассказывать ту же историю, что и остальные, и что и IP. Резидентный IP с браузерным TLS-отпечатком и браузерными настройками HTTP/2, но без выполнения JavaScript, все равно является аномалией на сайте, ожидающем выполнения JS-челленджа. Вот почему честный ответ на вопрос “какой прокси избегает обнаружения?” — “ни один сам по себе”, и почему наши рейтинги учитывают, что провайдер делает помимо IP; см. сравнение элитных прокси.

Как увидеть свой собственный отпечаток

Некоторые публичные страницы отображают JA3/JA4, которые они вычисляют для вашего соединения, а также то, каким браузером они его считают. Загрузите одну из них из настоящего браузера, затем получите ее с помощью вашего скрипта через тот же прокси и сравните. Если они различаются, вы нашли причину, по которой ваш скрипт обрабатывается иначе, чем ваш браузер — и никакая смена прокси это не исправит. Добавьте это к проверкам в Как тестировать прокси, когда известно, что цель использует корпоративный антибот.

Часто задаваемые вопросы

Что такое JA3?

MD5-хэш пяти полей из TLS ClientHello (версия, шифры, расширения, кривые, форматы точек), используемый для идентификации клиентского ПО. Он хорошо работает для скриптов и библиотек; для Chrome он стал ненадежным после того, как Chrome начал рандомизировать порядок расширений.

Что такое JA4?

Преемник JA3: он сортирует списки перед хэшированием, так что перемешивание расширений не меняет его, сохраняет часть отпечатка в читаемом виде и включает транспорт (TCP/QUIC), ALPN и количество. Он является основой большинства современных систем обнаружения TLS-отпечатков.

Меняет ли резидентный прокси мой TLS-отпечаток?

Нет. Прокси туннелирует ваше рукопожатие без изменений; место назначения видит IP прокси и ваш ClientHello. Отпечаток определяется только клиентским ПО.

Можно ли подделать TLS-отпечаток браузера в Python?

Да, с помощью имитирующих клиентов, таких как curl_cffi, которые воспроизводят ClientHello и поведение HTTP/2 выбранного браузера. Вы должны поддерживать заголовки согласованными с тем же браузером. Обычные requests/httpx этого не умеют.

Одно и то же TLS-фингерпринтинг и браузерный фингерпринтинг?

Нет. TLS-фингерпринтинг читает рукопожатие зашифрованного соединения и идентифицирует клиентское ПО; браузерный фингерпринтинг выполняет JavaScript и идентифицирует устройство (canvas, шрифты, GPU). Антибот-системы используют оба, плюс репутацию IP, и проверяют их согласованность.

Почему один и тот же скрипт блокируется на одном сайте, а на другом нет?

Потому что проверки TLS-отпечатков — это функция корпоративных антибот-продуктов, а не веба в целом. Сайты без такой защиты вообще не смотрят на рукопожатие; сайты с ней сравнивают его с User-Agent при каждом запросе.