/** * 密码加密和验证工具函数 * @module services/utils/password */ const crypto = require('crypto') const N = 16384 const r = 8 const p = 1 const KEYLEN = 64 const SALT_LEN = 16 const MD5_LEN = 32 /** * 计算 MD5 哈希 * @param {string} str - 输入字符串 * @returns {string} MD5 哈希值(十六进制) */ function md5(str) { return crypto.createHash('md5').update(str).digest('hex') } /** * 使用 scrypt 计算哈希 * @param {string} password - 密码 * @param {Buffer} [saltBuf] - 可选的 salt * @returns {Object} { salt, hash } */ function scryptHash(password, saltBuf) { const salt = saltBuf || crypto.randomBytes(SALT_LEN) const derived = crypto.scryptSync(String(password), salt, KEYLEN, { N, r, p, maxmem: 64 * 1024 * 1024 }) return { salt, hash: derived } } /** * 哈希密码(使用 scrypt) * @param {string} password - 密码 * @returns {string} 编码后的密码哈希 */ function hashPassword(password) { const { salt, hash } = scryptHash(password) const saltB64 = salt.toString('base64').replace(/=+$/, '') const hashB64 = hash.toString('base64').replace(/=+$/, '') return `scrypt$${N}$${r}$${p}$${saltB64}$${hashB64}` } /** * 验证 scrypt 密码 * @param {string} password - 密码 * @param {string} encoded - 编码后的密码哈希 * @returns {boolean} 是否验证通过 */ function verifyScrypt(password, encoded) { try { const parts = encoded.split('$') if (parts.length !== 6 || parts[0] !== 'scrypt') return false const saltB64 = parts[4] const expectedB64 = parts[5] const salt = Buffer.from(saltB64, 'base64') const expected = Buffer.from(expectedB64, 'base64') const { hash } = scryptHash(password, salt) if (hash.length !== expected.length) return false return crypto.timingSafeEqual(hash, expected) } catch { return false } } /** * 验证密码(支持 scrypt 和旧版 MD5) * @param {string} password - 密码 * @param {string} stored - 存储的密码哈希 * @returns {boolean} 是否验证通过 */ function verifyPassword(password, stored) { if (!stored) return false if (stored.startsWith('scrypt$')) return verifyScrypt(password, stored) if (/^[a-f0-9]{32}$/i.test(stored)) return md5(password).toLowerCase() === stored.toLowerCase() return false } /** * 检查是否是旧版 MD5 哈希 * @param {string} stored - 存储的密码哈希 * @returns {boolean} 是否是旧版哈希 */ function isLegacyHash(stored) { return stored && /^[a-f0-9]{32}$/i.test(stored) } /** * 检查是否需要重新哈希密码 * @param {string} stored - 存储的密码哈希 * @returns {boolean} 是否需要重新哈希 */ function needsRehash(stored) { return !stored || !stored.startsWith('scrypt$') } module.exports = { hashPassword, verifyPassword, isLegacyHash, needsRehash, md5 }