"use strict"; const crypto = require("crypto"); const { promisify } = require("util"); const scryptAsync = promisify(crypto.scrypt); const KEY_LEN = 64; const SALT_LEN = 16; const SCRYPT_OPTIONS = { N: 16384, r: 8, p: 1, maxmem: 32 * 1024 * 1024, }; /** * Хеширует пароль с помощью scrypt и случайной соли. * @param {string} password * @returns {Promise} Формат: "saltHex:derivedKeyHex" */ async function hashPassword(password) { if (typeof password !== "string") { throw new TypeError("Пароль должен быть строкой"); } const salt = crypto.randomBytes(SALT_LEN); const derivedKey = await scryptAsync(password, salt, KEY_LEN, SCRYPT_OPTIONS); return `${salt.toString("hex")}:${derivedKey.toString("hex")}`; } /** * Синхронное хеширование (для инициализации при старте). * @param {string} password * @returns {string} */ function hashPasswordSync(password) { if (typeof password !== "string") { throw new TypeError("Пароль должен быть строкой"); } const salt = crypto.randomBytes(SALT_LEN); const derivedKey = crypto.scryptSync(password, salt, KEY_LEN, SCRYPT_OPTIONS); return `${salt.toString("hex")}:${derivedKey.toString("hex")}`; } /** * Проверяет пароль против сохранённого scrypt-хеша с защитой от тайминг-атак. * @param {string} password * @param {string} storedHash * @returns {Promise} */ async function verifyPassword(password, storedHash) { if (typeof password !== "string" || typeof storedHash !== "string") { return false; } const parts = storedHash.split(":"); if (parts.length !== 2) { return false; } const [saltHex, keyHex] = parts; try { const salt = Buffer.from(saltHex, "hex"); const originalKey = Buffer.from(keyHex, "hex"); if (salt.length !== SALT_LEN || originalKey.length !== KEY_LEN) { return false; } const derivedKey = await scryptAsync(password, salt, KEY_LEN, SCRYPT_OPTIONS); return crypto.timingSafeEqual(originalKey, derivedKey); } catch { return false; } } /** * Синхронная проверка пароля. * @param {string} password * @param {string} storedHash * @returns {boolean} */ function verifyPasswordSync(password, storedHash) { if (typeof password !== "string" || typeof storedHash !== "string") { return false; } const parts = storedHash.split(":"); if (parts.length !== 2) { return false; } const [saltHex, keyHex] = parts; try { const salt = Buffer.from(saltHex, "hex"); const originalKey = Buffer.from(keyHex, "hex"); if (salt.length !== SALT_LEN || originalKey.length !== KEY_LEN) { return false; } const derivedKey = crypto.scryptSync(password, salt, KEY_LEN, SCRYPT_OPTIONS); return crypto.timingSafeEqual(originalKey, derivedKey); } catch { return false; } } module.exports = { hashPassword, hashPasswordSync, verifyPassword, verifyPasswordSync, };