Хеширование паролей — процесс преобразования исходного пароля в необратимую строку фиксированной длины. В отличие от шифрования, хеш нельзя «расшифровать» обратно. Проверка пароля выполняется сравнением хеша введённого значения с сохранённым хешем в базе данных.
Основные требования к алгоритму хранения паролей:
salt);Для хранения пользовательских паролей нельзя использовать обычные криптографические хеш-функции вроде MD5 или SHA-256 без дополнительных механизмов защиты. Для этой задачи применяются специальные алгоритмы: bcrypt, scrypt и Argon2.
MD5 (Message Digest Algorithm 5) — криптографическая
хеш-функция, разработанная Рональдом Ривестом в 1991 году. Алгоритм
создаёт 128-битный хеш.
Пример MD5-хеша:
5f4dcc3b5aa765d61d8327deb882cf99
MD5 долгое время использовался для:
Сегодня MD5 считается криптографически небезопасным.
Современные видеокарты способны вычислять миллиарды MD5-хешей в секунду. Это делает brute-force и dictionary-атаки чрезвычайно эффективными.
Коллизия — ситуация, при которой два разных входных значения дают одинаковый хеш.
MD5 имеет практические коллизии, поэтому алгоритм нельзя использовать в системах безопасности.
MD5 не использует salt, поэтому одинаковые пароли дают
одинаковые хеши.
Установка:
npm install crypto-js
Пример:
const CryptoJS = require("crypto-js");
const password = "qwerty123";
const hash = CryptoJS.MD5(password).toString();
console.log(hash);
Результат:
dc647eb65e6711e155375218212b3964
Даже при добавлении соли MD5 остаётся слишком быстрым. Скорость — преимущество для проверки файлов, но серьёзный недостаток для хранения паролей.
Современные рекомендации OWASP и NIST запрещают использовать MD5 для password hashing.
SHA (Secure Hash Algorithm) — семейство
криптографических хеш-функций.
Наиболее известные версии:
| Алгоритм | Размер хеша |
|---|---|
| SHA-1 | 160 бит |
| SHA-256 | 256 бит |
| SHA-512 | 512 бит |
SHA-1 долгое время считался безопасным, но сегодня также признан устаревшим.
В 2017 году Google продемонстрировал практическую коллизию SHA-1.
SHA-256 остаётся криптографически надёжным для проверки целостности и цифровых подписей.
Пример SHA-256:
ef92b778bafe771e89245b89ecbc08a44a4e166c06659911881f383d4473e94f
Однако SHA-256 плохо подходит для хранения паролей по той же причине, что и MD5 — слишком высокая скорость вычисления.
Встроенный модуль crypto:
const crypto = require("crypto");
const password = "qwerty123";
const hash = crypto
.createHash("sha256")
.update(password)
.digest("hex");
console.log(hash);
Пример с salt:
const crypto = require("crypto");
const password = "qwerty123";
const salt = crypto.randomBytes(16).toString("hex");
const hash = crypto
.createHash("sha256")
.update(password + salt)
.digest("hex");
console.log({
salt,
hash
});
Даже при использовании соли SHA-256 остаётся:
Для password hashing нужны алгоритмы с контролируемой стоимостью вычислений.
bcrypt — специализированный алгоритм хеширования паролей, основанный на шифре Blowfish.
Основные преимущества:
bcrypt остаётся одним из самых популярных решений для хранения паролей.
npm install bcrypt
const bcrypt = require("bcrypt");
async function hashPassword() {
const password = "qwerty123";
const saltRounds = 10;
const hash = await bcrypt.hash(password, saltRounds);
console.log(hash);
}
hashPassword();
Пример результата:
$2b$10$9z6m5Y9f8rX4...
const bcrypt = require("bcrypt");
async function verifyPassword() {
const password = "qwerty123";
const hash = await bcrypt.hash(password, 10);
const match = await bcrypt.compare(password, hash);
console.log(match);
}
verifyPassword();
saltRounds определяет вычислительную стоимость.
Пример:
| Значение | Примерное время |
|---|---|
| 8 | быстро |
| 10 | стандарт |
| 12 | медленно |
| 14+ | очень медленно |
Чем выше значение:
Пример:
$2b$10$N9qo8uLOickgx2ZMRZo5e.PJQ5...
Расшифровка:
| Часть | Назначение |
|---|---|
$2b$ |
версия алгоритма |
10 |
cost factor |
| далее | соль и хеш |
bcrypt автоматически хранит соль внутри строки хеша.
bcrypt учитывает только первые 72 байта пароля.
Длинные пароли могут обрезаться.
bcrypt проектировался раньше появления мощных GPU-ферм.
Сегодня существуют более современные memory-hard алгоритмы.
scrypt разработан Колином Персивалем в 2009 году.
Главная особенность — высокая потребность в памяти.
Это делает атаки на GPU и ASIC значительно дороже.
Node.js имеет встроенную поддержку.
const crypto = require("crypto");
const password = "qwerty123";
const salt = crypto.randomBytes(16).toString("hex");
crypto.scrypt(password, salt, 64, (err, derivedKey) => {
if (err) throw err;
console.log(derivedKey.toString("hex"));
});
const crypto = require("crypto");
function hash(password, salt) {
return new Promise((resolve, reject) => {
crypto.scrypt(password, salt, 64, (err, key) => {
if (err) reject(err);
resolve(key.toString("hex"));
});
});
}
async function verify() {
const password = "qwerty123";
const salt = "mysalt";
const originalHash = await hash(password, salt);
const checkHash = await hash(password, salt);
console.log(originalHash === checkHash);
}
verify();
Основные параметры:
| Параметр | Назначение |
|---|---|
N |
CPU/Memory cost |
r |
block size |
p |
parallelization |
Пример:
crypto.scrypt(password, salt, 64, {
N: 16384,
r: 8,
p: 1
}, callback);
Memory-hard алгоритмы требуют:
Это существенно увеличивает стоимость специализированного оборудования для атак.
Argon2 — победитель Password Hashing Competition 2015.
Считается современным стандартом password hashing.
Основные достоинства:
| Версия | Назначение |
|---|---|
| Argon2d | защита от GPU |
| Argon2i | защита от side-channel |
| Argon2id | комбинированный вариант |
На практике рекомендуется Argon2id.
npm install argon2
const argon2 = require("argon2");
async function run() {
const password = "qwerty123";
const hash = await argon2.hash(password);
console.log(hash);
}
run();
const argon2 = require("argon2");
async function verify() {
const password = "qwerty123";
const hash = await argon2.hash(password);
const valid = await argon2.verify(hash, password);
console.log(valid);
}
verify();
const argon2 = require("argon2");
async function run() {
const hash = await argon2.hash("qwerty123", {
type: argon2.argon2id,
memoryCost: 65536,
timeCost: 3,
parallelism: 4
});
console.log(hash);
}
run();
| Параметр | Назначение |
|---|---|
memoryCost |
объём памяти |
timeCost |
число итераций |
parallelism |
степень параллелизма |
$argon2id$v=19$m=65536,t=3,p=4$...
Строка содержит:
| Алгоритм | Скорость |
|---|---|
| MD5 | очень высокая |
| SHA-256 | высокая |
| bcrypt | средняя |
| scrypt | низкая |
| Argon2 | низкая |
Для хранения паролей предпочтительны медленные алгоритмы.
| Алгоритм | GPU-защита |
|---|---|
| MD5 | отсутствует |
| SHA | отсутствует |
| bcrypt | частичная |
| scrypt | высокая |
| Argon2 | очень высокая |
| Алгоритм | Memory-hard |
|---|---|
| MD5 | нет |
| SHA | нет |
| bcrypt | частично |
| scrypt | да |
| Argon2 | да |
| Сценарий | Рекомендуемый алгоритм |
|---|---|
| Новые проекты | Argon2id |
| Совместимость | bcrypt |
| Ограниченная поддержка | scrypt |
| Проверка файлов | SHA-256 |
| Хранение паролей | не MD5 и не SHA |
Salt — случайная строка, добавляемая к паролю перед хешированием.
Пример:
password + salt
Соль защищает от:
const crypto = require("crypto");
const salt = crypto.randomBytes(16).toString("hex");
console.log(salt);
Pepper — секретное значение, известное только серверу.
В отличие от соли:
const pepper = process.env.PEPPER;
const finalPassword = password + pepper;
Обычное сравнение строк может раскрывать информацию о совпадении символов.
Небезопасно:
if (hash1 === hash2) {
}
const crypto = require("crypto");
const match = crypto.timingSafeEqual(
Buffer.from(hash1),
Buffer.from(hash2)
);
Старые системы часто используют:
Типичная схема:
Предпочтительный выбор:
Argon2id
Используется:
bcrypt
cost >= 10
memoryCost >= 64 MB
timeCost >= 3
Критическая ошибка безопасности.
Одинаковые пароли дают одинаковые хеши.
Снижает устойчивость к brute-force.
Нельзя писать собственный password hashing вручную.
Следует использовать проверенные библиотеки.
const argon2 = require("argon2");
async function register(password) {
const hash = await argon2.hash(password, {
type: argon2.argon2id
});
return hash;
}
const argon2 = require("argon2");
async function login(password, storedHash) {
const valid = await argon2.verify(
storedHash,
password
);
return valid;
}
| Алгоритм | Безопасность | Скорость | Memory-hard | Рекомендация |
|---|---|---|---|---|
| MD5 | низкая | очень высокая | нет | не использовать |
| SHA-256 | средняя | высокая | нет | не для паролей |
| bcrypt | высокая | средняя | частично | допустимо |
| scrypt | высокая | низкая | да | хороший выбор |
| Argon2id | очень высокая | низкая | да | лучший выбор |