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
| Язык | НЕ для секретов | Криптостойкий |
| Python | random | secrets |
| JavaScript | Math.random | crypto.getRandomValues |
| Java | Random | SecureRandom |
Как работает под капотом: энтропия
CSPRNG черпает энтропию из непредсказуемых источников ОС (тайминги устройств, шум железа) и расширяет её криптостойким алгоритмом так, что по выданным числам нельзя восстановить состояние и предсказать следующие. Обычный PRNG, наоборот, полностью определяется начальным seed: узнал seed (или несколько выходов) — знаешь всю последовательность. В этом и разница «предсказуемо/непредсказуемо».
Наглядный пример риска — «генератор токенов из текущего времени». Кажется, что время уникально и потому годится для одноразовой ссылки сброса пароля. Но время предсказуемо: атакующий примерно знает, когда вы отправили письмо, и может перебрать узкий диапазон значений, угадав токен и перехватив сброс чужого пароля. То же с обычным PRNG, инициализированным временем запуска: пространство возможных seed мало, и его реально перебрать. Криптостойкий источник, напротив, выдаёт значения, которые нельзя сузить до перебираемого множества.
Практический вывод: длина и источник важны вместе. Токен должен быть достаточно длинным (например, 32 случайных байта), чтобы его нельзя было угадать перебором, и обязательно из CSPRNG, чтобы случайность была настоящей, а не воспроизводимой. Хорошая привычка — не собирать токены вручную из символов через обычный рандом, а просить у криптостойкого API готовые случайные байты и кодировать их в строку. Многие платформы уже предоставляют готовые помощники для генерации токенов и идентификаторов — разумно пользоваться именно ими, а не изобретать схему самому.
Частые ошибки
- Отключить verify сертификата. Открывает MITM; чините доверие, а не проверку.
- HTTP без редиректа на HTTPS. Часть трафика остаётся открытой.
Math.randomдля токенов/соли. Предсказуемо; берите CSPRNG.- Свой «генератор» из времени. Время угадывается — токен тоже.
- Слишком короткий случайный токен. Даже из CSPRNG: коротое значение перебирается; берите достаточную длину (например, 32 байта).
- Устаревшие версии и шифры TLS. Старые протоколы имеют известные слабости; задавайте минимальную версию явно.
Итоги
- Весь трафик — по TLS; HSTS и редирект закрывают откат на HTTP.
- Никогда не отключайте проверку сертификата — это убивает аутентификацию канала.
- Для секретов (токены, соли, ключи) используйте криптостойкий рандом, не обычный PRNG.