Все статьи
Криптография и безопасность

Bcrypt: правильное хеширование паролей

Почему bcrypt для паролей, salt, cost factor, сравнение с SHA, лучшие практики безопасности.

24 марта 2025
9 мин чтения
ConvertHub
#bcrypt#пароли#хеширование

Введение

Хранение паролей в открытом виде — смертный грех разработчика. Хранить их в виде MD5 или SHA-256 — почти так же плохо: современные GPU перебирают миллиарды хешей в секунду. На сцену выходит bcrypt — алгоритм, специально спроектированный для медленного хеширования паролей. В этой статье разберёмся, чем bcrypt отличается от обычных хеш-функций, что такое salt и cost factor, как правильно его применять и почему в 2025 году он всё ещё актуален. Сгенерировать bcrypt-хеш можно в нашем bcrypt-генераторе.

Почему обычные хеши не подходят

Хеш-функции вроде MD5, SHA-256 и SHA-512 создавались для одной цели — быстро вычислять дайджест больших объёмов данных. Скорость для них — достоинство: хеширование гигабайтного файла за секунду помогает проверять целостность.

Но при хранении паролей скорость превращается в уязвимость. Если атакующий получил базу с SHA-256-хешами, он может запустить перебор по словарю на GPU и за часы восстановить миллионы паролей. И добавление соли не спасает — она лишь не позволяет использовать готовые радужные таблицы, но не мешает прямому перебору.

АлгоритмХешей/сек (RTX 4090)Годен для паролей
MD5~50 млрдНет
SHA-256~15 млрдНет
SHA-512~5 млрдНет
bcrypt (cost=12)~200Да
argon2id~50Да (рекомендуется)

Что такое bcrypt

Bcrypt — это хеш-функция, разработанная Нильсом Пропсом и Дэвидом Мазьером в 1999 году на основе шифра Blowfish. Главное отличие от SHA — намеренная медлительность. Bcrypt использует настраиваемый «cost factor»: при каждом удвоении этого параметра время вычисления хеша также удваивается.

Стандартный bcrypt-хеш выглядит так:

$2b$12$N9qo8uLOickgx2ZMRZoMy.MrqKMD8j6IvF4pYdqLjq5w7XqjK8K7u
└┬┘ └┬┘ └──────────────┬───────────────┘ └───────────────┬───────────────┘
 │   │                  │                                  │
 │   │                  salt (22 символа)                  хеш (31 символ)
 │   cost factor
 версия алгоритма

Salt: защита от радужных таблиц

Salt (соль) — это случайная строка, добавляемая к паролю перед хешированием. Зачем она нужна? Если два пользователя выберут одинаковый пароль «qwerty123», то без соли их хеши совпадут. Атакующий, увидев одинаковые хеши, сразу понимает: пароли одинаковые, и ему достаточно подобрать его один раз.

Соль делает каждый хеш уникальным, даже если пароли совпадают. Bcrypt автоматически генерирует 16-байтную соль и встраивает её в итоговый хеш. Это удобно: при проверке пароля алгоритм сам извлекает соль из строки и использует её для вычисления.

Виды salt-стратегий

  • Без соли — нельзя использовать, уязвимо к радужным таблицам.
  • Глобальная соль — одна на всех. Защищает от готовых таблиц, но не от атак на конкретного пользователя.
  • Пер-пользовательская соль — оптимально. Bcrypt делает именно это.
  • Пер-пользовательская соль + pepper — соли хранятся в БД, pepper — в коде или secrets-менеджере. Дополнительно защищает при утечке БД.

Cost factor: настраиваемая медлительность

Cost factor (он же «work factor») — это логарифм по основанию 2 от количества раундов. Cost = 12 означает 2¹² = 4096 раундов. Каждый дополнительный шаг удваивает время вычисления.

CostРаундовВремя хеширования (типичное)
8256~5 мс
101024~25 мс
124096~100 мс
1416384~400 мс
1665536~1,6 с

Рекомендация OWASP на 2025 год — устанавливать cost factor так, чтобы хеширование занимало 250–500 мс. Для большинства серверов это значение попадает в диапазон 12–14. Увеличивать выше 16 нецелесообразно — пользователи будут слишком долго ждать входа.

Когда увеличивать cost

Закон Мура работает: процессоры становятся быстрее. Хорошая практика — раз в два года повышать cost на единицу. При этом старые хеши не нужно «мигрировать» вручную: при следующем успешном входе пользователя система пересчитывает хеш с новым cost и заменяет старый.

Как использовать bcrypt

Node.js

