MAATRIX / Блог / TLS-рукопожатие: что стороны говорят друг другу до первого байта данных

TLS-рукопожатие: что стороны говорят друг другу до первого байта данных

MAATRIX

Браузер открывает страницу за доли секунды, но перед тем как прилетит первый байт HTML, клиент и сервер успевают обменяться несколькими сообщениями: договориться о шифрах, показать сертификат, согласовать общий ключ. Это TLS-рукопожатие — и из-за него «просто добавить HTTPS» иногда превращается в отладку загадочных ошибок и просевшего TTFB. Разберём, что происходит в эти миллисекунды, чем TLS 1.3 быстрее TLS 1.2 и как посмотреть рукопожатие своими глазами через openssl и Wireshark.

Зачем вообще нужно рукопожатие

TLS решает три задачи: шифрует канал, подтверждает подлинность сервера и защищает целостность данных от подмены на лету. Ни один симметричный шифр сам по себе этого не даёт — у клиента и сервера ещё нет общего секретного ключа. Рукопожатие — переговоры до шифрования: стороны на виду у любого, кто слушает канал, договариваются о параметрах будущего соединения, и в итоге у обоих оказывается один и тот же секретный ключ, который подсмотревший трафик третий вычислить не может.

Дальше начинается передача данных, зашифрованная симметричным ключом — асимметричная криптография (пары ключей) в разы медленнее и годится только для первичного согласования, не для гигабайтов трафика. Важно разделять TLS-рукопожатие и HTTP поверх него: когда вы открываете https://site.com, сначала полностью отрабатывает TLS, и только потом внутри уже зашифрованного канала уходит обычный HTTP-запрос GET /.

ClientHello: с чего начинается разговор

Клиент открывает TCP-соединение и сразу отправляет первое TLS-сообщение — ClientHello. В нём:

  • поддерживаемые версии протокола — TLS 1.3, 1.2 и ниже (последние не должны использоваться на живых сервисах);
  • список шифронаборов (cipher suites) — комбинаций алгоритмов, которые клиент готов использовать, по приоритету;
  • client random — 32 байта энтропии для вычисления итогового ключа;
  • SNI (Server Name Indication) — имя домена, к которому обращается клиент, открытым текстом;
  • поддерживаемые группы для обмена ключом — эллиптические кривые или группы Диффи-Хеллмана;
  • в TLS 1.3 — ключевой материал (key share): клиент сразу присылает свою половину будущего ключа, не дожидаясь ответа сервера.

Последний пункт — главное отличие от TLS 1.2, к нему вернёмся ниже.

SNI решает конкретную проблему: на одном IP-адресе может висеть множество HTTPS-сайтов, и серверу нужно понять, сертификат какого домена отдавать, ещё до того, как соединение зашифровано. SNI передаётся открытым текстом — значит, провайдер или узел на пути видит, к какому домену вы обращаетесь, даже если содержимое страницы зашифровано. Есть расширение ECH (Encrypted Client Hello), которое шифрует и имя домена, но на конец августа 2026 года его поддержка у серверов и CDN неравномерная — полагаться на него как на единственный механизм приватности рано. Практическое следствие для администратора: если на Nginx несколько server_name с разными сертификатами на одном IP, именно SNI позволяет серверу выбрать нужный ssl_certificate.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

ServerHello и сертификат: сервер отвечает

Сервер выбирает конкретные параметры из предложенных клиентом и отвечает ServerHello: выбранную версию протокола и шифронабор, свой server random, а в TLS 1.3 — свою половину ключа в ответ на предложенную клиентом группу. Следом, всё ещё до завершения рукопожатия, сервер присылает сертификат — обычно цепочку: сертификат сайта, подписанный промежуточным центром сертификации, который подписан корневым, уже встроенным в доверенное хранилище браузера или ОС. Сервер также доказывает, что владеет приватным ключом, соответствующим сертификату.

Клиент, получив цепочку, проверяет:

  1. срок действияnotBefore/notAfter должны укладывать текущую дату в интервал;
  2. совпадение имени домена — запрошенное имя (или SNI) должно быть в CN/SAN сертификата;
  3. цепочку доверия — каждый сертификат подписан следующим, вплоть до корневого;
  4. статус отзыва (опционально, через OCSP/CRL) — не отозван ли сертификат досрочно.

