Когда вы открываете сайт по обычному HTTP, всё, что летит между браузером и сервером, — открытый текст. Любой, кто оказался на пути (владелец Wi-Fi в кафе, провайдер, взломанный роутер), может это прочитать и даже подменить: увидеть пароль, подставить свою страницу вместо настоящей, вырезать кусок ответа. HTTPS закрывает эту дыру: буква «S» на конце — это тот же HTTP, но упакованный в защищённый слой TLS. Разберёмся простыми словами, что делает TLS, как две стороны договариваются о ключах и почему браузер вообще верит, что на том конце именно тот сайт, за который он себя выдаёт.

Сначала — вся картина целиком: пять шагов рукопожатия и то, что из них достаётся тому, кто слушает канал.

TLS handshake: 5 шагов до первого запроса Браузер Сервер что видит тот, кто посередине 1ClientHello: версии, шифры, домендомен виден: bank.ru 2ServerHello + сертификатсертификат виден — он публичный 3проверяет сертификатподпись CA · домен · срокподпись CA подделать нечем 4общий ключ через асимметрикузаписал обмен — ключ не вычислил 5весь HTTP — симметричный ключ▓▓ только шифротекст ▓▓ ✗а если на том конце самозванец: ключи он сгенерирует,подпись доверенного CA — нет. Шаг 3 обрывает соединение, шагов 4 и 5 не будет

Асимметрика работает только на шагах 2–4 — предъявить сертификат и завести общий ключ; дальше весь HTTP идёт быстрым симметричным. Тот, кто посередине, видит и домен, и сертификат, и сам обмен, но ключ из записи не вычислит. А подпись доверенного CA подделать не может — на этом самозванец и останавливается, на шаге 3.

Обязательно

Зачем нужно шифрование

У защиты трафика две отдельные задачи, и их полезно не путать.

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

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

То есть TLS даёт три вещи сразу: конфиденциальность (никто не прочитал), целостность (никто не изменил) и аутентификацию (это точно тот сервер). Обычный HTTP не даёт ни одной.

Симметричное и асимметричное шифрование на пальцах

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

Симметричное шифрование — это один общий ключ. Тем же ключом, которым закрыли, и открывают. Быстро и удобно, но есть проблема: как передать этот ключ собеседнику по открытой сети, чтобы его по дороге не перехватили? Если отправить ключ в открытом виде, вся защита теряет смысл.

Асимметричное шифрование решает именно эту проблему. У стороны есть пара ключей: открытый (публичный) и закрытый (приватный). Открытый ключ можно раздавать всем — представьте открытый навесной замок, который вы разослали по почте. Кто угодно может защёлкнуть им коробку, но открыть её сможете только вы своим закрытым ключом, который никому не отдаёте. Работает это медленно, зато не требует заранее иметь общий секрет.

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

Одну деталь тут стоит уточнить сразу, иначе она собьёт с толку дальше. Раньше общий ключ действительно передавали «замком»: клиент придумывал секрет, защёлкивал его открытым ключом из сертификата и отправлял. Так больше не делают — в TLS 1.3 этот способ убрали совсем. Сегодня общий секрет вообще никуда не передают: каждая сторона присылает свою половинку, и из двух половинок обе независимо вычисляют один и тот же ключ, а подслушивающему двух половинок не достаётся. Называется это обменом Диффи-Хеллмана, и половинки берут одноразовые — свежие на каждое соединение. Сертификат в такой схеме ничего не шифрует, он только подписывает обмен: подтверждает, что половинку прислал настоящий сервер. Отсюда важное следствие: даже если закрытый ключ сервера однажды утечёт, записанный вчера разговор по нему расшифровать уже не выйдет.

TLS handshake: как договариваются о ключах

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

  1. Привет от клиента. Браузер сообщает: «хочу защищённое соединение, вот версии TLS и наборы шифров, которые я понимаю».
  2. Привет от сервера + сертификат. Сервер выбирает подходящий шифр и присылает свой сертификат — в нём его имя (домен) и открытый ключ.
  3. Проверка сертификата. Браузер убеждается, что сертификат настоящий, выдан кому надо и не просрочен (об этом ниже). Если что-то не так — та самая страшная красная страница «ваше соединение не защищено».
  4. Выработка общего ключа. Стороны обмениваются одноразовыми половинками и независимо считают из них один и тот же секрет, из которого рождается симметричный ключ сессии. Даже если кто-то записал весь обмен, вычислить этот ключ со стороны он не может.
  5. Переключение на симметрику. С этого момента весь HTTP-трафик шифруется быстрым симметричным ключом. Handshake закончен, полетели настоящие запросы.

