Как на самом деле работают HTTPS и TLS: рукопожатие и сертификаты

Security2026-10-27TryQuickToolBox

Вы добавляете SSL-сертификат, направляете домен на сервер, и появляется замочек. Большинство разработчиков на этом останавливаются. А потом в один день API-клиент падает с UNABLE_TO_VERIFY_LEAF_SIGNATURE, мобильное приложение отклоняет ваш сертификат на старых Android, или балансировщик нагрузки терминирует TLS, и ваше приложение видит всё как обычный HTTP. И внезапно вам нужно знать, что на самом деле происходит между TCP-соединением и первым HTTP-запросом.

Эта статья разбирает, как на самом деле работают HTTPS и TLS: что делает рукопожатие, как проверяются сертификаты, что изменилось в TLS 1.3 и как диагностировать ошибки, с которыми вы реально столкнётесь.

Что на самом деле добавляет HTTPS

HTTPS — это HTTP, передаваемый через TLS-соединение. TLS обеспечивает три гарантии:

Обратите внимание, чего в этом списке нет: клиент по умолчанию не аутентифицируется. TLS аутентифицирует сервер; пользователя аутентифицирует ваше приложение.

Рукопожатие TLS 1.2 шаг за шагом

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

  1. ClientHello — клиент отправляет поддерживаемые версии TLS, случайное значение, список шифров и (если применимо) имя хоста SNI.
  2. ServerHello — сервер выбирает версию и шифр, возвращает своё случайное значение и отправляет свою цепочку сертификатов.
  3. Обмен ключами — при ECDHE сервер отправляет эфемерный открытый ключ; клиент отправляет свой. Обе стороны выводят один и тот же общий секрет. Сервер подписывает стенограмму рукопожатия закрытым ключом своего сертификата.
  4. Finished — обе стороны отправляют MAC по стенограмме рукопожатия, подтверждая, что ничего не было подменено.
  5. Данные приложения — HTTP-запросы и ответы передаются в зашифрованном виде.

Ключевая деталь — эфемерный обмен ключами. При ECDHE новая пара ключей генерируется для каждого соединения. Даже если злоумышленник запишет годы трафика и позже украдёт закрытый ключ сервера, он не сможет расшифровать прошлые сессии. Это свойство называется прямой секретностью (forward secrecy), и это главная причина, по которой статический обмен ключами RSA был объявлен устаревшим.

Как TLS 1.3 изменил правила игры

TLS 1.3 упрощает рукопожатие до одного кругового обхода и полностью убирает устаревшие алгоритмы.

АспектTLS 1.2TLS 1.3
Круговые обходы (полное рукопожатие)21
Обмен ключамиRSA или ECDHEТолько ECDHE (прямая секретность)
ШифрыОтдельно шифр, MAC, обмен ключамиТолько AEAD-наборы, например TLS_AES_128_GCM_SHA256
Статический RSA, CBC, RC4РазрешеныУдалены
Возобновление сессииSession IDs / ticketsPSK с опцией 0-RTT

В TLS 1.3 клиент угадывает группу обмена ключами и отправляет свою долю в первом сообщении, так что сервер может завершить согласование ключей сразу. Сертификаты по-прежнему отправляются, но теперь они зашифрованы, а значит пассивные наблюдатели больше не могут увидеть, какое имя хоста обслуживается — его выдаёт только SNI в ClientHello, если не используется Encrypted Client Hello.

Возобновление 0-RTT позволяет возвращающемуся клиенту отправить данные приложения в первом пакете. Это быстро, но данные 0-RTT можно воспроизвести (replay), поэтому никогда не используйте его для неидемпотентных запросов, например платежей.

Сертификаты и проверка цепочки

Сертификат связывает открытый ключ с именем хоста. Проверка — это проход по цепочке:

Самая частая причина ошибок unable to verify — отсутствующий промежуточный сертификат. Браузеры иногда маскируют это, кэшируя промежуточные сертификаты, но инструменты командной строки и мобильные приложения падают с ошибкой. Всегда отдавайте полную цепочку: сначала конечный, затем промежуточные — и никогда корневой.

Практическая отладка с OpenSSL

Когда что-то ломается, сначала посмотрите, что на самом деле отправляет сервер, прежде чем винить клиента.

# Показать цепочку сертификатов, которую представляет сервер
openssl s_client -connect example.com:443 -servername example.com -showcerts

# Согласовать только TLS 1.3 и вывести выбранный шифр
openssl s_client -connect example.com:443 -tls1_3

# Проверить срок действия сертификата одной командой
echo | openssl s_client -connect example.com:443 2>/dev/null \
  | openssl x509 -noout -dates -subject -ext subjectAltName

Если s_client сообщает verify error:num=21:unable to verify the first certificate, цепочка неполная. Если он сообщает о несовпадении имени хоста, проверьте список SAN — wildcard-сертификаты покрывают только один уровень, поэтому *.example.com соответствует api.example.com, но не a.b.example.com.

Типичные ошибки конфигурации

Разумная конфигурация сервера

Для Nginx современная базовая конфигурация выглядит так:

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;

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

FAQ

Шифрует ли HTTPS имя хоста?

По умолчанию нет. Расширение SNI в ClientHello отправляется открытым текстом, чтобы сервер знал, какой сертификат представить. Encrypted Client Hello (ECH) скрывает его, но распространение пока ограничено. Путь и строка запроса всегда шифруются.

Почему мой сертификат работает в браузере, но не в curl или мобильном приложении?

Почти всегда это отсутствующий промежуточный сертификат. Браузеры часто сами загружают или кэшируют промежуточные сертификаты, а более строгие клиенты требуют, чтобы сервер отправлял полную цепочку. Проверьте с помощью openssl s_client -showcerts.

TLS 1.3 всегда быстрее TLS 1.2?

Для новых соединений — да, один круговой обход вместо двух. Для возобновлённых соединений разрыв сокращается, а 0-RTT может сделать TLS 1.3 ещё быстрее ценой риска воспроизведения для неидемпотентных запросов.

Если вы проводите аудит веб-сервера после изменения TLS, анализ логов часто выявляет реальное влияние: сломанные перенаправления, неожиданные всплески 4xx или клиенты, всё ещё обращающиеся по HTTP. Вы можете загрузить access-лог в Nginx log analyzer, чтобы увидеть распределение кодов состояния и найти клиентов, которые так и не перешли на HTTPS.