Если хоть одна проверка проваливается — браузер показывает предупреждение вместо страницы. Это не бюрократия: именно она не даёт подсунуть поддельный сертификат для атаки «человек посередине».

Согласование ключа: (EC)DHE и forward secrecy

Общий секрет стороны получают через алгоритм Диффи-Хеллмана — обычно эллиптическую версию (ECDHE). Каждая сторона генерирует временную пару ключей, отправляет другой только публичную половину, и из своего приватного плюс чужого публичного обе стороны вычисляют один и тот же секрет — а подсмотревший оба публичных значения третий восстановить его за разумное время не может.

Ключевое слово — ephemeral, временный. Пара генерируется заново для каждого рукопожатия и выбрасывается сразу после использования. Отсюда forward secrecy: даже если приватный ключ сертификата сервера когда-нибудь скомпрометируют, расшифровать записанный ранее трафик не получится — временных ключей той сессии уже нигде нет. Старая схема со статическим RSA-обменом ключом этим свойством не обладала: компрометация ключа сервера задним числом раскрывала весь ранее записанный трафик. Поэтому в TLS 1.3 чистый RSA-обмен ключом убрали вовсе.

Из client random, server random и общего секрета через функцию формирования ключа (HKDF в TLS 1.3) обе стороны выводят набор симметричных ключей отдельно для каждого направления. Дальше данные идут на AEAD-шифрах вроде AES-GCM или ChaCha20-Poly1305 — быстрых и заодно проверяющих целостность каждого пакета.

TLS 1.3 против TLS 1.2: откуда прирост скорости

Главное отличие — в числе round-trip'ов до того, как можно начать слать зашифрованные данные приложения.

TLS 1.2TLS 1.3
Round-trip'ов до данных приложения21
Ключевой материал в ClientHelloнет, обмен ключом отдельным шагомда, key share сразу
0-RTT при повторном подключениинетесть, с оговорками
Устаревшие шифры (RC4, статический RSA, CBC для новых наборов)разрешеныубраны из спецификации

В TLS 1.2 порядок такой: ClientHello без ключевого материала → ServerHello с сертификатом → отдельный обмен для согласования ключа → сигнал о переходе на шифрование. Это два полных round-trip'а до данных приложения. В TLS 1.3 клиент сразу присылает key share в ClientHello, предполагая группу, которую выберет сервер (обычно угадывает — популярные группы вроде X25519 стандартны), а сервер сразу отвечает своей половиной. В итоге уже после одного round-trip'а обе стороны обладают общим секретом. Экономия — не абстрактная цифра в бенчмарке, а один RTT, вычтенный из времени до первого байта каждого нового HTTPS-соединения; на дальних и медленных каналах это ощущается заметнее, чем внутри одного дата-центра — конкретные миллисекунды сильно зависят от вашей сети, точных цифр называть не буду.

Отдельно — 0-RTT: при повторном подключении клиент может отправить данные приложения вместе с самым первым пакетом, используя ключ из прошлой сессии. Оговорка в том, что такие данные уязвимы к replay-атакам, поэтому 0-RTT включают выборочно, обычно только для идемпотентных запросов — компромисс скорости против конкретного риска, а не бесплатный выигрыш.

Как посмотреть рукопожатие своими глазами

Самый быстрый способ — openssl s_client:

openssl s_client -connect example.com:443 -servername example.com

Флаг -servername обязателен при работающем SNI — без него сервер может отдать сертификат по умолчанию вместо нужного виртуального хоста. В выводе смотрите на:

  • Certificate chain — список сертификатов от листового к промежуточным;
  • Verify return code0 (ok) значит цепочка проверена; любое другое число — конкретная причина (например, истёкший сертификат или неполная цепочка);
  • Protocol и Cipher — какая версия TLS и какой шифронабор согласованы в итоге.

Чтобы явно проверить поддержку TLS 1.3:

openssl s_client -connect example.com:443 -servername example.com -tls1_3

Обрыв с ошибкой согласования означает, что сервер эту версию не поддерживает — стоит проверить ssl_protocols в Nginx или аналогичную директиву у вашего веб-сервера.