Всё это занимает доли секунды и происходит незаметно каждый раз, когда вы видите замочек в адресной строке. Современный TLS 1.3 ужал число шагов, чтобы соединение устанавливалось ещё быстрее.

Сертификаты и центры сертификации

Остаётся главный вопрос из шага 3: браузер получил сертификат с открытым ключом, но откуда ему знать, что это сертификат настоящего банка, а не самозванца, который тоже умеет генерировать ключи? Ключи-то может создать кто угодно.

Ответ — центры сертификации (Certificate Authority, CA). Это организации, которым браузеры и операционные системы доверяют «из коробки»: список их корневых сертификатов зашит в систему. CA проверяет, что вы действительно владеете доменом, и подписывает ваш сертификат своей подписью. Браузер, получив сертификат сайта, проверяет эту подпись: «подписано доверенным CA, домен совпадает, срок не вышел — верю».

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

корневой центр в хранилище системы промежуточный подписан корнем сертификат сайта подписан промежуточным

Подписи идут слева направо, а браузер проверяет справа налево: от сертификата сайта до корня, которому доверяет система.

Раньше сертификаты стоили денег и выдавались вручную. Сейчас есть Let's Encrypt — бесплатный CA, который выдаёт сертификаты автоматически. Важная особенность: его сертификаты живут недолго — по умолчанию 90 дней, и продлевают их не руками, а по расписанию специальным клиентом. Короткий срок здесь не придирка: чем меньше живёт сертификат, тем меньше пользы от него тому, к кому утёк ключ. Отрасль движется в ту же сторону — предельный срок жизни сертификатов планово сокращают, так что «выпустили на два года и забыли» перестаёт быть вариантом в принципе, а автопродление становится единственным рабочим.

Версии и число кругов

Версий TLS формально четыре, живых — две.

1.0 и 1.1 выключены везде: браузеры их не поддерживают, а стандарты безопасности запрещают. Если сервис ещё их принимает, это находка для проверяющих, а не совместимость.

1.2 — рабочая лошадка последних лет: рукопожатие занимает два круга (клиент — сервер — клиент — сервер), то есть добавляет к соединению два сетевых оборота поверх того, который уже потратил TCP.

1.3 — один круг. Список алгоритмов урезали до безопасных, лишние согласования убрали, и рукопожатие уложилось в один оборот. Отсюда и ответ на «почему говорят то один, то два оборота»: это разница версий. На соединении между континентами (150 мс в одну сторону) переход с 1.2 на 1.3 экономит около 300 миллисекунд на каждое новое соединение.

Возобновление сессии и 0-RTT

Второй раз к тому же серверу соединяться дешевле — и это не кэш, а отдельный механизм.

Возобновление. При первом рукопожатии сервер выдаёт клиенту билет (session ticket) — зашифрованную памятку о согласованных ключах. Клиент предъявляет билет при следующем соединении, и полное согласование не нужно: рукопожатие сокращается. В TLS 1.3 это один оборот вместо одного плюс вычисления, в 1.2 — один вместо двух.

0-RTT (в TLS 1.3) идёт дальше: клиент отправляет данные вместе с первым пакетом рукопожатия, не дожидаясь ответа сервера. Задержка соединения становится нулевой, но есть жёсткая оговорка: такие данные можно повторить (перехватить и отправить снова), поэтому в 0-RTT разрешают только безопасные операции — GET без побочных эффектов. Отправлять так POST нельзя, и поддержка на сервере обычно выключена по умолчанию.

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

Отзыв сертификата, HSTS и смешанное содержимое

Три вещи, которые всплывают в проде вместе с сертификатом.

Отзыв. Украденный или ошибочно выданный сертификат нужно отозвать до истечения срока. Старый способ — списки отзыва (CRL), большие и редко обновляемые; следующий — запрос статуса у центра сертификации (OCSP), который добавляет задержку и утечку информации о посещениях. Рабочий сегодня подход — прикрепление статуса (OCSP stapling): сервер сам получает у центра подписанное «сертификат жив» и отдаёт его в рукопожатии. Настраивается на стороне сервера, ускоряет соединение и убирает зависимость клиента от доступности центра. Отдельно стоит знать, что браузеры в проверке отзыва не полагаются только на эти механизмы — короткий срок жизни сертификата (90 дней у бесплатных центров) сам по себе снижает цену утечки.

