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