Для разбора на уровне пакетов, когда нужно увидеть каждое TLS-сообщение отдельно, удобнее Wireshark. Захватите трафик:

sudo tcpdump -i any -w handshake.pcap port 443

Откройте файл в Wireshark и примените фильтр tls.handshake, чтобы отсечь всё, кроме сообщений рукопожатия — там видно развёрнутое содержимое ClientHello и ServerHello: список шифронаборов, расширения, выбранную группу для ключа. Зашифрованные данные приложения вы, разумеется, не увидите — рукопожатие для того и существует, чтобы дальше канал был непрозрачен для наблюдателя.

Типичные ошибки рукопожатия

Просроченный сертификат. Самая частая причина — certificate has expired:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates

Если текущая дата за пределами notBefore/notAfter, сертификат пора перевыпустить. При Let's Encrypt чаще всего дело в том, что автопродление через certbot перестало срабатывать — по этой теме есть отдельный разбор, почему Let's Encrypt не обновился автоматически.

Неполная цепочка сертификатов. Сервер отдаёт только сертификат сайта, забыв про промежуточный. Браузеры часто докачивают недостающий сертификат сами через AIA, а вот openssl s_client и многие нестандартные клиенты — нет, и получают unable to get local issuer certificate. Лечится тем, что в конфиг кладут полную цепочку (fullchain), а не только листовой сертификат — поэтому certbot по умолчанию выдаёт fullchain.pem.

Несовпадение имени домена. Сертификат выпущен на www.example.com, а вы обращаетесь напрямую на example.com, или наоборот. Проверьте SAN:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -text | grep -A1 "Subject Alternative Name"

Если нужного имени в списке нет — сертификат придётся перевыпускать с этим именем добавленным явно.

Провал согласования версии или шифронабора. Клиент и сервер не нашли общего языка — например, старый прокси умеет только TLS 1.0/1.1, а сервер их принципиально не поддерживает (и правильно делает — обе версии считаются небезопасными). Диагностируется тем же openssl s_client с явным указанием версии.

Если вы настраиваете HTTPS с нуля, обзор всего процесса есть в статье про установку Let's Encrypt SSL на VPS, а если сертификат уже есть, но Nginx его почему-то не применяет — в статье «Nginx не применяет SSL». Тем, кто хочет автоматический выпуск сертификатов без ручной возни с certbot, стоит посмотреть на Caddy с автоматическим SSL — он проходит то же рукопожатие, но берёт выпуск и продление сертификата на себя.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Можно ли ускорить TLS-рукопожатие, если сервер и клиент далеко друг от друга?

Частично: TLS 1.3 убирает один round-trip, а session resumption убирает почти все при повторном подключении. Физическую задержку канала (расстояние, число узлов) рукопожатие не лечит — тут помогает только более близкий к пользователю сервер.

Зачем нужен именно (EC)DHE, если можно обменяться ключом через RSA один раз и не тратить ресурсы каждый раз?

Ради forward secrecy: временный ключ, использованный один раз и выброшенный, не даёт расшифровать трафик задним числом, даже если приватный ключ сервера утечёт годы спустя.

Что произойдёт, если сертификат самоподписанный (self-signed)?

Рукопожатие технически пройдёт — стороны согласуют шифры и обменяются ключом, — но клиент не найдёт цепочку до доверенного корневого центра и покажет предупреждение. Для внутренних сервисов со своим CA это иногда осознанный выбор, для публичного сайта — повод получить нормальный сертификат.

Виден ли путь HTTP-запроса наблюдателю во время рукопожатия?

Нет, сам путь (/some/page) идёт уже внутри зашифрованного канала после рукопожатия. А вот SNI — имя домена — передаётся открытым текстом в ClientHello, так что какой сайт вы посещаете, теоретически видно.

Обязательно ли поддерживать TLS 1.2, если TLS 1.3 быстрее?

На практике да, пока часть клиентов (старые версии Android, встроенные библиотеки в legacy-приложениях) не умеет TLS 1.3. Большинство серверов предлагают 1.3 приоритетно, но не отключают 1.2 полностью — вопрос совместимости, а не безопасности, если убраны совсем старые версии и небезопасные шифронаборы.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →