惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Spread Privacy
Spread Privacy
S
Schneier on Security
博客园 - 【当耐特】
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
美团技术团队
Application and Cybersecurity Blog
Application and Cybersecurity Blog
MongoDB | Blog
MongoDB | Blog
NISL@THU
NISL@THU
N
Netflix TechBlog - Medium
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Webroot Blog
Webroot Blog
月光博客
月光博客
T
The Exploit Database - CXSecurity.com
Forbes - Security
Forbes - Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 叶小钗
Recent Announcements
Recent Announcements
IT之家
IT之家
B
Blog
C
CERT Recently Published Vulnerability Notes
S
SegmentFault 最新的问题
Recent Commits to openclaw:main
Recent Commits to openclaw:main
F
Fortinet All Blogs
Martin Fowler
Martin Fowler
Know Your Adversary
Know Your Adversary
Security Latest
Security Latest
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Troy Hunt's Blog
O
OpenAI News
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
V2EX - 技术
V2EX - 技术
L
Lohrmann on Cybersecurity
C
Cyber Attacks, Cyber Crime and Cyber Security
H
Help Net Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Last Week in AI
Last Week in AI
Help Net Security
Help Net Security
Hacker News: Ask HN
Hacker News: Ask HN
A
About on SuperTechFans
Y
Y Combinator Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
Engineering at Meta
Engineering at Meta
T
Threat Research - Cisco Blogs
Vercel News
Vercel News
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Recorded Future
Recorded Future
C
Cisco Blogs
Project Zero
Project Zero

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Безопасное хранение паролей: соли, перцы и выбор алгоритма
liwoumsegi · 2026-06-25 · via Все публикации подряд на Хабре

Безопасное хранение паролей: соли, перцы и выбор алгоритма

Простой

10 мин

0

Если база данных утечёт, именно от алгоритма хеширования паролей будет зависеть, получит ли атакующий рабочие учётные данные за несколько часов или не получит ничего полезного вообще. Разберём, что нужно знать разработчику, чтобы оказаться во втором сценарии.

Хеширование против шифрования

Первое, что нужно зафиксировать: пароли хешируются, а не шифруются. Это принципиальное различие, которое часто путают.

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

Хеширование — одностороннее. Из хеша нельзя получить исходные данные. При проверке пароля приложение просто заново хеширует введённое значение и сравнивает результат с хранимым хешем. Никаких паролей в открытом виде в базе нет и быть не должно.

Отдельно стоит сказать о быстрых хеш‑функциях — SHA-256, SHA-512, MD5. Их использование для хранения паролей является грубой ошибкой. Эти алгоритмы проектировались для максимально быстрой работы, и современная GPU способна вычислять миллиарды таких хешей в секунду. MD5 и SHA-1 к тому же криптографически сломаны. Для паролей нужны специализированные алгоритмы, спроектированные быть намеренно медленными.

Соль и перец

Прежде чем переходить к алгоритмам, нужно понять два дополнительных механизма защиты — соль и перец.

Соль (salt) — случайная строка, которая добавляется к паролю перед хешированием. Генерируется отдельно для каждого пользователя и хранится в базе данных вместе с хешем в открытом виде. Соль не является секретом — её задача в другом.

Без соли один и тот же пароль всегда даёт один и тот же хеш. Это открывает дорогу к радужным таблицам (rainbow tables) — заранее вычисленным таблицам соответствий «пароль → хеш» для всех возможных комбинаций. Имея такую таблицу, атакующий «обращает» хеширование простым поиском: получает хеш из базы, находит его в таблице, читает пароль. Таблица всех возможных 8-символьных буквенно‑цифровых паролей содержит порядка 218 триллионов строк — большое, но вполне реальное число для современных хранилищ.

Соль это ломает. Каждый пользователь получает свою уникальную соль, и один и тот же пароль у двух пользователей даст два разных хеша. Чтобы атаковать базу с солями, атакующему пришлось бы строить отдельную rainbow table для каждой соли. При 64-битной соли размер такой таблицы для тех же 8-символьных паролей вырастает до 4×10³³ строк — это за пределами любых реальных возможностей. Современные библиотеки — passlib, Spring Security, bcrypt.js — генерируют и встраивают соль автоматически, вручную возиться с этим не нужно.

