check-wordpress-password
Version:
Check a WordPress generated password hash against a password
118 lines (117 loc) • 4.24 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateHash = generateHash;
exports.checkPassword = checkPassword;
const node_crypto_1 = __importDefault(require("node:crypto"));
/**
* Returns the custom Base64 alphabet used for encoding.
*
* @returns {string} The Base64 alphabet.
*/
function itoa64() {
return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
}
/**
* Hashes a password using the provided salt and settings.
*
* @param {string} password - The password to hash.
* @param {string} setting - The hash setting string containing salt and iteration count.
* @param {string} itoa64 - The custom Base64 alphabet.
* @returns {string} The hashed password.
* @throws {Error} If the iteration count or salt length is invalid.
*/
function cryptPrivate(password, setting, itoa64) {
const count_log2 = itoa64.indexOf(setting[3]);
if (count_log2 < 7 || count_log2 > 30) {
throw new Error('Invalid iteration count');
}
const count = 1 << count_log2;
const salt = setting.slice(4, 12);
if (salt.length !== 8) {
throw new Error('Invalid salt length');
}
let hash = node_crypto_1.default.createHash('md5').update(salt + password).digest();
for (let i = 0; i < count; i++) {
hash = node_crypto_1.default.createHash('md5').update(Buffer.concat([hash, Buffer.from(password)])).digest();
}
return setting.slice(0, 12) + encode64(hash, 16, itoa64);
}
/**
* Encodes binary data to a string using a custom Base64 alphabet.
*
* @param {Buffer} input - The input buffer to encode.
* @param {number} count - The number of bytes to encode.
* @param {string} itoa64 - The custom Base64 alphabet.
* @returns {string} The encoded string.
*/
function encode64(input, count, itoa64) {
let output = '';
let i = 0;
while (i < count) {
let value = input[i++];
output += itoa64[value & 0x3f];
if (i < count) {
value |= input[i] << 8;
output += itoa64[(value >> 6) & 0x3f];
i++;
}
else {
output += itoa64[(value >> 6) & 0x3f];
break;
}
if (i < count) {
value |= input[i] << 16;
output += itoa64[(value >> 12) & 0x3f];
i++;
}
else {
output += itoa64[(value >> 12) & 0x3f];
break;
}
output += itoa64[(value >> 18) & 0x3f];
}
return output;
}
/**
* Generates a hash for a given password using a random salt and specified iteration count.
*
* @param {string} password - The password to hash.
* @param {number} iterationCountLog2 - Log2 of the iteration count (must be between 7 and 30).
* @returns {string} The generated hashed password.
* @throws {Error} If the iteration count log2 is out of range.
*/
function generateHash(password, iterationCountLog2) {
const itoa64Str = itoa64();
if (iterationCountLog2 < 7 || iterationCountLog2 > 30) {
throw new Error('Invalid iteration count log2');
}
// Convert the iteration count log2 to the corresponding character from the itoa64
const countChar = itoa64Str[iterationCountLog2];
// Create a random 8-character salt
const saltBytes = node_crypto_1.default.randomBytes(6); // 6 bytes to encode as 8 characters
const salt = encode64(saltBytes, 6, itoa64Str);
// Create hash setting string
const setting = `$P$${countChar}${salt}`;
return cryptPrivate(password, setting, itoa64Str);
}
/**
* Verifies a password against a hashed password.
*
* @param {string} password - The password to verify.
* @param {string} hash - The hashed password.
* @returns {boolean} True if the password matches the hash, otherwise false.
*/
function checkPassword(password, hash) {
const itoa64Str = itoa64();
if (hash.startsWith('$P$') || hash.startsWith('$H$')) {
const computedHash = cryptPrivate(password, hash, itoa64Str);
return computedHash === hash;
}
else {
// Unsupported hash format
return false;
}
}
;