0-RTT
0-RTT (Zero Round-Trip Time) — фича TLS 1.3 и QUIC, которая позволяет клиенту отправить прикладные данные сразу в первом пакете, не дожидаясь ответа сервера. Экономия — целый RTT (обычно 50-300 мс).
Как это возможно
Работает только для повторных подключений к серверу, с которым уже была сессия:
- Первое подключение: обычный TLS 1.3 handshake (1-RTT). В конце сервер отдаёт Session Ticket / PSK — «в следующий раз можешь использовать это».
- Клиент кэширует ticket.
- Следующее подключение: клиент шлёт ClientHello + прикладные данные (Early Data) уже зашифрованные ключом из ticket в одном пакете.
- Сервер видит ticket, извлекает ключ, дешифрует Early Data, начинает обрабатывать сразу.
Обычный 1-RTT:
Client → SYN
← SYN/ACK
Client → ACK + ClientHello
← ServerHello + Cert + Finished
Client → Finished + GET /
← 200 OK (2 RTT до данных)
0-RTT:
Client → ClientHello + Early Data (GET /)
← ServerHello + Finished + 200 OK (1 RTT до данных)
QUIC 0-RTT:
Client → Initial + Early Data (GET /) ← сразу первый пакет
← Handshake + Finished + 200 OK (0 RTT!)
Compromise: replay-атаки
Главная проблема: атакующий может записать 0-RTT пакет и переиграть его позже. Ключ ticket-а тот же, сервер примет. Значит если 0-RTT запрос был POST /transfer money — атака переведёт деньги дважды.
Что делать
- Разрешать 0-RTT только для идемпотентных операций (GET, HEAD, OPTIONS).
- Anti-replay кэш на сервере. Сервер помнит принятые Early Data (по хешу или nonce) и отвергает повторы. Работает в рамках одного сервера, при нескольких — сложнее.
- Client-side ограничение в HTTP/3: клиент шлёт в 0-RTT только «safe» методы.
Как реализовано
- Cloudflare — 0-RTT только для GET, есть anti-replay.
- Fastly, Google, Amazon — аналогично.
- nginx:
ssl_early_data on;+ приложение должно проверить$ssl_early_data.
Криптографические тонкости
- 0-RTT ключ выведен из PSK/ticket. Не имеет forward secrecy (в отличие от полного handshake), но только для Early Data — весь остальной трафик после handshake защищён обычным ephemeral ключом.
- Сервер может отклонить 0-RTT (например если ticket устарел) — клиент шлёт данные повторно через обычный 1-RTT.
Числа
| Handshake | Round-trips до данных |
|---|---|
| TLS 1.2 | 2 RTT |
| TLS 1.3 | 1 RTT |
| TLS 1.3 resumption | 1 RTT |
| TLS 1.3 0-RTT | 0 RTT (только с ticket) |
| QUIC first connection | 1 RTT (совмещён TCP+TLS) |
| QUIC resumption 0-RTT | 0 RTT |