Перец (pepper) — дополнительный секретный компонент, добавляемый к паролю. В отличие от соли, перец не хранится в базе данных — он живёт отдельно: в конфигурационном файле, переменной среды или HSM. Смысл в том, что даже при полной компрометации базы данных атакующий не получит перец и не сможет эффективно атаковать хеши. Соль делает каждый хеш уникальным; перец добавляет секрет, которого в базе нет вообще.

Перец разделяется между всеми пользователями — в отличие от соли. OWASP рекомендует реализовывать его через HMAC: хеш пароля выступает сообщением, секретное значение — ключом. Простая конкатенация password + pepper хуже, потому что граница между паролем и перцем может быть восстановлена при частичной утечке. Важное ограничение перца: его невозможно изменить без принудительного сброса паролей всем пользователям. Если перец скомпрометирован, атакующий узнаёт значение, применённое ко всем хешам в базе сразу.

Коэффициент сложности: как алгоритм адаптируется к железу

Все современные алгоритмы хранения паролей имеют настраиваемый коэффициент сложности, который определяет вычислительную стоимость одного хеша. Цель — сделать так, чтобы одна операция хеширования занимала 200–500 мс на сервере. Это достаточно медленно, чтобы brute‑force атака была экономически нецелесообразной, и достаточно быстро, чтобы легитимный пользователь при входе ничего не заметил.

Коэффициент сложности нужно периодически пересматривать — примерно раз в 2–3 года. Железо дешевеет и ускоряется, и то, что в 2020 году давало 300 мс, в 2026-м может отрабатывать за 50 мс. Хорошая практика — при каждом успешном входе пользователя проверять, соответствует ли его хеш актуальному work factor, и если нет — перехешировать «на лету».

Алгоритмы

Argon2id

Победитель Password Hashing Competition 2015 года, разработан командой под руководством Алексея Бирюкова, Даниэля Яна и Димитриса Худракиса, стандартизирован в RFC 9106. Текущая рекомендация OWASP и де‑факто стандарт для новых проектов.

Argon2 существует в трёх вариантах. Argon2d оптимизирован против GPU‑атак через зависящий от данных доступ к памяти, но именно поэтому уязвим к атакам через сторонние каналы. Argon2i использует независимый от данных доступ к памяти — устойчив к side‑channel, но в 2016 году академическая работа показала слабости против GPU‑атак, которые частично компенсировались изменением параметров. Argon2id — гибрид: первая половина итераций работает как Argon2i, вторая — как Argon2d. Для хранения паролей всегда используется Argon2id.

Главное преимущество Argon2 — высокие требования к памяти при вычислении хеша. Это делает параллельные атаки на GPU экономически невыгодными: GPU имеет ограниченный объём памяти на ядро, и если алгоритм требует 64 МБ на один хеш, одновременно атаковать тысячи хешей параллельно просто нечем.

Алгоритм имеет три параметра: память (m), итерации (t) и параллелизм (p). Минимальные параметры по OWASP: память 19 МБ, итерации 2, параллелизм 1. При наличии ресурсов рекомендуется поднять память до 64 МБ и итерации до 3.

from argon2 import PasswordHasher

ph = PasswordHasher(
    time_cost=3,        # итерации
    memory_cost=65536,  # 64 МБ в KiB
    parallelism=4,
    hash_len=32,
    salt_len=16
)

# Хешируем
hash = ph.hash("user_password")

# Проверяем
try:
    ph.verify(hash, "user_password")
    # Опционально: перехешировать если параметры устарели
    if ph.check_needs_rehash(hash):
        hash = ph.hash("user_password")
except Exception:
    # Неверный пароль
    pass
const argon2 = require('argon2');

// Хешируем
const hash = await argon2.hash("user_password", {
    type: argon2.argon2id,
    memoryCost: 65536, // 64 MB
    timeCost: 3,
    parallelism: 4
});

// Проверяем
const match = await argon2.verify(hash, "user_password");
// Spring Security 6+
PasswordEncoder encoder = new Argon2PasswordEncoder(
    16,    // salt length
    32,    // hash length
    1,     // parallelism
    65536, // memory in KB (64 MB)
    3      // iterations
);

