JA3 и JA4 fingerprinting: как TLS-отпечатки выявляют ботов

Бот может подделать User-Agent, эмулировать движения мыши и подменить canvas fingerprint. Но он не может изменить то, как его TLS-библиотека представляется серверу при установке соединения. Именно на этом основана технология JA3 fingerprinting — метод идентификации клиентов по параметрам TLS-рукопожатия, который работает до того, как загрузится хотя бы один байт вашей страницы. В этой статье мы разберём, как устроены отпечатки JA3 и JA4, почему они надёжнее JavaScript-проверок и как reverse proxy может использовать их для фильтрации ботов в реальном времени.

Что происходит до загрузки страницы: TLS ClientHello

Прежде чем браузер получит HTML, CSS или JavaScript, он устанавливает зашифрованное соединение с сервером. Первое сообщение в этом процессе — ClientHello. Это пакет, который клиент отправляет серверу в самом начале TLS-рукопожатия (handshake). ClientHello содержит набор параметров, которые описывают криптографические возможности клиента.

Основные поля ClientHello

  • TLS Version — версия протокола, которую поддерживает клиент (TLS 1.2 = 0x0303, TLS 1.3 = 0x0304).
  • Cipher Suites — упорядоченный список шифронаборов, которые клиент готов использовать. Каждый шифронабор — это комбинация алгоритмов шифрования, хеширования и обмена ключами.
  • Extensions — расширения протокола: поддержка SNI, ALPN, supported_versions, signature_algorithms, key_share и десятки других.
  • Elliptic Curves (Supported Groups) — список эллиптических кривых для обмена ключами (x25519, secp256r1, secp384r1 и другие).
  • EC Point Formats — форматы представления точек на эллиптических кривых (как правило, uncompressed).

Каждая TLS-библиотека формирует ClientHello по-своему. Chrome, Firefox, Safari, curl, Python requests, Go net/http — все они отправляют разные наборы параметров в разном порядке. Именно эта уникальность и делает ClientHello источником отпечатка.

Важный момент: ClientHello отправляется до установки шифрованного канала. Это открытый текст, доступный любому промежуточному узлу — и в первую очередь reverse proxy перед вашим сервером. В отличие от HTTP-заголовков, которые формируются на уровне приложения и легко подделываются, параметры ClientHello определяются криптографической библиотекой, встроенной в клиент. Разработчик бота не контролирует эти параметры, если не модифицирует саму TLS-библиотеку — а это задача совершенно другого уровня сложности.

Как работает JA3 fingerprinting: от ClientHello до хеша

JA3 — метод, разработанный командой Salesforce (John Althouse, Jeff Atkinson, Josh Atkins — отсюда название) в 2017 году. Идея элегантна в своей простоте: взять пять полей из ClientHello, объединить их в строку и вычислить MD5-хеш.

Формула JA3

