TLS handshake — как устанавливается защищённое соединение

TLS handshake — как устанавливается защищённое соединение

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

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

Перед тем как клиент и сервер начнут передавать зашифрованные данные, им нужно:

  1. Договориться о версии TLS и наборе алгоритмов (cipher suite).
  2. Аутентифицировать сервер (клиент должен убедиться, что это настоящий example.com, а не атакующий).
  3. Создать общий сессионный ключ для симметричного шифрования.

Всё это происходит за 1 round-trip в TLS 1.3. Round-trip — это обмен сообщениями туда и обратно: клиент посылает пакет, сервер отвечает, клиент снова посылает. Один round-trip добавляет задержку, равную времени прохождения сигнала до сервера и обратно (RTT).

Шаг 1: ClientHello

Клиент (браузер) начинает рукопожатие, отправляя сообщение ClientHello:

Client → Server: ClientHello
  Версия: TLS 1.3
  Случайное число: client_random (32 байта)
  Поддерживаемые cipher suites: TLS_AES_256_GCM_SHA384, TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256
  Поддерживаемые группы ключей: x25519, P-256, P-384
  Параметры для DH (публичный ключ клиента в выбранной группе)
  server_name (SNI): example.com

Клиент заранее отправляет свой DH-публичный ключ в ClientHello — это оптимизация TLS 1.3, позволяющая сократить рукопожатие. SNI (Server Name Indication) сообщает серверу, к какому домену обращается клиент, что критично при виртуальном хостинге (один сервер обслуживает несколько доменов с разными сертификатами).

Шаг 2: ServerHello

Сервер получает ClientHello, выбирает из предложенных опций и отвечает:

Server → Client: ServerHello
  Выбранная версия: TLS 1.3
  Выбранный cipher suite: TLS_AES_256_GCM_SHA384
  Случайное число: server_random (32 байта)
  Параметры для DH (публичный ключ сервера)
  
Server → Client: EncryptedExtensions
  Дополнительные параметры (например, ALPN — какой протокол: h2, http/1.1)
  
Server → Client: Certificate
  Сертификат сервера (цепочка: leaf → intermediate CA → root CA)
  
Server → Client: CertificateVerify
  Подпись всего рукопожатия закрытым ключом сервера — доказательство владения сертификатом
  
Server → Client: Finished
  MAC всего рукопожатия — подтверждение целостности

Важный момент: в TLS 1.3 всё, кроме ServerHello, шифруется. Перехватчик видит только выбор версии и cipher suite, но не сертификат сервера — это дополнительная защита приватности.

Шаг 3: Проверка сертификата клиентом

Получив сертификат, клиент должен его проверить. Это ключевой шаг для аутентичности:

  1. Проверить цепочку сертификатов. Сертификат подписан Intermediate CA, чей сертификат подписан Root CA. Root CA встроены в браузер или ОС.
  2. Проверить подпись CertificateVerify. Убедиться, что сервер владеет закрытым ключом, соответствующим сертификату.
  3. Проверить домен. Сертификат должен быть выдан на домен example.com (или *.example.com).
  4. Проверить срок действия. Сертификат не истёк (обычно выдаётся на 90 дней).
  5. Проверить отзыв. Сертификат не отозван (через CRL или OCSP).

Если любая проверка не проходит — браузер показывает предупреждение «Ваше соединение не защищено» и предлагает пользователю решить, продолжать ли (а иногда и не предлагает).

Шаг 4: Завершение рукопожатия

Если проверка пройдена, клиент завершает рукопожатие:

Client → Server: Finished
  MAC всего рукопожатия

На этом этапе обе стороны вычислили общий сессионный ключ из client_random, server_random и результата DH. Рандомные числа от обеих сторон гарантируют, что даже если DH-ключи повторно использовать, сессионный ключ будет разным (добавляют энтропию).

Теперь можно передавать данные приложения — HTTP-запросы и ответы, зашифрованные симметричным ключом.