String hash = encoder.encode("user_password");
boolean matches = encoder.matches("user_password", hash);

bcrypt

Разработан Нильсом Провосом и Дэвидом Мазьером в 1999 году на основе шифра Blowfish. Более 25 лет реального использования — серьёзный аргумент в его пользу. OWASP считает bcrypt приемлемым для существующих систем, для новых проектов рекомендует Argon2.

Параметр сложности bcrypt — степень двойки: значение 12 означает 2^12 = 4096 итераций. Минимум в 2026 году — 12, оптимально 13–14 на современном железе. Для ориентира: на 2 ГГц процессоре значение 12 даёт порядка 2–3 хешей в секунду, 13 — около 1 хеша в секунду.

Принципиальное ограничение bcrypt: максимальная длина входных данных — 72 байта. Всё, что длиннее, молча обрезается. Это создаёт проблему: пользователь устанавливает пароль длиной 80 символов, а хранится хеш только первых 72. Если хочется поддерживать длинные пароли, нужна предварительная обработка — но делать это нужно осторожно.

Распространённый неправильный подход: захешировать пароль через MD5 или SHA-256 и передать результат в bcrypt. Проблема в том, что SHA-256 возвращает бинарные данные, которые могут содержать нулевые байты, а bcrypt обрезает строку по первому нулевому байту — это приводит к реальным уязвимостям, известным как «password shucking». Правильный вариант — кодировать результат в Base64 или hex перед передачей в bcrypt.

import bcrypt

# Хешируем
password = "user_password".encode("utf-8")
salt = bcrypt.gensalt(rounds=13)
hash = bcrypt.hashpw(password, salt)

# Проверяем
bcrypt.checkpw(password, hash)  # True/False
const bcrypt = require("bcrypt");

// Хешируем
const hash = await bcrypt.hash("user_password", 13);

// Проверяем
const result = await bcrypt.compare("user_password", hash); // true/false
// Spring Security
PasswordEncoder encoder = new BCryptPasswordEncoder(13);
String hash = encoder.encode("user_password");
boolean matches = encoder.matches("user_password", hash);

scrypt

Разработан Колином Персивалем в 2009 году для сервиса резервного копирования Tarsnap. Первый широко используемый алгоритм с явными требованиями к памяти — именно scrypt ввёл эту концепцию, которую позже развил Argon2.

Алгоритм генерирует в начале работы большой вектор псевдослучайных битовых последовательностей, а затем обращается к его элементам в псевдослучайном порядке, комбинируя их для получения ключа. Порядок обращений зависит от промежуточных результатов вычисления, поэтому каждое следующее обращение зависит от предыдущего — параллелизовать это затруднительно. Теоретически можно не хранить вектор в памяти, а пересчитывать каждый элемент в момент обращения, но scrypt специально настроен так, чтобы такая реализация была слишком медленной — баланс между памятью и временем намеренно невыгоден для безпамятных реализаций.

Параметры scrypt: N (степень двойки, определяет память и количество итераций), r (размер блока) и p (параллелизм). Потребление памяти оценивается как 128 × r × N байт. OWASP рекомендует N=2^17, r=8, p=1, что требует около 128 МБ памяти на хеш.

У scrypt есть существенный недостаток по сравнению с Argon2: три параметра взаимодействуют нелинейно, и неправильная конфигурация — особенно высокое p при малом N — может дать слабую защиту. При настройке на 1 мс операции алгоритм использует слишком мало памяти и становится слабее bcrypt при сопоставимой скорости. Argon2 имеет более предсказуемое поведение при настройке.

import os, hashlib

password = b"user_password"
salt = os.urandom(16)

hash_bytes = hashlib.scrypt(
    password,
    salt=salt,
    n=2**17,   # CPU/memory cost
    r=8,       # block size
    p=1,       # parallelism
    dklen=32
)

# Сохранить salt + hash_bytes вместе

PBKDF2