Из ClientHello извлекаются пять параметров:

  1. TLSVersion — числовое значение версии протокола.
  2. Ciphers — список cipher suites, разделённых дефисом.
  3. Extensions — список расширений, разделённых дефисом.
  4. EllipticCurves — список поддерживаемых кривых.
  5. EllipticCurvePointFormats — форматы точек.
  6. Эти пять полей объединяются через запятую в одну строку — JA3 raw string:

    
    TLSVersion,Ciphers,Extensions,EllipticCurves,ECPointFormats
    

    Для реального Chrome эта строка выглядит примерно так:

    
    771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,65281-16-10-35-43-51-13-45-28-27-17513-21,29-23-24,0
    

    Здесь 771 — это TLS 1.2 (0x0303 = 771 в десятичной системе). Далее идут cipher suites в том порядке, в каком их перечисляет Chrome, затем расширения, кривые и формат точек.

    Затем от этой строки берётся MD5-хеш — и это финальный JA3-отпечаток:

    
    cd08e31494f9531f560d64c695473da9
    

    Этот хеш стабилен для конкретной версии конкретного клиента. Chrome 120 на Windows и Chrome 120 на macOS дадут одинаковый JA3, потому что обе платформы используют один и тот же встроенный BoringSSL. Но Chrome и Firefox — разные, поскольку Firefox использует NSS (Network Security Services) — совершенно другую криптографическую библиотеку с собственным набором предпочтений. Chrome и Puppeteer с модифицированным порядком cipher suites — тоже разные. Даже минорные обновления браузера могут изменить JA3, если в новую версию добавлена поддержка нового cipher suite или расширения.

    GREASE: ловушка для неаккуратного парсинга

    В 2016 году Google ввёл механизм GREASE (Generate Random Extensions And Sustain Extensibility). Суть: в ClientHello добавляются специальные «мусорные» значения из зарезервированного диапазона — 0x0a0a, 0x1a1a, 0x2a2a, 0x3a3a, 0x4a4a, 0x5a5a, 0x6a6a, 0x7a7a, 0x8a8a, 0x9a9a, 0xaaaa, 0xbaba и другие.

    Эти значения случайны при каждом соединении. Их цель — протестировать серверы на устойчивость к неизвестным расширениям и cipher suites. Но для fingerprinting они создают проблему: если учитывать GREASE, один и тот же браузер будет давать разные отпечатки при каждом подключении.

    Поэтому при вычислении JA3 все GREASE-значения отфильтровываются. Это документировано в спецификации JA3 и обязательно для корректной реализации. Любая система, которая не удаляет GREASE перед хешированием, будет генерировать нестабильные отпечатки и принимать ложные решения.

    JA4: следующее поколение TLS-отпечатков

    JA3 отлично справляется с базовой идентификацией, но у него есть ограничения. MD5-хеш необратим — по нему нельзя понять, какие именно параметры использовал клиент. Два совершенно разных набора параметров теоретически могут дать один и тот же хеш (коллизия). Кроме того, JA3 чувствителен к порядку cipher suites и extensions, а некоторые библиотеки рандомизируют этот порядок.

    JA4, разработанный FoxIO (John Althouse — один из авторов JA3), решает эти проблемы. JA4 — это не один хеш, а структурированный отпечаток из нескольких секций.

    Структура JA4

    JA4-отпечаток состоит из трёх частей, разделённых символом подчёркивания:

    Часть 1 — JA4_a (метаданные):

    Компактное текстовое описание клиента:

    • Протокол (t = TCP, q = QUIC)
    • Версия TLS (12 = TLS 1.2, 13 = TLS 1.3)
    • Наличие SNI (d = есть, i = нет)
    • Количество cipher suites (двузначное число)
    • Количество extensions (двузначное число)
    • Первое значение ALPN (h2 = HTTP/2, h1 = HTTP/1.1)

    Пример: t13d1516h2 — TCP, TLS 1.3, SNI есть, 15 cipher suites, 16 extensions, HTTP/2.

    Часть 2 — JA4_b (cipher suites):

    SHA-256 (первые 12 символов) от отсортированного списка cipher suites. Сортировка устраняет зависимость от порядка — даже если библиотека рандомизирует порядок cipher suites, хеш останется стабильным.

    Часть 3 — JA4_c (extensions + signature algorithms):

    SHA-256 (первые 12 символов) от двух блоков: отсортированные extensions и отдельно — значения signature_algorithms.

    Итоговый JA4 выглядит так:

    
    t13d1516h2_8daaf6152771_e5627efa2ab1
    

    Что JA4 улучшает по сравнению с JA3

    Характеристика JA3 JA4
    Алгоритм хеширования MD5 (уязвим к коллизиям) SHA-256 (криптографически стойкий)
    Зависимость от порядка Да — порядок cipher suites влияет на хеш Нет — списки сортируются перед хешированием
    Читаемость Только хеш, ничего не видно Метаданные читаемы: протокол, версия, количества
    Signature Algorithms Не учитываются Отдельная секция в JA4_c
    Гранулярность Один хеш на всё Три независимые секции для гибкого сравнения

    Благодаря сортировке JA4 устойчив к рандомизации порядка, которую всё чаще применяют современные TLS-библиотеки. А читаемая первая секция позволяет быстро понять, с чем вы имеете дело, даже без полной базы отпечатков.

    Почему headless-браузеры не могут скрыть TLS-отпечаток

    Когда бот использует Puppeteer или Playwright, он управляет настоящим Chromium. На уровне JavaScript всё выглядит как обычный Chrome — User-Agent совпадает, navigator.webdriver скрыт, canvas fingerprint подменён. Но TLS-рукопожатие происходит на уровне TLS-стека операционной системы или встроенной библиотеки (BoringSSL в случае Chromium).

    Вот в чём проблема: headless-браузеры часто используют другую сборку Chromium, скомпилированную с другими флагами. Это приводит к тому, что список cipher suites или extensions отличается от стандартного Chrome. Даже если различие минимально — например, отсутствие одного расширения или изменённый порядок кривых — JA3-хеш будет совершенно другим.

    Конкретный пример

    Стандартный Chrome может отправить cipher suites в таком порядке:

    
    4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53
    

    А Puppeteer, запущенный с bundled Chromium, может дать:

    
    4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53-10
    

    Одно лишнее значение — и JA3-хеш полностью меняется. Никакой User-Agent spoofing это не скроет, потому что TLS-рукопожатие завершается за сотни миллисекунд до того, как JavaScript получит возможность что-либо подменить.

    Есть и другая категория различий. Headless-режим Chromium может не загружать определённые расширения TLS, связанные с визуальным рендерингом или пользовательским интерфейсом. Некоторые модификации Puppeteer отключают расширение delegated_credentials или изменяют параметры key_share. Каждое такое отличие — это изменённый бит в JA3-строке и, как следствие, совершенно другой MD5-хеш.

    Антидетект-браузеры (Multilogin, GoLogin, Dolphin) пытаются решить эту проблему, подменяя TLS-стек. Некоторые из них используют patched BoringSSL, чтобы эмулировать отпечаток конкретной версии Chrome. Но это требует постоянного обновления при каждом релизе Chrome, и любое отставание мгновенно выдаёт подделку. На практике между выходом новой версии Chrome и обновлением антидетект-браузера проходит от нескольких дней до нескольких недель — окно, в котором бот легко обнаруживается.

    JA3 fingerprinting на reverse proxy: детекция до загрузки страницы

    Ключевое преимущество TLS-отпечатков — они доступны на самом раннем этапе обработки запроса. Reverse proxy (Nginx, HAProxy, Envoy или кастомный сервер) может извлечь JA3/JA4 из ClientHello до проксирования запроса к бэкенду.

    Как это работает на практике

    1. Клиент инициирует TLS-подключение к вашему домену.
    2. Reverse proxy перехватывает ClientHello и вычисляет JA3/JA4-хеш.
    3. Хеш сравнивается с базой известных отпечатков: легитимные браузеры, известные боты, автоматизированные инструменты.
    4. На основании результата proxy принимает решение: пропустить, заблокировать, отправить на CAPTCHA или пометить для аналитики.
    5. Всё это происходит за миллисекунды — пользователь не замечает никакой задержки.
    6. При использовании технологии eBPF (extended Berkeley Packet Filter) можно анализировать ClientHello на уровне ядра операционной системы — ещё до того, как пакет дойдёт до user-space приложения. Это даёт минимальную задержку и возможность обрабатывать десятки тысяч соединений в секунду.

      Важно: JA3/JA4-хеш записывается в лог вместе с каждым запросом. Это позволяет ретроспективно анализировать трафик — например, обнаружить, что 40% визитов с определённого JA3-отпечатка приходят от автоматизированного инструмента, а не от настоящего Chrome.

      Сравнение TLS-отпечатков: кто есть кто

      Ниже приведена таблица, показывающая, как различные клиенты идентифицируются через JA3 fingerprinting. Конкретные хеши зависят от версии, ОС и настроек, но принцип стабилен: каждый тип клиента даёт свой уникальный отпечаток.

      Клиент TLS Version Примерное кол-во Cipher Suites JA3-хеш (пример) Характерные признаки
      Chrome 120+ TLS 1.3 15 cd08e31494f9531f560d64c695473da9 GREASE-значения, расширение compressed_certificate
      Firefox 121+ TLS 1.3 17 579ccef312d18482fc42e2b822ca2430 Delegated credentials, расширение record_size_limit
      Puppeteer (bundled) TLS 1.3 15-16 b32309a26951912be7dba376398abc3b Похож на Chrome, но отличается порядком extensions
      curl (OpenSSL) TLS 1.2/1.3 18-24 456523fc94726331a4d5a2e1d40b2cd7 Широкий набор cipher suites, нет GREASE
      Python requests TLS 1.2 11-31 3b5074b1b5d032e5620f69f9f700ff0e Зависит от версии OpenSSL в системе
      Go net/http TLS 1.3 5 a0e9f5d64349fb13191bc781f81f42e1 Минимальный набор cipher suites

      Обратите внимание на несколько закономерностей:

      • curl и Python requests используют системную OpenSSL, которая предлагает широкий набор cipher suites — это мгновенный маркер нечеловеческого трафика при обращении к веб-ресурсу.
      • Go net/http имеет характерный минимализм — всего 5 cipher suites. Любой бот на Go немедленно идентифицируется. Go — популярный язык для написания парсеров и скрейперов, поэтому его отпечаток встречается в бот-трафике непропорционально часто.
      • Puppeteer очень близок к Chrome, но отличия всё равно есть — достаточные для надёжной классификации. Ключевое различие обычно в наборе extensions и порядке их перечисления.
      • Firefox использует библиотеку NSS, которая формирует принципиально другой ClientHello — с другим набором cipher suites, другим порядком extensions и поддержкой расширений, которых нет в Chrome (например, delegated_credentials). Это делает Firefox легко отличимым от Chrome-based клиентов.

      Практические сценарии применения JA3 fingerprinting

      Защита рекламного трафика от скликивания

      Рекламные боты кликают по объявлениям, расходуя бюджет рекламодателя. JavaScript-проверки они научились обходить. Но если reverse proxy фиксирует, что клик пришёл от клиента с JA3-отпечатком curl или Go — это не человек. Даже если бот использует headless Chromium, различия в TLS-отпечатке позволяют выделить автоматизированный трафик.

      Защита API от парсинга

      Парсеры часто используют Python requests или Go-клиенты. Их JA3-отпечатки радикально отличаются от браузерных. Даже если парсер подставляет User-Agent Chrome, его TLS-стек сообщает правду.

      Аудит трафика и аналитика

      JA3/JA4-отпечатки можно собирать пассивно, не блокируя ни одного запроса. Это позволяет провести аудит: какой процент трафика приходит от реальных браузеров, а какой — от автоматизированных инструментов. Результаты часто удивляют: доля ботов в некоторых сегментах достигает 40-60%.

      Пассивный режим особенно ценен на старте: вы подключаете сбор отпечатков, накапливаете данные за несколько дней и получаете объективную картину без какого-либо риска для легитимных пользователей. Только после анализа данных вы принимаете решение о блокировке конкретных отпечатков или категорий клиентов.

      Многоуровневая защита

      TLS-отпечатки наиболее эффективны в комбинации с другими сигналами:

      • JA3/JA4 — идентификация TLS-стека клиента.
      • HTTP/2 fingerprint — порядок фреймов SETTINGS, WINDOW_UPDATE, HEADERS.
      • Поведенческий анализ — паттерны навигации, скорость взаимодействия.
      • JavaScript fingerprint — canvas, WebGL, AudioContext (для тех, кто прошёл TLS-проверку).

      Каждый уровень отсеивает свою категорию ботов. TLS-fingerprinting — первый и самый быстрый барьер.

      Можно ли обойти JA3/JA4 fingerprinting

      Было бы нечестно утверждать, что TLS-fingerprinting невозможно обмануть. Существуют подходы:

      • Patched TLS-библиотеки — модификация BoringSSL или OpenSSL для имитации конкретного отпечатка. Библиотеки вроде utls (Go) или curl-impersonate позволяют это делать.
      • Антидетект-браузеры — коммерческие решения, которые подменяют TLS-стек вместе с браузерным fingerprint.
      • Использование реальных браузеров — запуск обычного Chrome через CDP (Chrome DevTools Protocol) с кастомным профилем.

      Но каждый из этих методов существенно повышает стоимость атаки. Обычный скрипт на Python requests заменяется сложной инфраструктурой с поддержкой patched библиотек, резидентных прокси и постоянного обновления отпечатков. Для массового скликивания это делает атаку экономически невыгодной.

      Кроме того, JA4 с его сортировкой и структурированным форматом усложняет точную имитацию — нужно воспроизвести не только список cipher suites, но и signature algorithms, extensions с корректными значениями, а также поведение на уровне HTTP/2-фреймов.

      Здесь работает экономический принцип: чем дороже имитация, тем меньше ботов будут её реализовывать. JA3/JA4 fingerprinting не претендует на то, чтобы остановить 100% ботов. Его задача — резко поднять порог входа, отсекая массовый автоматизированный трафик дешёвых ботов, которые составляют подавляющее большинство фродовых визитов. Оставшиеся сложные боты обрабатываются следующими уровнями защиты — HTTP/2 fingerprinting, поведенческим анализом и машинным обучением на совокупности сигналов.

      Проверьте свой TLS-отпечаток

      Увидеть собственный TLS-отпечаток можно на специализированных демо-страницах, которые анализируют ClientHello при подключении и показывают JA3-хеш, JA4-строку и все параметры TLS-рукопожатия. Такие инструменты визуализируют каждое поле ClientHello: версию протокола, полный список cipher suites с расшифровкой имён алгоритмов, все расширения, эллиптические кривые и GREASE-значения.

      Простой эксперимент: откройте демо-страницу с TLS-анализом в обычном Chrome, а затем — в режиме инкогнито, в Firefox или через командную строку с curl. Вы увидите, насколько отличаются отпечатки даже при одинаковом User-Agent. Chrome и Firefox дадут принципиально разные хеши. curl покажет отпечаток, не похожий ни на один браузер. А если вы запустите Puppeteer и откроете ту же страницу — его отпечаток будет близок к Chrome, но не идентичен.

      Этот эксперимент наглядно демонстрирует, почему TLS fingerprinting работает: каждый клиент оставляет уникальный след ещё до того, как сервер отправит хотя бы один байт контента.

      Заключение

      JA3 и JA4 fingerprinting — технология, которая работает на самом фундаментальном уровне сетевого стека. Она не зависит от JavaScript, не требует загрузки страницы и не может быть обойдена стандартными средствами автоматизации. В комбинации с eBPF-обработкой на reverse proxy эта технология обеспечивает детекцию ботов за миллисекунды — до того, как вредоносный трафик дойдёт до вашего приложения.

      Для бизнеса это означает конкретные результаты: снижение расходов на фродовый рекламный трафик, защита API от парсинга, объективная картина качества трафика на основе данных, а не предположений.

Scroll to Top