You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

74 lines
2.0 KiB

/**
* Проверка хеша в формате 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);
}