Стандарт RSA Laboratories 2000 года (RFC 2898), основан на многократном применении HMAC. Итеративно применяет HMAC к паролю и соли — вычисляет U1, U2,..., Un, затем XOR всех промежуточных значений даёт итоговый ключ. Количество итераций напрямую контролирует вычислительную стоимость.

Главная слабость PBKDF2 — отсутствие требований к памяти при вычислении. GPU без каких‑либо ограничений по памяти может атаковать PBKDF2 параллельно, что делает его существенно слабее Argon2 при одинаковом времени вычисления. По оценкам, взлом 8-символьного пароля под Argon2id стоит атакующему от $500 000 на современном железе, тогда как под PBKDF2 — порядка $5 000.

Тем не менее PBKDF2 остаётся стандартом де‑факто в FIPS 140-2/140-3 средах. Если система работает в условиях требований FIPS‑совместимости, PBKDF2-HMAC‑SHA-256 с минимум 600 000 итерациями — единственный приемлемый выбор. В остальных случаях предпочтителен Argon2.

import hashlib, os

password = b"user_password"
salt = os.urandom(16)

hash_bytes = hashlib.pbkdf2_hmac(
    "sha256",
    password,
    salt,
    iterations=600000,
    dklen=32
)

Распространённые ошибки

MD5 и SHA без итераций. Самая грубая ошибка — хранить md5(password) или sha256(password). Без замедления это миллиарды хешей в секунду на обычной GPU. Пин‑код из пяти цифр, захешированный SHA-512 без соли, гуглится напрямую — хеш просто будет в первых результатах поиска.

Статическая соль. Использовать одну и ту же соль для всех пользователей бессмысленно: rainbow table всё равно можно построить, просто один раз под эту конкретную соль. Два пользователя с одинаковым паролем получат одинаковый хеш — атакующий сразу это видит.

bcrypt(md5(password)). Выглядит как двойная защита, но MD5 снижает энтропию и создаёт проблему с нулевыми байтами в бинарном выходе — bcrypt обрежет строку по первому нулевому байту. Если нужна предобработка для длинных паролей — использовать Base64-кодирование результата SHA-256, не сырые байты.

Слишком высокие параметры как DoS‑вектор. Особенно актуально для scrypt: если N выставлен слишком высоко, атакующий может намеренно инициировать множество запросов на хеширование и исчерпать память сервера. Фактор сложности должен быть достаточным для защиты, но не настолько высоким, чтобы одна операция занимала несколько секунд.

Сравнение хешей через ==. Строковое сравнение уязвимо к атакам по времени: по времени ответа можно определить, насколько совпадает начало хеша. Использовать только constant‑time comparison — hmac.compare_digest() в Python, MessageDigest.isEqual() в Java.

import hmac

# Небезопасно
if stored_hash == computed_hash:
    ...

# Правильно
if hmac.compare_digest(stored_hash.encode(), computed_hash.encode()):
    ...

Миграция со старых алгоритмов

Если в базе хранятся MD5 или SHA‑хеши, массовый сброс паролей — не единственный вариант. Можно мигрировать постепенно: при каждом успешном входе пользователя старый хеш заменяется новым, вычисленным с актуальным алгоритмом. Для этого в базе нужно хранить версию алгоритма рядом с хешем.

Более радикальный вариант для срочной миграции: захешировать существующие MD5-хеши через Argon2 — получится Argon2(MD5(password)). Защита станет значительно лучше немедленно, без сброса паролей. При следующем входе пользователя хеш заменяется на Argon2(password) — уже без MD5 в цепочке.

Итоговый выбор

Для нового проекта — Argon2id с памятью от 64 МБ, 3 итерациями, параллелизмом по числу доступных ядер. Соль генерируется автоматически библиотекой. Перец — опционально, через HMAC, если модель угрозы предполагает компрометацию базы без доступа к конфигурации сервера.

Для существующего проекта на bcrypt — фактор сложности не ниже 12, периодический пересмотр. Планировать миграцию на Argon2 при следующем крупном рефакторинге аутентификации.

FIPS‑среды — PBKDF2-HMAC‑SHA-256, 600 000 итераций.

MD5, SHA-1, SHA-256 и SHA-512 без итераций — никогда, ни при каких обстоятельствах.