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

Это седьмая часть цикла «Что происходит, когда открываешь сайт».

Что показывает curl

$ curl -sv -o /dev/null https://alekseychikun.ru/robots.txt
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_CHACHA20_POLY1305_SHA256 / X25519MLKEM768 / RSASSA-PSS
* ALPN: server accepted h2
клиентсервер Client Hello: шифры, половина ключа, SNI, ALPN — 1567 Б Server Hello + сертификат + подпись + Finished — 6383 Б всё после Server Hello уже зашифровано Finished + сразу HTTP-запрос ответ 32 мс126 мс128 мс
TLS 1.3 договаривается за один круг: клиент получает всё нужное в первом же ответе сервера.

Шаг за шагом

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

Ответ сервера — один пакет за другим, 6383 байта:

  • Server Hello — выбранный шифр и вторая половина ключа. С этого момента обе стороны могут вычислить общий секрет, и всё дальнейшее уже зашифровано;
  • Encrypted Extensions — остальные договорённости, например выбранный протокол h2;
  • Certificate — паспорт сайта: сертификат и цепочка до доверенного центра (о ней в следующей части);
  • CertificateVerify — подпись всего рукопожатия закрытым ключом сертификата. Это доказательство, что сервер действительно владеет ключом, а не просто показывает чужой сертификат;
  • Finished — контрольная сумма всего разговора: никто не подменил ни байта.

Finished клиента — и сразу следом HTTP-запрос, в том же полёте. Итого TLS 1.3 стоит один круг по сети. В TLS 1.2 было два: сначала договаривались о шифре, потом обменивались ключами.

Куда делись 128 мс, если круг — 29

Из захвата: приветствие клиента ушло на 32-й миллисекунде, ответ сервера пришёл на 126-й. Круг по сети — 29 мс, значит около 65 мс сервер думал. На что:

  • подпись RSA-ключом 4096 бит. Ключ сертификата этого сайта — RSA 4096 (видно в выводе openssl). Подпись таким ключом — самая дорогая операция рукопожатия, а сервер у нас — ноутбук на i3 2013 года. Ключ ECDSA P-256 подписывает на порядок быстрее при сопоставимой стойкости;
  • обмен ключами X25519MLKEM768. Это гибрид: классический X25519 плюс постквантовый ML-KEM. Его теперь по умолчанию предлагают современные клиенты, отсюда и размер приветствия — 1,5 КБ вместо пары сотен байт. Вычисляется быстро, но данных по сети больше.

Во втором запросе из первой части TLS занял 69 мс вместо 128. Каждый запуск curl — новый процесс без памяти о прошлой сессии, так что это было полное рукопожатие, просто на «разогретом» сервере. Браузер устроен иначе: он сохраняет билет сессии и в следующий раз проходит сокращённый путь, без пересылки сертификата и тяжёлой подписи.

Почему ChaCha20, а не AES

В строке итога — шифр TLS_CHACHA20_POLY1305_SHA256. Обычно в TLS выбирают AES-GCM: на современных процессорах он ускорен аппаратно. Но у процессора нашего сервера аппаратного AES нет, и прокси (traefik, написан на Go) учитывает это: если у сервера нет ускорения AES, он предпочитает ChaCha20, который быстро считается обычными инструкциями. Шифр — не догма, а выбор под железо.

Посмотреть у своего сайта

# версия, шифр, обмен ключами, подпись
curl -sv -o /dev/null https://example.ru/ 2>&1 | grep -E 'SSL connection|ALPN'

# тип и длина ключа сертификата
echo | openssl s_client -connect example.ru:443 -servername example.ru 2>/dev/null \
  | openssl x509 -noout -text | grep -E 'Public Key Algorithm|Public-Key'

# сколько стоит рукопожатие: время до конца TLS минус время TCP
curl -so /dev/null -w 'tcp=%{time_connect} tls=%{time_appconnect}\n' https://example.ru/

Если TLS стоит больше трёх кругов TCP — смотрите на тип ключа и на нагрузку сервера, а не на сеть.

Защищённое соединение открывается долго?

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

· полезно?
АЧ
Алексей Чикун веб-разработчик, команда TRIOHEAD

Делаю сайты и сервисы целиком: от вёрстки до сервера. Пишу о том, что проверил на своих проектах.

Комментарии

// загружаю комментарии…

Комментарии публикуются после проверки. Ссылки и HTML не принимаются.