Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Основы 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); // 36

UUID удобен как номер комментария: его не нужно вычислять по уже существующим записям.

Случайный токен

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.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.