Основы безопасности: угрозы, шифрование, аутентификация
В прошлых статьях трека мы собрали работающий интернет: научили компьютеры находить друг друга и обмениваться пакетами (Как компьютеры общаются) и проследили путь запроса от браузера до сервера и обратно (Как работает веб). Но всё это мы строили в наивном допущении, что по проводам ходят только те, кого мы ждём, и говорят только правду. В реальном мире это не так. Как только два компьютера начинают общаться через сеть, между ними может встать третий — который слушает чужой трафик, подменяет пакеты, выдаёт себя за сервер, подсовывает вредоносный ввод. Безопасность — это дисциплина о том, как заставить систему делать ровно то, что задумано, даже когда против неё работает умный противник.
Ключевое слово здесь — противник. Этим безопасность отличается от надёжности. Надёжность защищает от случая: диск может умереть, пакет — потеряться, но природа не злонамеренна, она просто бросает кости, и мы закладываемся на средний случай. Атакующий кости не бросает — он целенаправленно ищет худший случай и бьёт именно туда, где вы не подстраховались. Поэтому в безопасности нельзя рассуждать «вероятность мала»: если дыра есть, её найдут и используют. Эта статья — обзорная карта: модель угроз, три кита криптографии, устройство HTTPS, разница аутентификации и авторизации, типовые атаки и инженерные принципы. Отдельного глубокого трека по безопасности на портале пока нет, поэтому за деталями я по ходу отправляю в первоисточники — OWASP, NIST и книгу Росса Андерсона «Security Engineering».
Безопасность — свойство, а не фича
Первая и главная ошибка — думать о безопасности как о фиче, которую «прикрутят в конце».
Нельзя дописать модуль security.py и объявить систему защищённой. Безопасность — это
сквозное свойство, которое должно держаться на каждом слое той башни абстракций, что мы
строили весь трек: на железе (утечки через тайминги кэша), в ОС (изоляция процессов), в сети
(шифрование канала), в приложении (проверка ввода), в людях (не кликать по фишингу). Провал
на любом слое обнуляет старания на остальных. Замок на бронированной двери бесполезен, если
ключ лежит под ковриком.
Отсюда два рабочих понятия. Поверхность атаки (attack surface) — совокупность всех точек, через которые противник может воздействовать на систему: каждый открытый порт, каждое поле ввода, каждая зависимость, каждый сотрудник. Чем она меньше, тем лучше. Модель угроз (threat model) — честный ответ на три вопроса: что мы защищаем, от кого и какой ценой атака этому «кому» обойдётся. Без модели угроз разговор о безопасности превращается в суеверие: шифровать всё подряд бессмысленно, если ключ хранится рядом в открытом виде.
Триада CIA: что именно мы защищаем
Что вообще значит «защитить данные»? Классический ответ — три свойства, триада CIA:
- Confidentiality (конфиденциальность) — данные видит только тот, кому положено. Нарушение: подслушивание трафика, утечка базы паролей, чтение чужих сообщений.
- Integrity (целостность) — данные не подменены незаметно. Нарушение: злоумышленник меняет сумму перевода в пути, подсовывает поддельное обновление, правит запись в БД.
- Availability (доступность) — система доступна, когда нужна. Нарушение: DDoS-атака кладёт сайт, вымогатель шифрует ваши же файлы (ransomware).
Триаду часто дополняют аутентичностью (данные действительно от того, кто заявлен) и неотказуемостью (автор не может потом отпереться от своего действия — это даёт цифровая подпись). Разные системы расставляют приоритеты по-разному: банку критична целостность, мессенджеру — конфиденциальность, бирже — доступность. Модель угроз начинается с вопроса «какая из букв CIA для нас дороже всего».
Думать как атакующий
Инженерное правило номер один: не доверяй вводу. Любые данные, пришедшие из-за границы доверия — из сети, от пользователя, из файла, из чужого сервиса, — потенциально враждебны, пока не доказано обратное. Внутри программы вы контролируете значения; на входе — нет. Граница доверия (trust boundary) — это линия, пересекая которую данные обязаны пройти проверку. Подавляющее большинство реальных дыр — это ровно момент, когда недоверенный ввод приняли за доверенный: SQL-инъекция, XSS, переполнение буфера — всё это варианты одной ошибки.
Полезный чек-лист угроз — мнемоника STRIDE от Microsoft: Spoofing (подмена личности), Tampering (подмена данных), Repudiation (отрицание действия), Information disclosure (утечка), Denial of service (отказ в обслуживании), Elevation of privilege (повышение прав). Прогоняя каждый компонент системы через эти шесть букв, вы систематически, а не наугад, находите, где тонко.
Криптография: математика доверия
Криптография — набор математических инструментов, которые дают триаде CIA техническую опору. Три кита: хеш-функции (целостность), симметричное шифрование (конфиденциальность дёшево) и асимметричное шифрование (конфиденциальность + подпись, решает передачу ключа). Их математический фундамент — теория чисел и модульная арифметика; если хочется понять, почему эти функции трудно обратить, это в глубоком треке (Математика: обзор).
Хеш-функции: односторонняя дверь
Криптографическая хеш-функция берёт вход любого размера и выдаёт короткую строку фиксированной длины — «отпечаток» (например, SHA-256 всегда даёт 256 бит). Свойства, которые делают её криптографической:
- Детерминированность. Один вход → всегда один и тот же хеш.
- Быстрота в одну сторону, невозможность — в другую. По входу хеш считается мгновенно, а восстановить вход по хешу (прообраз) — практически нельзя. Это «односторонняя дверь».
- Лавинный эффект. Изменение одного бита входа меняет примерно половину битов хеша — похожие входы дают совершенно непохожие отпечатки.
- Стойкость к коллизиям. Найти два разных входа с одинаковым хешем вычислительно неподъёмно.
Хеши — это про целостность, а не про секретность: ключа нет, «расшифровать» хеш нельзя by design. Их применяют для проверки, что файл скачался без искажений (контрольная сумма), для дедупликации, для «отпечатков» сертификатов, для хранения паролей (об этом ниже). Важная практическая деталь: старые MD5 и SHA-1 сломаны — для них умеют строить коллизии, — и в безопасности их использовать нельзя. Актуальны SHA-256/SHA-512 (семейство SHA-2) и SHA-3.
Симметричное шифрование: один общий ключ
Симметричный шифр (стандарт — AES) использует один секретный ключ и для зашифровки, и для расшифровки. Он очень быстрый (аппаратное ускорение AES шифрует гигабайты в секунду), поэтому именно им шифруют основной объём данных. Но есть фундаментальная проблема: как передать ключ собеседнику по открытой сети так, чтобы его не перехватили? Если бы у нас уже был защищённый канал для передачи ключа, мы бы по нему передавали и сами данные. Это классическая «проблема распределения ключей», и до 1970-х она казалась неразрешимой.
Асимметричное шифрование: пара ключей
Прорыв — асимметричная (с открытым ключом) криптография: RSA, а сегодня чаще эллиптические кривые ECC. У каждого участника не один ключ, а пара: открытый и закрытый, математически связанные так, что зашифрованное одним расшифровывается только другим, но вычислить закрытый ключ по открытому невозможно за разумное время (стойкость опирается на трудность разложения на множители или дискретного логарифма). Открытый ключ можно смело публиковать хоть на визитке: им можно только зашифровать сообщение для вас — расшифровать сможете лишь вы своим закрытым. Проблема передачи ключа исчезает.
Плата за это — скорость: асимметрия в сотни раз медленнее и годится лишь для маленьких данных. Поэтому на практике их комбинируют (гибридная схема): асимметрией безопасно передают случайный симметричный ключ, а дальше весь трафик шифруют быстрым AES. Именно так работает HTTPS.
Цифровая подпись: аутентичность и неотказуемость
Пара ключей работает и «наоборот». Если зашифровать хеш сообщения закрытым ключом, получится цифровая подпись: любой, у кого есть ваш открытый ключ, проверит, что подпись могли поставить только вы (аутентичность) и что сообщение не меняли (целостность), а вы не сможете от неё отпереться (неотказуемость). Так подписывают обновления ПО, сертификаты и транзакции. Симметричный аналог целостности — MAC / HMAC: короткий тег на общем секретном ключе, доказывающий, что сообщение не подменили. Разница: HMAC проверяет тот, кто знает секрет; подпись — кто угодно, зная открытый ключ.
HTTPS изнутри: рукопожатие TLS
Теперь соберём куски. Зелёный замок в браузере — это протокол TLS (раньше назывался SSL), который превращает открытый HTTP в защищённый HTTPS. TLS решает разом все три задачи: шифрует канал (конфиденциальность), проверяет целостность и удостоверяет, что сервер действительно тот, за кого себя выдаёт (аутентичность). Упрощённое рукопожатие:
Ключевой шаг — проверка сертификата. Как браузер узнаёт, что открытый ключ принадлежит
именно bank.com, а не подсунут злоумышленником? Сертификат — это открытый ключ сервера плюс
доменное имя, подписанные доверенным удостоверяющим центром (CA). В вашей ОС и браузере
зашит список корневых CA, которым доверяют по умолчанию. Получается цепочка доверия:
браузер доверяет корневому CA → CA своей подписью ручается за сертификат сервера → значит,
ключу сервера можно верить. Без этой проверки шифрование бесполезно: атакующий-«посредник»
(man-in-the-middle) просто подставил бы свой ключ и читал бы весь «зашифрованный» трафик.
Именно проверка сертификата превращает пассивную защиту от подслушивания в активную защиту от
подмены. Слабые места цепочки — скомпрометированный CA и пользователь, кликнувший «всё равно
продолжить» на предупреждении о невалидном сертификате. Актуальная версия — TLS 1.3
(RFC 8446); настоящее рукопожатие сложнее схемы выше,
но идея та же.
Аутентификация и авторизация: кто ты и что тебе можно
Два слова, которые постоянно путают, — а это разные вопросы:
- Аутентификация (authentication, «кто ты?») — доказательство личности. Ввод пароля, отпечаток, одноразовый код.
- Авторизация (authorization, «что тебе можно?») — проверка прав уже опознанного субъекта. Обычный пользователь вошёл (аутентифицирован), но удалять чужие записи ему нельзя (не авторизован).
Сначала первое, потом второе. Классическая уязвимость — когда систему аутентифицировали, а
авторизацию забыли: залогиненный пользователь меняет id=123 на id=124 в URL и читает чужой
заказ (это называется IDOR / broken access control — годами держится в топе OWASP).
Аутентификация опирается на три типа факторов: знание (что-то, что вы знаете — пароль), владение (что-то, что у вас есть — телефон, аппаратный ключ) и свойство (что-то, чем вы являетесь — отпечаток, лицо). Один фактор ненадёжен: пароль подсмотрят, телефон украдут. Многофакторная аутентификация (MFA) требует минимум двух факторов разных типов — и это, пожалуй, самая дешёвая и эффективная защита учётной записи из существующих.
Как правильно хранить пароли
Здесь совершают самые дорогие ошибки. Абсолютные правила:
- Никогда не храните пароль в открытом виде. Утечка базы = мгновенная катастрофа.
- Никогда не храните простой хеш пароля (
SHA256(password)). Хеш быстрый — атакующий переберёт миллиарды вариантов в секунду по «радужным таблицам» заранее посчитанных хешей. - Храните пароль как соль + медленная функция вывода ключа (KDF): bcrypt, scrypt или современный Argon2. Соль — случайная строка, уникальная для каждого пользователя; она добавляется к паролю перед хешированием, так что одинаковые пароли дают разные хеши и заранее посчитанные таблицы бесполезны. Медленность — намеренная: KDF настроен так, чтобы одна проверка занимала десятки миллисекунд. Для честного входа это незаметно, а перебор замедляется в миллионы раз.
сам пароль НЕ хранится")] end subgraph login["Вход"] P2[Пользователь вводит пароль] --> L1[Достать соль и hash из БД по логину] L1 --> H2["hash2 = Argon2(введённое + соль)"] H2 --> C{"hash2 == hash?
сравнение за постоянное время"} C -->|да| OK[Пустить и выдать сессию] C -->|нет| NO[Отказать] end
# Идиоматичное хранение пароля на Python через argon2-cffi.
# Библиотека сама генерирует соль, зашивает её в строку хеша
# и подбирает параметры «медленности» — своё изобретать не нужно.
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
ph = PasswordHasher() # разумные параметры по умолчанию
def register(password: str) -> str:
# Возвращаем строку вида $argon2id$v=19$m=...,t=...,p=...$<соль>$<хеш>.
# Именно её кладём в БД вместо пароля.
return ph.hash(password)
def check(stored_hash: str, password: str) -> bool:
try:
# verify сравнивает за постоянное время — не «побайтово с ранним выходом»,
# иначе по времени ответа можно подбирать хеш (тайминг-атака).
return ph.verify(stored_hash, password)
except VerifyMismatchError:
return False
Обратите внимание на комментарий про сравнение за постоянное время. Наивное == для
секретов опасно: оно возвращает False на первом же несовпавшем байте, и по времени ответа
атакующий восстанавливает секрет побайтово. Это первый пример того, как криптография,
идеальная на бумаге, протекает через реализацию.
Сессии и токены
Проверять пароль на каждый запрос неудобно и небезопасно. После успешного входа сервер выдаёт
сессионный идентификатор (в cookie) или токен (например, JWT), который клиент
прикладывает к следующим запросам как «пропуск». Такой токен — это bearer-токен: право
даётся любому, кто его предъявил, поэтому украденный токен = украденная сессия. Отсюда защита:
отдавать cookie только по HTTPS (флаг Secure), запрещать доступ к ней из JavaScript
(HttpOnly), ограничивать межсайтовую отправку (SameSite) и обязательно ставить срок жизни.
Отдельная большая тема — делегированная аутентификация: кнопка «Войти через Google». Протоколы OAuth 2.0 и OpenID Connect позволяют стороннему приложению получить подтверждение вашей личности от Google, не узнав вашего пароля от Google, — приложению выдаётся ограниченный токен доступа. Это не только удобнее, но и безопаснее: вы не рассыпаете пароль по десяткам сайтов.
Типовые атаки на веб и защита от них
Большинство реальных взломов — это несколько повторяющихся из года в год классов ошибок (канонический список — OWASP Top 10). Интуиция и защита по каждому:
- SQL-инъекция. Ввод пользователя склеивают в текст SQL-запроса, и
'; DROP TABLE users;--выполняется как команда. Защита: параметризованные запросы — данные передаются отдельно от кода запроса и командой стать не могут. Подробнее про запросы — в треке Базы данных. - XSS (межсайтовый скриптинг). Атакующий внедряет свой JavaScript в страницу (через
комментарий, имя, параметр), и он выполняется в браузере жертвы, крадя cookie. Защита:
экранирование любого вывода,
Content-Security-Policy,HttpOnlyна cookie. - CSRF (подделка межсайтового запроса). Чужой сайт заставляет ваш браузер отправить запрос
на сервис, где вы залогинены, используя ваши cookie. Защита: CSRF-токены,
SameSite. - Man-in-the-middle. Посредник в канале читает/подменяет трафик. Защита: TLS с проверкой сертификата, HSTS.
- Replay (повтор). Атакующий записывает валидный запрос и отправляет повторно. Защита: одноразовые числа (nonce), метки времени, короткоживущие токены.
- Brute force / credential stuffing. Перебор паролей или подстановка утёкших пар «логин-пароль». Защита: rate limiting, блокировки, MFA, проверка по базам утечек.
- DDoS. Лавина запросов кладёт сервис (удар по доступности). Защита: фильтрация, CDN, автомасштабирование.
- Фишинг и социальная инженерия. Взлом не системы, а человека: поддельное письмо выманивает пароль. Защита: обучение, MFA, менеджеры паролей (не введут пароль на левом домене).
- Атака на цепочку поставок. Вредонос попадает через скомпрометированную зависимость или сборку. Защита: фиксация версий, проверка подписей, аудит зависимостей.
Принципы безопасной инженерии
За частными атаками стоят несколько универсальных принципов. Они старше любого конкретного языка и переживут любую моду.
- Наименьшие привилегии (least privilege). Каждый компонент и пользователь получает ровно те права, что нужны для работы, и ни каплей больше. Взломанный сервис с минимальными правами наносит минимальный урон.
- Защита в глубину (defense in depth). Не одна идеальная стена, а несколько независимых рубежей: пробитый один слой не должен открывать доступ к данным.
- Отказ в безопасную сторону (fail secure). При сбое система закрывается, а не открывается. Ошибка проверки прав → «запретить», а не «на всякий случай пустить».
- Не изобретай свою криптографию. Используй проверенные библиотеки и стандарты. Своя «хитрая» схема почти наверняка содержит дыру, которую вы не видите, а эксперт увидит сразу.
- Никакой безопасности через неясность (принцип Керкгоффса). Стойкость должна держаться на секретности ключа, а не алгоритма. Секретный алгоритм рано или поздно раскроют; хороший алгоритм остаётся стойким, даже когда опубликован и изучен всем миром.
- Безопасно по умолчанию, минимальная поверхность атаки. Закрыто, пока явно не открыли; меньше кода, портов и зависимостей — меньше того, что можно взломать.
- Предполагай взлом (assume breach / zero trust). Стройте так, будто периметр уже пробит: шифруйте данные и внутри сети, проверяйте каждый запрос, логируйте и мониторьте, чтобы заметить вторжение и быстро отреагировать.
Где абстракция протекает
Криптография математически почти идеальна — и именно поэтому взламывают не её, а всё вокруг. «Атакующие не ломают шифр в лоб — они обходят его».
- Побочные каналы (side channels). Секрет утекает не через алгоритм, а через физику исполнения: время работы, энергопотребление, звук, поведение кэша. Мы уже видели тайминг-атаку на сравнение хешей. Аппаратные Spectre и Meltdown вытаскивали секреты через спекулятивное исполнение процессора — привет слоям «Как работает процессор» и «Многозадачность».
- Качество случайности. Вся криптография держится на непредсказуемых ключах. Слабый или предсказуемый генератор случайных чисел обнуляет самый сильный шифр — предсказуемый ключ подберут, не трогая алгоритм.
- Человеческий фактор — самое слабое звено. Ни один шифр не спасёт, если пользователь сам отдаст пароль по фишинговой ссылке или приклеит его на монитор. Поэтому в схеме «защиты в глубину» самый внешний рубеж — это люди.
- Безопасность — процесс, а не состояние. Нельзя один раз «сделать безопасно» и забыть. Новые уязвимости находят постоянно, зависимости устаревают, появляются новые классы атак. Безопасность требует обновлений, мониторинга и пересмотра модели угроз — вечно.
Типичные заблуждения
- «Мы маленькие, кому мы нужны». Большинство атак автоматические: боты сканируют весь интернет подряд и бьют по любой известной дыре, не разбирая, кто вы.
- «Есть HTTPS — значит, сайт безопасен». TLS защищает только канал между браузером и сервером. Он никак не мешает SQL-инъекции, XSS или дырявой авторизации на самом сервере.
- «Захешировали пароли — всё в порядке». Простой быстрый хеш (MD5,
SHA256без соли) переберут за часы. Нужны соль и медленный KDF. - «Своё шифрование надёжнее — никто не знает, как оно устроено». Это security through obscurity, антипаттерн: секретный самопал почти всегда слабее открытого стандарта, который годами ломали лучшие в мире и не сломали.
- «Сложные требования к паролю = безопасность». Правила вроде «спецсимвол и цифра» рождают
предсказуемые
Password1!и мучают людей. Куда важнее длина, проверка по базам утечек, менеджер паролей и MFA — так теперь и советует NIST.
Мини-итог
Безопасность — не модуль, который дописывают в конце, а свойство, которое обязано держаться на всех слоях сразу, потому что против системы работает не случай, а умный противник, целящий в худший случай. Мы защищаем три вещи — конфиденциальность, целостность, доступность (CIA), — и опираемся на три криптографических кита: хеши дают целостность и безопасное хранение паролей, симметричное шифрование даёт дешёвую секретность, асимметричное решает передачу ключа и даёт цифровую подпись; HTTPS/TLS соединяет их в гибридную схему с проверкой сертификата по цепочке доверия. Аутентификация («кто ты») отвечает на вопрос личности, авторизация («что можно») — на вопрос прав; путать их опасно. Пароли хранят только как соль плюс медленный KDF, входы всегда проверяют, а над всем этим стоят вечные принципы: наименьшие привилегии, защита в глубину, отказ в безопасную сторону, не изобретать свою криптографию и предполагать, что взлом уже случился. И помнить: самое слабое звено — обычно не математика, а человек и реализация.
Что дальше
Мы прошли всю башню — от битов и логики через процессор, память, код, ОС, сети и веб до данных и их защиты. Пора собрать эти слои в единую картину: увидеть, как одна абстракция стоит на другой от транзистора до веб-приложения, где каждая из них честно протекает и куда двигаться дальше, чтобы из «понимаю в общих чертах» вырасти в инженера, который знает, что происходит под капотом на каждом уровне.
Как всё складывается: слои абстракции от транзистора до приложения