Files
services/utils/password.js
T
2026-06-03 14:15:55 +08:00

108 lines
2.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 密码加密和验证工具函数
* @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
}