bcrypt.js используется в Node.js для безопасного хэширования паролей на основе алгоритма bcrypt, который специально разработан для устойчивости к перебору и атакам методом brute force. В отличие от быстрых хэш-функций (например, SHA-256), bcrypt намеренно является медленным и настраиваемым по сложности, что делает его практичным инструментом для хранения паролей в серверных приложениях.
bcrypt.js — чистая JavaScript-реализация алгоритма bcrypt, не требующая нативных зависимостей. Это особенно важно для окружений, где невозможна сборка бинарных модулей.
Ключевые характеристики:
Cost factor задаёт количество итераций алгоритма:
2^{cost}
Рост значения cost экспоненциально увеличивает время вычисления хэша.
В проектах Node.js bcrypt.js устанавливается как обычная зависимость:
npm install bcryptjs
Использование в коде:
import bcrypt from 'bcryptjs'
или CommonJS:
const bcrypt = require('bcryptjs')
const password = 'user_password'
const saltRounds = 10
const hash = await bcrypt.hash(password, saltRounds)
hash содержит и соль, и результат хэширования. Это
важная особенность bcrypt: отдельное хранение salt не требуется.
const isMatch = await bcrypt.compare(password, hashFromDatabase)
Функция compare сама извлекает соль из хэша и выполняет
повторное вычисление.
Koa использует объект контекста ctx, который объединяет
запрос и ответ в единую абстракцию. Работа с паролями в таком контексте
строится вокруг middleware, где ctx.request.body содержит
входные данные, а ctx.state используется для передачи
данных между слоями.
Типичный поток регистрации включает преобразование пароля до сохранения в базу данных.
import bcrypt from 'bcryptjs'
async function register(ctx) {
const { username, password } = ctx.request.body
const saltRounds = 12
const passwordHash = await bcrypt.hash(password, saltRounds)
const user = {
username,
password: passwordHash
}
// имитация сохранения в БД
ctx.body = { status: 'ok', user }
}
Ключевой момент — исходный пароль никогда не сохраняется.
async function login(ctx) {
const { username, password } = ctx.request.body
const user = await getUserByUsername(username)
if (!user) {
ctx.status = 401
return
}
const valid = await bcrypt.compare(password, user.password)
if (!valid) {
ctx.status = 401
return
}
ctx.state.user = user
ctx.body = { status: 'authenticated' }
}
ctx.state.user используется для передачи авторизованного
пользователя дальше по цепочке middleware.
Koa позволяет разделять ответственность на уровни:
async function authMiddleware(ctx, next) {
const { password, username } = ctx.request.body
ctx.state.credentials = { username, password }
await next()
}
Далее слой сервиса работает уже с нормализованными данными:
async function authService(ctx) {
const { username, password } = ctx.state.credentials
const user = await getUser(username)
const match = await bcrypt.compare(password, user.password)
ctx.state.user = match ? user : null
}
bcrypt автоматически генерирует соль и включает её в итоговый хэш:
$2a$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36...
Структура включает:
Это исключает необходимость ручного управления солью.
Увеличение cost factor значительно влияет на время ответа сервера:
T 2^{cost}
Практически используемые значения:
bcrypt может выбрасывать ошибки при некорректных входных данных:
try {
const hash = await bcrypt.hash(password, 12)
} catch (err) {
ctx.throw(500, 'Hashing error')
}
В Koa предпочтительно использовать ctx.throw для
централизованной обработки.
При работе с базой данных пароль заменяется хэшем:
const userSchema = {
username: String,
passwordHash: String
}
Сравнение всегда происходит через bcrypt.compare, а не
через прямое сравнение строк.
ctx.statebcrypt.hashSync в асинхронном
сервереbcrypt.js поддерживает оба варианта, но в Koa используется только асинхронный подход:
const hash = bcrypt.hashSync(password, 10)
Синхронные операции блокируют event loop и ухудшают масштабируемость.
Контекст Koa позволяет централизовать доступ к данным пользователя:
app.use(async (ctx, next) => {
ctx.state.auth = {
isAuthenticated: false,
user: null
}
await next()
})
После успешной проверки:
ctx.state.auth.isAuthenticated = true
ctx.state.auth.user = user
bcrypt использует защиту от timing attacks за счёт равномерного времени выполнения операции сравнения, независимо от совпадения строки.
Разделение Koa-контекста и бизнес-логики повышает читаемость:
class AuthService {
async hashPassword(password) {
return bcrypt.hash(password, 12)
}
async verify(password, hash) {
return bcrypt.compare(password, hash)
}
}
Контекст ctx остаётся только транспортным слоем, не
содержащим бизнес-логики.
При расширенной авторизации bcrypt используется только на этапе
проверки пароля, после чего система обычно переключается на токены
(например, JWT), а ctx.state служит временным хранилищем
состояния запроса.