TLS и криптостойкий рандом

Два практичных кирпича: защищённый канал и правильный источник случайности.

TLS шифрует и аутентифицирует сетевое соединение. CSPRNG — криптостойкий генератор случайных чисел, пригодный для секретов.

TLS: данные в пути

Без шифрования канала любой на пути (Wi-Fi, провайдер, прокси) читает и меняет трафик: ворует пароли и токены, подменяет ответы. TLS решает три задачи сразу — конфиденциальность (шифрование), целостность (защита от подмены) и аутентификацию сервера (вы говорите именно с тем доменом). Практическое правило: весь трафик по HTTPS, обычный HTTP редиректит на HTTPS, заголовок HSTS запрещает откат.

Распространённое заблуждение — «шифровать надо только страницу логина, остальное не секрет». На деле перехватчику часто и не нужен ваш пароль: достаточно украсть cookie сессии из любого незашифрованного запроса, и он войдёт под вами без пароля вовсе. К тому же открытый HTTP позволяет атакующему не только читать, но и дописывать в страницу свой код по пути, превращая безобидную статью в инструмент атаки на посетителя. Поэтому современный подход — шифровать вообще весь трафик, а не «важные» его части.

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

# Безопасные дефолты транспорта
server:
  https_only: true
  hsts: "max-age=63072000; includeSubDomains"   # только HTTPS, надолго
  min_tls_version: "1.2"                          # отключить устаревшие протоколы

Не отключайте проверку сертификата

Самая опасная «починка» TLS — выключить проверку сертификата, чтобы «заработало» с самоподписанным сертификатом. Это убивает аутентификацию: теперь к вам может подключиться кто угодно под видом нужного сервера (man-in-the-middle).

Понять, почему это так разрушительно, помогает аналогия с паспортом. Шифрование без проверки сертификата — как разговор по защищённому каналу с собеседником, чью личность вы отказались проверять: связь не подслушать, но вы не знаете, с кем говорите. Сертификат и есть тот «паспорт», который доказывает, что на другом конце действительно нужный домен, а не подставной узел. Отключив проверку, вы сохраняете шифрование, но выбрасываете аутентификацию — и злоумышленнику достаточно встать посередине, предъявить собственный сертификат, и весь «зашифрованный» трафик пойдёт через него в открытом для него виде.

Типичная причина такого отключения — самоподписанный сертификат в разработке, на который ругается клиент. Правильное решение — не глушить проверку, а научить клиента доверять нужному источнику: добавить корневой сертификат вашего внутреннего удостоверяющего центра в список доверенных. Тогда проверка остаётся включённой и в dev, и в prod, а отличается лишь набор доверенных корней. Отключение verify в коде особенно коварно тем, что легко «переезжает» из временной отладки прямиком в продакшен.

// Уязвимо: отключили проверку сертификата ради удобства
client = HttpClient(verify=False)   // принимает ЛЮБОЙ сертификат -> MITM

// Безопасно: проверка включена; для своих сервисов доверяем свой CA
client = HttpClient(verify=True)
// в dev — добавить корневой сертификат в доверенные, а не отключать проверку

Криптостойкий рандом

Токены сессий, сбросы пароля, соли, nonce, CSRF-токены, ключи — всё это должно быть непредсказуемым. Обычные генераторы (rand(), Math.random(), Random) предназначены для статистики и игр: они быстрые, но предсказуемые — зная несколько значений, можно угадать следующие. Для секретов нужен CSPRNG. Разделение простое: если от случайности зависит безопасность (кто-то не должен угадать значение), берите криптостойкий источник; если случайность нужна лишь для удобства или статистики (перемешать список, выбрать цвет, сгенерировать тестовые данные) — годится обычный генератор. Ошибка почти всегда в одну сторону: обычный генератор используют там, где на кону была безопасность.

// Уязвимо: предсказуемый генератор для токена сброса пароля
token = Math.random().toString(36)     // угадывается -> чужой сброс пароля

// Безопасно: криптостойкий источник
token = secureRandomBytes(32).toHex()  // crypto.randomBytes / secrets / SecureRandom
ЯзыкНЕ для секретовКриптостойкий
Pythonrandomsecrets
JavaScriptMath.randomcrypto.getRandomValues
JavaRandomSecureRandom

Как работает под капотом: энтропия

CSPRNG черпает энтропию из непредсказуемых источников ОС (тайминги устройств, шум железа) и расширяет её криптостойким алгоритмом так, что по выданным числам нельзя восстановить состояние и предсказать следующие. Обычный PRNG, наоборот, полностью определяется начальным seed: узнал seed (или несколько выходов) — знаешь всю последовательность. В этом и разница «предсказуемо/непредсказуемо».

Наглядный пример риска — «генератор токенов из текущего времени». Кажется, что время уникально и потому годится для одноразовой ссылки сброса пароля. Но время предсказуемо: атакующий примерно знает, когда вы отправили письмо, и может перебрать узкий диапазон значений, угадав токен и перехватив сброс чужого пароля. То же с обычным PRNG, инициализированным временем запуска: пространство возможных seed мало, и его реально перебрать. Криптостойкий источник, напротив, выдаёт значения, которые нельзя сузить до перебираемого множества.

Практический вывод: длина и источник важны вместе. Токен должен быть достаточно длинным (например, 32 случайных байта), чтобы его нельзя было угадать перебором, и обязательно из CSPRNG, чтобы случайность была настоящей, а не воспроизводимой. Хорошая привычка — не собирать токены вручную из символов через обычный рандом, а просить у криптостойкого API готовые случайные байты и кодировать их в строку. Многие платформы уже предоставляют готовые помощники для генерации токенов и идентификаторов — разумно пользоваться именно ими, а не изобретать схему самому.

Частые ошибки

  • Отключить verify сертификата. Открывает MITM; чините доверие, а не проверку.
  • HTTP без редиректа на HTTPS. Часть трафика остаётся открытой.
  • Math.random для токенов/соли. Предсказуемо; берите CSPRNG.
  • Свой «генератор» из времени. Время угадывается — токен тоже.
  • Слишком короткий случайный токен. Даже из CSPRNG: коротое значение перебирается; берите достаточную длину (например, 32 байта).
  • Устаревшие версии и шифры TLS. Старые протоколы имеют известные слабости; задавайте минимальную версию явно.

Итоги

  • Весь трафик — по TLS; HSTS и редирект закрывают откат на HTTP.
  • Никогда не отключайте проверку сертификата — это убивает аутентификацию канала.
  • Для секретов (токены, соли, ключи) используйте криптостойкий рандом, не обычный PRNG.
Проверьте себя
1. Почему опасно отключать проверку TLS-сертификата клиента?
AСоединение станет медленнее
BБез проверки клиент примет любой сертификат, и атакующий может выдать себя за сервер (man-in-the-middle)
CПерестанет работать сжатие
DЭто нарушает кодировку
2. Почему Math.random или обычный Random нельзя использовать для токенов и солей?
AОни слишком медленные
BОни предсказуемы: зная несколько выходов или seed, можно угадать остальные; для секретов нужен криптостойкий генератор
CОни не работают с большими числами
DОни выдают только целые числа
3. Какие задачи решает TLS для трафика?
AТолько сжатие данных
BКонфиденциальность, целостность и аутентификацию сервера одновременно
CТолько ускорение соединения
DТолько хранение данных на диске