const bcrypt = require('bcrypt');

// Хеширование пароля
const password = 'my-secret-password';
const saltRounds = 12;

bcrypt.hash(password, saltRounds, (err, hash) => {
  console.log(hash);
  // $2b$12$N9qo8uLOickgx2ZMRZoMy.MrqKMD8j6IvF4pYdqLjq5w7XqjK8K7u
});

// Проверка пароля
const storedHash = '$2b$12$N9qo8uLOickgx2ZMRZoMy.MrqKMD8j6IvF4pYdqLjq5w7XqjK8K7u';
bcrypt.compare('my-secret-password', storedHash, (err, result) => {
  console.log(result); // true
});

Python

import bcrypt

password = b'my-secret-password'

# Хеширование
salt = bcrypt.gensalt(rounds=12)
hashed = bcrypt.hashpw(password, salt)
print(hashed)
# b'$2b$12$...'

# Проверка
if bcrypt.checkpw(password, hashed):
    print('Пароль верный')

Go

package main

import (
  "fmt"
  "golang.org/x/crypto/bcrypt"
)

func main() {
  password := []byte("my-secret-password")

  hashed, _ := bcrypt.GenerateFromPassword(password, 12)
  fmt.Println(string(hashed))

  err := bcrypt.CompareHashAndPassword(hashed, password)
  fmt.Println(err == nil) // true
}

Версии bcrypt: $2a$, $2b$, $2y$

Префикс в начале хеша указывает на версию алгоритма:

  • $2a$ — оригинальная спецификация (1999).
  • $2x$ — патч для бага в реализации crypt_blowfish.
  • $2y$ — используется в PHP, идентичен $2a$ по поведению.
  • $2b$ — актуальная версия, рекомендуемая в новых проектах.

Все версии совместимы на уровне проверки: хеш, созданный как $2a$, можно проверить с помощью библиотеки, ожидающей $2b$, и наоборот.

Ограничения bcrypt

Bcrypt обрезает пароль до 72 байт. Это историческое ограничение, связанное с размером блока Blowfish. Если пользователь задаст пароль длиной 100 символов, последние 28 символов будут проигнорированы. Чтобы этого избежать, разработчики часто предварительно хешируют пароль через SHA-256, а результат уже передают в bcrypt:

const crypto = require('crypto');
const bcrypt = require('bcrypt');

function hashPassword(password) {
  // Предхеширование: 64 hex-символа = 64 байта, вписывается в лимит bcrypt
  const preHash = crypto.createHash('sha256').update(password).digest('hex');
  return bcrypt.hash(preHash, 12);
}

function verifyPassword(password, storedHash) {
  const preHash = crypto.createHash('sha256').update(password).digest('hex');
  return bcrypt.compare(preHash, storedHash);
}

Альтернатива — перейти на argon2id, у которого нет ограничения по длине и который считается наиболее стойким из современных алгоритмов.

Bcrypt vs argon2 vs scrypt

АлгоритмГодЗащита от GPUЗащита от ASICСложность настройки
bcrypt1999СредняяСлабаяПросто
scrypt2009ВысокаяСредняяСредняя
argon2id2015ВысокаяВысокаяСредняя

Argon2id — победитель конкурса Password Hashing Competition 2015 и текущая рекомендация OWASP. Однако bcrypt остаётся отличным выбором для большинства проектов: он проще в настройке, поддерживается везде и десятилетиями доказывал свою надёжность.

Лучшие практики

  1. Используйте cost factor не ниже 12 для новых проектов.
  2. Никогда не храните «свой» алгоритм хеширования. Берите проверенную библиотеку.
  3. Не пытайтесь оптимизировать bcrypt — его медлительность и есть защита.
  4. Добавьте pepper — секретный ключ из конфигурации, который не хранится в БД.
  5. Раз в 2 года повышайте cost factor и мигрируйте хеши при следующем входе.
  6. Логируйте неудачные попытки входа и используйте rate limiting.
  7. Предложите пользователям двухфакторную аутентификацию.

Заключение

Bcrypt — проверенный временем алгоритм, специально созданный для хранения паролей. Его медлительность — это не баг, а особенность: чем дольше вычисляется один хеш, тем дороже для атакующего массовый перебор. Соль, cost factor и правильная библиотека — этого набора достаточно, чтобы база утекших хешей осталась бесполезной для злоумышленника. Сгенерировать bcrypt-хеш для проверки можно в нашем bcrypt-генераторе, а стойкий пароль для нового аккаунта — вгенераторе паролей.

Попробуйте эти инструменты

Похожие статьи