/** * Проверка хеша в формате Werkzeug 3 (scrypt:… / pbkdf2:…). * @see https://github.com/pallets/werkzeug/blob/main/src/werkzeug/security.py */ import crypto from 'crypto'; /** * @param {string} pwhash * @param {string} password * @returns {boolean} */ function hashInternal(method, salt, password) { const methodParts = method.split(':'); const kind = methodParts[0]; const saltBytes = Buffer.from(salt, 'utf8'); const passwordBytes = Buffer.from(password, 'utf8'); if (kind === 'scrypt') { const n = methodParts[1] ? parseInt(methodParts[1], 10) : 2 ** 15; const r = methodParts[2] ? parseInt(methodParts[2], 10) : 8; const p = methodParts[3] ? parseInt(methodParts[3], 10) : 1; const maxmem = 132 * n * r * p; return crypto .scryptSync(passwordBytes, saltBytes, 64, { N: n, r, p, maxmem }) .toString('hex'); } if (kind === 'pbkdf2') { const hashName = methodParts[1] || 'sha256'; const iterStr = methodParts[2]; if (!iterStr) { throw new Error('pbkdf2: missing iterations'); } const iterations = parseInt(iterStr, 10); return crypto .pbkdf2Sync(passwordBytes, saltBytes, iterations, 32, hashName) .toString('hex'); } throw new Error(`Invalid hash method: ${kind}`); } /** * @param {string} pwhash * @param {string} password * @returns {boolean} */ export function checkWerkzeugPassword(pwhash, password) { if (!pwhash || pwhash.length < 3) { return false; } const parts = pwhash.split('$'); if (parts.length < 3) { return false; } const hashval = parts.pop(); const salt = parts.pop(); const method = parts.join('$'); if (!method || !salt || !hashval) { return false; } let computed; try { computed = hashInternal(method, salt, password); } catch { return false; } const a = Buffer.from(computed, 'hex'); const b = Buffer.from(hashval, 'hex'); if (a.length !== b.length) { return false; } return crypto.timingSafeEqual(a, b); }