HSTS — заголовок, которым сервер говорит браузеру: «ко мне ходить только по HTTPS, минимум столько-то времени». После первого визита браузер сам переписывает http:// в https://, не отправляя незащищённого запроса вовсе. Это закрывает окно между вводом адреса и перенаправлением — и это же делает настройку необратимой на срок действия: ошибочно выставленный длинный срок означает, что вернуться на HTTP нельзя, пока он не истечёт.

Смешанное содержимое. Страница по HTTPS, а картинка или скрипт по HTTP — браузер такое блокирует (скрипты) или ругается (картинки), и выглядит это как «на проде поехала вёрстка». Причина почти всегда в жёстко прописанном адресе в коде или в базе, а не в настройке сервера.

Цикл перенаправления за балансировщиком

Классический симптом, который стоит уметь узнавать: приложение бесконечно перенаправляет запрос на само себя, браузер отвечает «слишком много перенаправлений».

Механика такая. TLS завершается на балансировщике, дальше к приложению идёт обычный HTTP. Приложение видит http, считает, что клиент пришёл незащищённо, и отвечает перенаправлением на https://…. Клиент идёт по нему — и снова попадает на балансировщик, который снова передаёт запрос по HTTP. Цикл.

Разрывается он тем, что балансировщик сообщает исходную схему заголовком X-Forwarded-Proto: https, а приложение обязано его читать и доверять ему. В Spring Boot за это отвечает обработка заголовков перенаправления (server.forward-headers-strategy=native или framework), в других стеках — своя настройка того же смысла.

Две обязательные оговорки. Доверять этому заголовку можно только если запросы приходят исключительно через ваш балансировщик: иначе клиент подставит его сам и притворится защищённым. И то же касается адреса клиента: X-Forwarded-For — единственный источник настоящего адреса за прокси, и в журналах приложения без него все запросы приходят «от балансировщика».

Где лежат сертификаты и доверенные корни

Разговор «отключить проверку сертификата» возникает потому, что неясно, где вообще живёт доверие.

У сервиса на JVM есть своё хранилище доверенных корней — файл в составе среды исполнения (cacerts). Системные корни операционной системы в него не попадают автоматически: корпоративный корневой сертификат, установленный в систему, для Java-приложения не существует, пока его не добавили в хранилище. Отсюда и классика: curl работает, приложение отвечает «путь сертификации не построен».

Правильные ходы в такой ситуации: добавить нужный корень в хранилище доверия образа (шагом сборки), или указать своё хранилище параметрами запуска, или (в Kubernetes) смонтировать общий набор корней. Отключение проверки (trustAllCerts, -k у curl) годится только для разовой диагностики: оно превращает HTTPS в шифрование без аутентификации, то есть снимает защиту от подмены сервера.

Собственный сертификат сервиса (для mTLS или для входящих соединений) держат в отдельном хранилище ключей и обновляют вместе с перезапуском или перечитыванием — как именно, разобрано в разделе про эксплуатацию ниже.

TLS termination: где расшифровывается трафик

В реальной системе TLS почти никогда не расшифровывается на самом приложении. Между интернетом и вашим сервисом обычно стоит балансировщик или обратный прокси, и именно он расшифровывает входящий HTTPS. Это называется TLS termination — «точка, где TLS заканчивается».

Схема типичная: снаружи всё идёт по HTTPS до балансировщика (nginx, облачный load balancer, ingress). Балансировщик держит сертификат, расшифровывает трафик и передаёт дальше — до вашего сервиса — уже как обычный HTTP по внутренней сети. Ваш код при этом работает с простым HTTP и вообще не думает про сертификаты.

браузер адресная строка https https, трафик зашифрован балансировщик держит сертификат http и X-Forwarded-Proto: https сервис видит обычный http

Шифрование кончается на балансировщике: дальше идёт обычный HTTP, и об исходной схеме сервис узнаёт только из заголовка X-Forwarded-Proto.

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

mTLS: взаимная проверка

В обычном TLS личность проверяет только клиент: браузер убеждается, что сервер настоящий, а сам серверу не предъявляет никакого сертификата (пользователь доказывает, кто он, уже потом — паролем или токеном).

mTLS (mutual TLS, взаимный TLS) добавляет проверку в обе стороны: не только клиент проверяет сервер, но и сервер требует сертификат от клиента. Обе стороны предъявляют документы. Для сайтов это избыточно, но между сервисами внутри системы удобно: сервис A принимает запрос, только если сервис B предъявил валидный клиентский сертификат. Так соседний сервис доказывает, что он свой, ещё до всякой бизнес-логики. Это одна из основ безопасности внутренних коммуникаций, к которой стоит вернуться, когда дойдёте до защиты сервисов.