Визуализация полного обмена (TLS 1.3)

Client                                Server
  |                                     |
  |--- ClientHello ------------------->|  (предлагает алгоритмы, DH public key)
  |                                     |
  |<-- ServerHello --------------------|  (выбирает алгоритмы, DH public key)
  |<-- {EncryptedExtensions} ----------|  (зашифровано)
  |<-- {Certificate} -----------------|  (сертификат сервера, зашифрован)
  |<-- {CertificateVerify} -----------|  (доказательство владения ключом)
  |<-- {Finished} --------------------|  (MAC рукопожатия)
  |                                     |
  |--- {Finished} ------------------->|  (MAC рукопожатия)
  |                                     |
  |<==== Защищённые данные ===========>|  (HTTP запросы и ответы)

К моменту отправки HTTP-запроса рукопожатие уже завершено, и весь трафик защищён.

Вычисление сессионных ключей

Из трёх компонентов — DH shared secret, client_random и server_random — стороны вычисляют несколько ключей через HKDF (HMAC-based Key Derivation Function):

master_secret = HKDF(DH_shared_secret, client_random, server_random)

Из master_secret выводятся:
  - encryption_key_client  (для шифрования client → server)
  - encryption_key_server  (для шифрования server → client)
  - mac_key_client         (для проверки целостности client → server)
  - mac_key_server         (для проверки целостности server → client)

Ключи для каждого направления разные — это важно. Если атакующий каким-то чудом узнает ключ шифрования клиента, он не сможет расшифровать ответы сервера. HKDF гарантирует, что даже если один из компонентов (например, client_random) предсказуем, итоговый ключ безопасен при условии секретности DH shared secret.

0-RTT для повторных соединений

TLS 1.3 поддерживает 0-RTT — клиент может отправить данные приложения уже в первом пакете, если ранее уже общался с этим сервером:

Client                                Server
  |--- ClientHello + данные приложения + pre-shared key -->|

Это работает через механизм PSK (Pre-Shared Key): сервер выдаёт клиенту идентификатор сессии, и при следующем подключении клиент использует его, пропуская полное рукопожатие. 0-RTT полезно для мобильных приложений и повторных заходов на сайт, но данные 0-RTT уязвимы к replay-атакам (повторной отправке). Поэтому на 0-RTT отправляют только идемпотентные запросы (GET), но не POST.

Проверь себя

  1. Зачем в ClientHello отправляется публичный DH-ключ, а не ждать ответа сервера?
  2. Что проверяет клиент, получив сертификат сервера?
  3. Что означает, что в TLS 1.3 часть рукопожатия зашифрована?
<details> <summary>Ответы</summary>
  1. Это оптимизация: клиент заранее отправляет DH-ключ, чтобы обе стороны могли вычислить сессионный ключ за один round-trip, а не за два.
  2. Цепочку сертификатов до Root CA, подпись CertificateVerify (доказательство владения закрытым ключом), соответствие домену, срок действия, не отозван ли сертификат.
  3. Перехватчик не видит сертификат сервера и другие параметры, что защищает приватность (не даёт узнать, на какой именно домен заходит клиент) и усложняет анализ протокола для атак.
</details>

Что унести с урока

  • TLS 1.3 рукопожатие — 1-RTT: ClientHello → ServerHello + Certificate + Finished → Client Finished.
  • Клиент проверяет сертификат по цепочке, домену, сроку, подписи и отзыву.
  • 0-RTT позволяет отправлять данные с первым пакетом при повторных соединениях, но уязвимо к replay.
  • SNI (Server Name Indication) в ClientHello сообщает серверу, к какому домену обращается клиент.

В следующем уроке разберём подробнее сертификаты и центры сертификации — как работает PKI, почему сертификатов несколько в цепочке и что значит «доверенный корневой сертификат».

Попробуйте интерактивную версию

Практические задачи, квизы и AI-наставник — бесплатный старт без карты

Перейти к практике