Сети и протоколы
Рукопожатие за 1 RTT: key share и ECDHE
Браузер открывает TCP-соединение и немедленно отправляет ClientHello. Это единственное сообщение содержит и список шифров, и половину ключа шифрования — сервер может вычислить общий секрет прямо здесь и ответить зашифрованными данными за один проход. Именно так TLS 1.3 превращает два RTT в один.
Рукопожатие за 1 RTT: шаг за шагом
Шаг 1 — ClientHello. Клиент отправляет: список версий TLS (через расширение supported_versions), поддерживаемые наборы шифров, поддерживаемые именованные группы (кривые), поддерживаемые алгоритмы подписи и расширение key_share с как минимум одним публичным ключом ECDHE — обычно x25519.
Шаг 2 — ServerHello. Сервер выбирает набор шифров (например TLS_AES_128_GCM_SHA256), выбирает именованную группу из key_share клиента, генерирует собственную эфемерную пару ключей и отвечает своим публичным ключом. Теперь обе стороны знают публичные ключи друг друга и могут запустить ECDHE для вывода общего секрета.
Шаг 3 — Зашифрованное рукопожатие. Сервер немедленно выводит ключ трафика рукопожатия из общего секрета и отправляет EncryptedExtensions, Certificate, CertificateVerify и серверный Finished — все зашифрованные — в одном сетевом проходе.
Шаг 4 — Клиент завершает. Клиент выводит тот же ключ трафика, расшифровывает сообщения сервера, проверяет цепочку сертификатов, проверяет подпись CertificateVerify над транскриптом, проверяет HMAC серверного Finished, отправляет клиентский Finished и начинает передавать данные приложения.
Итого round-trip: один. ClientHello уходит, ServerHello + зашифрованное рукопожатие приходит, клиентский Finished + первые данные приложения уходят — всё в одном сетевом обмене.
- ClientHello
- ~512 байт
- ServerHello
- ~150 байт
- Certificate (цепочка ECDSA P-256)
- 1–3 КБ
- CertificateVerify (ECDSA)
- ~70 байт
- Server Finished
- ~50 байт
- Client Finished
- ~50 байт
- Итого RTT
- 1
ECDHE: обмен ключами за key share
Обе стороны договариваются о кривой (обычно x25519 для производительности или P-256 для FIPS). Каждая генерирует приватный скаляр (случайное 256-битное целое) и вычисляет публичную точку: public = private · G, где G — базовый генератор кривой. Антон отправляет свою публичную точку в key_share, Дима — свою в ответ. Каждый вычисляет общий секрет, умножая публичную точку другого на свой приватный скаляр:
- Антон:
shared = ant_private · dim_public - Дима:
shared = dim_private · ant_public
Оба получают одну и ту же точку на кривой. Подслушивающий, который видел только публичные точки, не может её восстановить без решения задачи дискретного логарифма на эллиптической кривой — неразрешимой для правильно выбранных кривых на классическом оборудовании.
Perfect Forward Secrecy (PFS)
Оба клиент и сервер используют эфемерные пары ключей, сгенерированные заново для каждого соединения и уничтожаемые после него. Если злоумышленник позже украдёт долгосрочный приватный ключ Димы с диска, прошлые сессии всё равно не расшифруются — они использовали эфемерные ключи, которых уже нет. Это и есть Perfect Forward Secrecy.
TLS 1.2 позволял RSA-транспорт ключей: клиент шифровал секрет сессии долгосрочным публичным ключом сервера. Кража приватного ключа означала расшифровку всех записанных прошлых сессий. TLS 1.3 требует эфемерный обмен ключами, делая PFS настройкой по умолчанию.
Проверка цепочки сертификатов
Получив Certificate, клиент видит цепочку: листовой сертификат для хоста, затем один или несколько промежуточных. Он обходит цепочку снизу вверх:
- Проверить, что листовой подписан промежуточным.
- Проверить, что промежуточный подписан корневым.
- Убедиться, что корень находится в локальном хранилище доверия.
- Проверить, что SAN (Subject Alternative Names) листового включают хост подключения.
- Проверить срок действия (
notBefore <= now <= notAfter). - Проверить отзыв (CRLite, stapled OCSP или политика короткоживущих сертификатов).
Если хоть один шаг провалился — соединение завершается с fatal alert (например unknown_ca).
Хеш транскрипта и Finished
Каждый байт, отправленный в ходе рукопожатия, поступает в накапливаемый SHA-256-дайджест — хеш транскрипта. Сообщение Finished — это HMAC(finished_key, transcript_hash). Если один бит на проводе был перевёрнут или подменён — HMAC не совпадёт и соединение прервётся. Именно так TLS 1.3 обнаруживает атаки понижения версии: транскрипт записывает первоначально предложенную клиентом версию, поэтому попытка middlebox’а принудить к TLS 1.2 сломает проверку Finished.
Проследите успешное холодное рукопожатие TLS 1.3 от ClientHello до первого зашифрованного байта данных.
Почему TLS 1.3 требует эфемерный обмен ключами (ECDHE) и убирает RSA-транспорт ключей?
Какую гарантию целостности даёт хеш транскрипта TLS 1.3?
Упорядочьте шаги проверки цепочки сертификатов:
- 1 Получить Certificate с листовым + промежуточными сертификатами
- 2 Убедиться, что SAN листового совпадает с именем хоста подключения
- 3 Обойти цепочку: проверить подпись каждого сертификата публичным ключом его издателя
- 4 Найти корневой в локальном хранилище доверия
- 5 Проверить срок действия (notBefore <= now <= notAfter)
- 6 Проверить статус отзыва (CRLite, stapled OCSP или политика короткоживущих сертификатов)
Почему это работает
RSA vs ECDSA сертификаты. Алгоритм подписи серверного сертификата не зависит от ECDHE. Сертификаты ECDSA P-256 примерно в 5 раз быстрее верифицировать, чем RSA-2048, и подписи в четыре раза меньше. Edge-сервер, обрабатывающий миллион рукопожатий в секунду, платит в пять раз больше CPU при RSA. Провайдеры облаков по умолчанию используют цепочки ECDSA; RSA предлагается только для совместимости с устаревшими клиентами.
- 01Почему сервер отправляет HelloRetryRequest и что клиент делает в ответ?
- 02Что происходит при провале проверки цепочки сертификатов (неизвестный CA)?
- 03Объясните ECDHE в одном абзаце. Почему обе стороны приходят к одному секрету, не передавая его?
TLS 1.3 сжимает рукопожатие до одного RTT, встраивая публичный ключ ECDHE клиента в первое сообщение. Сервер немедленно запускает алгоритм Диффи-Хеллмана на эллиптических кривых для вычисления общего секрета, шифрует остальную часть рукопожатия под выведенным ключом трафика и отправляет сертификат, CertificateVerify и Finished в одном проходе. Поскольку обе стороны используют свежие эфемерные ключи, компрометация долгосрочного ключа подписи сервера позднее не позволит расшифровать прошлые сессии — это и есть Perfect Forward Secrecy. Хеш транскрипта в каждом сообщении Finished обнаруживает любые изменения байтов рукопожатия, включая попытки понижения версии middlebox’ами.
встречается в152
- Почему GraphQL получает N+1junior
- Механика DataLoader: батчинг на границе тикаmiddle
- Контракты batch-функции: порядок, формы, ошибкиmiddle
- Federation и lookahead: батчинг за пределами DataLoadermiddle
- Защита сложности запросов: depth, cost, persisted queriesmiddle
- Senior GraphQL API: scheduling-контракт, изоляция арендаторов, наблюдаемостьsenior
- Зачем идемпотентность: безопасные retryjunior
- Серверный state machine: четыре состояния idempotency keymiddle
- Outbox и inbox: effectively-once через dual-write границуmiddle
- Конкурентность и архитектура кеша для идемпотентности на масштабеsenior
- Наблюдаемость, production-инциденты и дизайн для глобального масштабаsenior
- Event loop: один поток, три очередиjunior
- Задачи, микрозадачи и scheduler.yield()middle
- Голодание микрозадач, длинные задачи и LoAFsenior
- Event loop Node.js: фазы, nextTick и задержка циклаsenior
- React, Vue и наблюдаемость INP в продакшенеsenior
- Render pipeline: шесть стадий от байтов до пикселейjunior
- Цена стадий и модель процесса рендерераmiddle
- Инвалидация, dirty-биты и containmiddle
- Слои композитора: продвижение, перекрытие и память GPUmiddle
- Флейм-стрип DevTools и жизненный цикл кадраmiddle
- Layout thrash: форсированная синхронная компоновкаsenior
- BeginMainFrame, анимации на потоке compositor и память GPUsenior
- Observability в проде: LoAF, INP и полная поверхность атакиsenior
- Что такое V8 и почему производительность различается в 100 разjunior
- Четырёхуровневый JIT-конвейер V8 и профилированная тиеризацияmiddle
- Hidden classes, деревья переходов и расположение в памятиmiddle
- Inline caches, состояния IC и деоптимизацияmiddle
- Orinoco GC: параллельный scavenger, конкурентная разметка и барьеры записиmiddle
- Спекулятивный движок TurboFan и ловушка deopt-loopsenior
- V8 в production: Isolates, сжатие указателей и реальные аварииsenior
- Жизненный цикл service worker и стратегии кешированияmiddle
- Граничные случаи service worker: version skew, долговременность и ловушка навигацииsenior
- Что делает реконсилер: render vs commitjunior
- Объект fiber и дерево с двойной буферизациейmiddle
- Чистота фазы render и подшаги фазы commitmiddle
- Реконсиляция: эвристики диффа и ловушка ключейmiddle
- Приоритетные lanes, time-slicing и useTransitionmiddle
- Bailout, мемоизация и tearingsenior
- React Profiler, компилятор и продакшн-наблюдаемостьsenior
- Стратегии рендеринга: SSG, SSR, ISR, streaming и гидратацияjunior
- SSG, SSR, ISR, streaming и RSC — как работает каждая стратегияmiddle
- Цена гидратации: selective, progressive, острова, resumabilitymiddle
- Hydration mismatch: причины, обнаружение и правило детерминизмаsenior
- RSC, стратегия на маршрут и production-наблюдаемостьsenior
- Core Web Vitals: что измеряют LCP, INP и CLSjunior
- CLS: почему происходят сдвиги лейаута и как их остановитьmiddle
- Трейдоффы метрик, RUM-атрибуция и цикл CI+полеsenior
- Общая картина: от URL до LCP до INP как эстафетаjunior
- Восемь слоёв трассировки: от service worker до второй навигацииmiddle
- Пять канонических поломок: где производство стабильно ломаетсяsenior
- Метод трёх треков: чтение трасс и построение системы мониторингаsenior
- Что такое cache stampede и почему он делает всё хужеjunior
- Лок и single-flight: ограничение параллельных rebuildmiddle
- XFetch: вероятностное раннее истечение без координацииmiddle
- Stale-while-revalidate и CDN request coalescingmiddle
- Детектирование stampede и дизайн TTL для продакшенаmiddle
- Метастабильный сбой, fencing-токены и production-постмортемыsenior
- Что такое отношение: таблицы, строки, ключи и ограниченияjunior
- Ограничения, ключи и типы данных Postgresmiddle
- Нормальные формы, денормализация и почему схемы «прилипают»middle
- JSONB, массивы и когда side table побеждаетmiddle
- Heap-хранилище, TOAST и выравнивание колонокsenior
- Целостность схемы: deferral, версионирование и сбои в продакшнеsenior
- Реляционная модель vs документные, wide-column, граф и key-valuesenior
- Index-only scan, Visibility Map и INCLUDEsenior
- Типичные сбои в продакшне и аудит индексовsenior
- pg_statistic, ANALYZE и производственная наблюдаемостьmiddle
- Производственные режимы отказа и стабильность плановsenior
- MVCC: как Postgres раздаёт согласованные снимкиjunior
- Заголовок tuple и механика снимковmiddle
- HOT-обновления и уровни изоляцииmiddle
- VACUUM, bloat и autovacuummiddle
- CLOG, XID wraparound и MultiXactsenior
- SSI и production-тюнинг autovacuumsenior
- Реальные провалы MVCC, deployment-паттерны и распределённые снимкиsenior
- Connection pool: зачем амортизировать стоимость backend Postgresjunior
- Режимы PgBouncer: session, transaction и statementmiddle
- Размер пула: формула (ядра × 2) + шпинделей и двухуровневый стекmiddle
- Исчерпание пула и idle-in-transaction: сценарий отказа в 3 ночиmiddle
- Миграция на transaction mode: план развёртывания и prepared statements в PgBouncer 1.21middle
- Процессная модель Postgres и почему увеличение max_connections снижает производительностьsenior
- Ландшафт пулеров 2026, serverless connection storms и полная таксономия отказовsenior
- Что такое миграция схемы и почему она заменяет ad-hoc DDLjunior
- ADD COLUMN: мгновенно в PG 11+ против перезаписи в старом Postgresjunior
- Режим отказа очереди блокировок: почему мгновенный DDL может заморозить базуmiddle
- Безопасные DDL-паттерны: NOT VALID, CONCURRENTLY и исправления небезопасных операцийmiddle
- Expand-contract: нулевой простой для ломающих изменений схемыmiddle
- Advisory-блокировки, инструменты миграций и координация деплояsenior
- Таксономия сбоев миграций и дисциплина продакшнаsenior
- Зачем нужно шардирование: потолок одного Postgresjunior
- Выбор ключа шарда: стратегии hash, range, list и directorymiddle
- Партиционирование против шардирования: одно слово, два разных понятияmiddle
- Ко-локация и Citus: инвариант, делающий шардирование пригодным к использованиюmiddle
- Режим отказа hot shard: обнаружение, изоляция и долгосрочная политикаmiddle
- Schema-based шардирование и альтернативы мультиарендностиsenior
- Онлайн-решардинг, 2PC и операционная стоимость шардированияsenior
- Семь актов: от CREATE TABLE до Citusjunior
- Акты 1–3 в глубину: схема, индексы и статистика планировщикаmiddle
- Акты 4–6 в глубину: MVCC bloat, connection pooling и безопасные миграцииmiddle
- Акт 7 в глубину: шардинг, co-location и семиуровневый каскад трейдоффовmiddle
- Наблюдаемость, антипаттерны и производственный триажsenior
- Роли Raft, term и почему majority-кворум предотвращает split brainjunior
- Как Raft реплицирует log entry и решает, что его безопасно коммититьmiddle
- Выборы лидера в Raft: таймауты, правила голосования и четыре свойства безопасностиmiddle
- Raft в реальном мире: partition, медленный диск и клиентская маршрутизацияmiddle
- Расширения Raft: pre-vote, learner, snapshot и линеаризуемые чтенияsenior
- Raft в production: membership change, Multi-Raft и observabilitysenior
- Где происходит data fetching — и почему это решает LCPjunior
- Fetch waterfall''''ы — диагностика и лечение через Promise.allmiddle
- React Server Components и Suspense streamingmiddle
- Клиентский кэш: TanStack Query, SWR и stale-while-revalidatemiddle
- LCP, prefetch и race conditions в интерактивном fetchingmiddle
- Senior internals: RSC payload, слои кэша и production паденияsenior
- Что такое три сигнала: метрики, логи, трейсыjunior
- Зачем нужны структурные логи: дневник против таблицыjunior
- Схема продакшн-лога: поля, которые несёт каждая строкаmiddle
- PII-редакция и log injectionsenior
- OTel Logs Data Model и audit-логи как подсистемаsenior
- SLI, SLO и error budget: надёжность в числахjunior
- Error budget policy, latency SLO и составные journeysmiddle
- Продакшн-отказы SLO, самонаблюдаемость, безопасность и общая картинаsenior
- Петля инцидента: от пейджера до постмортема до предотвращенияmiddle
- Cache lines и false sharing: когда параллелизм замедляет кодmiddle
- SIMD и data layout: AoS vs SoA и разница в 4–8xmiddle
- Cache-oblivious алгоритмы, PGO и production failuressenior
- GC в production: наблюдаемость, безопасность, edge cases и управление флотомsenior
- Batching: амортизируй фиксированную цену каждой операцииjunior
- Окно батчинга: размер и время ожиданияmiddle
- Batching в Kafka и Postgresmiddle
- io_uring и наблюдаемость пакетированияmiddle
- От Nagle до io_uring: эволюция пакетированияmiddle
- Backpressure, изоляция сбоев и безопасность батчей в продакшенеsenior
- CI enforcement и RUM: делаем бюджеты рабочимиmiddle
- V8 JIT-пайплайн, HTTP-приоритеты и безопасность bundlesenior
- Цикл performance: дисциплина, а не проектjunior
- Классификация и исправление: сопоставление family bottleneck с методамиmiddle
- Observability-стек и CI gates: ловить регрессии до выпускаmiddle
- От инцидента к enforcement: SLO burn до верифицированного исправления за 35 минутmiddle
- Культура, экономика и масштаб performancesenior
- At-most-once, at-least-once, exactly-once: три контракта доставкиjunior
- Три ножки сбоя — где реально происходят дубликаты и потериmiddle
- Consumer-side dedup: самый дешёвый путь к exactly-once processingmiddle
- Kafka exactly-once semantics: idempotent producer и транзакцииmiddle
- SQS visibility timeout, DLQ и outbox patternmiddle
- Exactly-once в production: impossibility-доказательство, гибридные паттерны и реальные инцидентыsenior
- Что такое OAuth и почему пароли — не ответjunior
- Authorization code flow с PKCEmiddle
- Валидация ID-токена и управление JWKS-кешемmiddle
- Ротация refresh-токенов и scope-based least privilegemiddle
- Sender-constrained токены: DPoP и mTLSsenior
- OAuth в production: audience атаки, observability и реальные провалыsenior