Основы node:crypto
Модуль node:crypto отвечает за криптографию: хеши, случайные значения, шифрование. Для блога из него нужны три вещи: уникальные идентификаторы, случайные токены и безопасное хранение пароля администратора.
Случайный идентификатор
randomUUID() создаёт UUID — строку из 36 символов, которая с практической точностью не повторится никогда:
import { randomUUID } from 'node:crypto';
const id = randomUUID();
console.log(id); // например, 3b241101-e2bb-4255-8caf-4136c566a962
console.log(id.length); // 36UUID удобен как номер комментария: его не нужно вычислять по уже существующим записям.
Случайный токен
randomBytes() возвращает заданное число случайных байтов. Превращённые в строку, они становятся токеном — например, для ссылки сброса пароля:
import { randomBytes } from 'node:crypto';
const token = randomBytes(32).toString('hex');
console.log(token.length); // 64Каждый байт записывается двумя шестнадцатеричными символами, поэтому 32 байта дают 64 символа. Math.random() для таких целей не годится: его значения можно предсказать.
Хеш
Хеш — «отпечаток» данных фиксированной длины. Одинаковые данные всегда дают одинаковый хеш, а по хешу нельзя восстановить исходные данные:
import { createHash } from 'node:crypto';
const hash = (text) => createHash('sha256').update(text).digest('hex');
console.log(hash('Привет, Node.js').length); // 64
console.log(hash('Привет, Node.js') === hash('Привет, Node.js')); // true
console.log(hash('Привет, Node.js') === hash('Привет, Node.JS')); // falseХеш используют, чтобы проверить, что файл не изменился, или чтобы получить короткий ключ для кеша.
Пароли
Для паролей обычный sha256 слишком быстрый: перебрать миллионы вариантов не составит труда. Нужна медленная функция с «солью» — случайной добавкой, своей для каждого пароля. Встроенная функция scrypt подходит:
import { randomBytes, scrypt, timingSafeEqual } from 'node:crypto';
import { promisify } from 'node:util';
const scryptAsync = promisify(scrypt);
async function hashPassword(password) {
const salt = randomBytes(16).toString('hex');
const key = await scryptAsync(password, salt, 64);
return `${salt}:${key.toString('hex')}`;
}
async function verifyPassword(password, stored) {
const [salt, keyHex] = stored.split(':');
const key = await scryptAsync(password, salt, 64);
return timingSafeEqual(key, Buffer.from(keyHex, 'hex'));
}
const stored = await hashPassword('blog-admin-2025');
console.log(await verifyPassword('blog-admin-2025', stored)); // true
console.log(await verifyPassword('wrong', stored)); // falseВ базе хранят строку соль:хеш. При входе пароль хешируют с той же солью и сравнивают. timingSafeEqual() сравнивает за одинаковое время, чтобы по скорости ответа нельзя было угадать совпавшие символы.
Частая ошибка
Хранить пароли открытым текстом или простым хешем без соли нельзя: при утечке базы их сразу узнают. Даже sha256 без соли раскрывается по готовым таблицам популярных паролей. Используйте scrypt с солью, как выше, или проверенную библиотеку вроде bcrypt.