Где это включено на практике: service mesh вроде Istio выдаёт каждому сервису сертификат и включает mTLS между всеми подами автоматически, а у Kafka и PostgreSQL клиентский сертификат часто заменяет пароль. Как это укладывается в структуру сервиса, разобрано в статье про структуру модулей.

Где это применяется

Для backend-разработчика TLS — это не абстрактная криптография, а несколько очень практичных вещей.

  • Где терминируется TLS. В типовой схеме — на балансировщике или ingress перед приложением; внутри кластера сервисы часто общаются по обычному HTTP в доверенной сети. Знать эту границу нужно, чтобы понимать, откуда взялся заголовок X-Forwarded-Proto и почему приложение «видит» http, хотя пользователь пришёл по https.
  • Истёкший сертификат — частый инцидент. Сертификаты имеют срок годности, и забытое продление роняет сайт целиком: браузеры отказываются открывать страницу. Отсюда практика автопродления и мониторинга даты истечения — это одна из самых обидных и предотвратимых аварий.
  • Самоподписанные сертификаты в тестах. Локально и в тестовых окружениях часто используют сертификат, который вы подписали сами, без CA. Браузер такому не верит и ругается — это нормально для разработки. Главное не тащить привычку «отключить проверку сертификата» в production-код.

Где спотыкаются начинающие:

  • Путают HTTPS и отдельный протокол. HTTPS — это не замена HTTP, а тот же HTTP внутри защищённого TLS-конверта. Все методы, статусы и заголовки те же.
  • Думают, что TLS шифрует всё подряд. Имя сайта, к которому вы подключаетесь, на этапе установки соединения ещё видно со стороны: клиент называет его открытым текстом в самом первом сообщении рукопожатия, в поле SNI (Server Name Indication). Иначе и не выйдет — за одним адресом обычно живут сотни сайтов, и серверу надо знать, чей сертификат доставать, ещё до того как появилось шифрование. Так что шифруется содержимое запросов и ответов, а не сам факт «вы зашли на такой-то сайт».
  • Считают, что шифрование = подлинность. Можно зашифровать канал с самозванцем. Именно сертификат и CA отвечают за то, что на том конце нужный сервер, а не просто «кто-то с ключами».
  • Отключают проверку сертификата, чтобы «заработало». Быстро убирает ошибку в тесте и тихо убивает всю защиту, если уедет в production.
Дополнительно: при первом чтении можно пропустить

Глубже: эксплуатация сертификатов: SNI, ALPN, версии, ротация и хранилище довериярасширенное

Рукопожатие выше объясняет, что происходит; здесь то, что приходится настраивать и что ломается.

SNI и ALPN. На одном адресе и порту живут десятки сайтов, и сервер должен выбрать сертификат до того, как соединение зашифровано. Для этого клиент называет имя сайта открытым текстом в первом сообщении, это SNI, и по нему балансировщик выбирает сертификат и часто сам сервис. Клиент без SNI (старые библиотеки, вызов по IP-адресу) получает сертификат по умолчанию и ошибку «имя не совпадает». Там же, в первом сообщении, клиент перечисляет протоколы, которые умеет, h2 и http/1.1, это ALPN; по нему сервер выбирает HTTP/2, и без ALPN HTTP/2 не включится, о чём молча узнают те, у кого прокси по дороге его не пробрасывает.

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

Ротация. Сертификат живёт ограниченный срок, у публичных центров сейчас 90 дней и меньше, и продлевают его не руками. Стандарт это ACME (Let's Encrypt и другие): клиент на сервере или в кластере (certbot, cert-manager) сам доказывает владение доменом, получает сертификат и перевыпускает за месяц до конца. Что нужно от вас: проверка, что сервис подхватывает новый файл без перезапуска (перечитывание по сигналу или перезагрузка балансировщика), и сигнал тревоги на срок до истечения меньше двух недель, потому что автопродление тоже ломается, а узнать об этом в день истечения поздно. Внутренние сертификаты от своего центра живут по тем же правилам, только выпускает их Vault или cert-manager с внутренним издателем.

Хранилище доверия. Клиент проверяет сертификат сервера по списку корневых центров, которым доверяет, и список у каждой платформы свой. У JVM это файл cacerts внутри установки Java, и внутренний центр или корпоративный прокси в нём неизвестны: отсюда PKIX path building failed. Лечится добавлением корня в хранилище (keytool -importcert) или отдельным хранилищем через -Djavax.net.ssl.trustStore, и никогда отключением проверки. У Node список свой плюс NODE_EXTRA_CA_CERTS, у Python requests свой пакет корней и REQUESTS_CA_BUNDLE, у Go и curl системное хранилище. В контейнере всё это делает образ, и обновление образа Java обновляет и список корней, что важно раз в несколько лет, когда старый корень истекает.

Глубже: mTLS между двумя сервисами по-настоящемурасширенное

Раздел про mTLS выше объясняет идею. В реальной связке «сервис A вызывает сервис B по mTLS» нужно собрать четыре вещи, и любая из них даёт ту же ошибку рукопожатия, по которой не видно, что именно не так.

У каждого сервиса два хранилища: ключ и сертификат (кто я) и хранилище доверия (кому верю). Сервис B в настройках сервера требует клиентский сертификат и проверяет его по своему хранилищу доверия, где лежит корень, выпустивший сертификат A. Сервис A в настройках клиента предъявляет свой сертификат и проверяет сертификат B по корню, выпустившему B. Оба сертификата выпускает один внутренний центр, и тогда хранилища доверия одинаковы; при разных центрах в каждое кладут чужой корень.

Проверка личности не заканчивается рукопожатием: B знает, что A предъявил валидный сертификат, но не знает, что это именно A, а не любой другой сервис с сертификатом от того же центра. Поэтому B сверяет имя из сертификата (CN или SAN, в сетках это spiffe://cluster/ns/shop/sa/orders) со списком разрешённых, и это уже авторизация, а не TLS. Пароли и токены при этом не нужны: сертификат и есть учётная запись, так работают подключения к Kafka и PostgreSQL по сертификатам.

Ротация здесь удваивается: истекает и серверный, и клиентский сертификат, и оба обязаны перечитываться без перезапуска; в service mesh это делает sidecar, выпуская сертификаты на часы, а вне сетки cert-manager или Vault с агентом, который кладёт файлы и шлёт сигнал. Отладка: openssl s_client -connect b:8443 -cert a.pem -key a.key -CAfile ca.pem показывает рукопожатие целиком, и по строке Verify return code видно, кто кого не признал.

Коротко

  • HTTPS это тот же HTTP внутри TLS: рукопожатие договаривается о ключах, сертификат и центр подтверждают, что сервер настоящий.
  • TLS терминируют на балансировщике или ingress; внутри доверенной сети сервисы часто ходят по HTTP, а между ними при необходимости mTLS.
  • SNI называет сайт до шифрования, ALPN выбирает HTTP/2; живые версии 1.2 и 1.3, старые отключены на входе.
  • Сертификаты продлевают автоматически (ACME, cert-manager), сервис перечитывает файл без перезапуска, тревога за две недели до истечения; внутренние корни кладут в хранилище доверия платформы (cacerts у JVM), а не отключают проверку.
  • mTLS между сервисами это ключ и доверие с обеих сторон плюс проверка имени из сертификата; отладка через openssl s_client.
  • Живых версий две: TLS 1.2 с двумя кругами рукопожатия и TLS 1.3 с одним — отсюда разница «один-два оборота» в оценках задержки.
  • Второе соединение дешевле за счёт возобновления по билету, а 0-RTT отправляет данные сразу, но только для безопасных операций: такие данные можно повторить.
  • Статус отзыва отдают прикреплением (OCSP stapling), HSTS запрещает браузеру ходить по HTTP (и необратим на срок действия), смешанное содержимое блокируется браузером.
  • Цикл перенаправлений за балансировщиком лечится доверием к X-Forwarded-Proto — и доверять ему можно только при единственном входе через свой балансировщик.
  • У приложения на JVM своё хранилище доверия: системные корни в него не попадают, поэтому корпоративный корень добавляют в образ, а не отключают проверку.

Что почитать дальше

TLS стоит поверх транспортного слоя, поэтому полезно понимать, что под ним. Начните с моделей OSI и TCP/IP, чтобы видеть, на каком слое живёт шифрование, и с TCP и UDP — TLS работает поверх надёжного TCP-соединения. Дальше — как устроены соединения и почему handshake добавляет задержку к каждому новому подключению. Вернитесь к HTTP, чтобы окончательно закрепить, что HTTPS — это он же в защищённом конверте. А общую картину защиты сервисов — сертификаты, секреты, доступы — собирает раздел про безопасность backend.