57 lines
1.8 KiB
TypeScript
57 lines
1.8 KiB
TypeScript
/**
|
||
* 密码哈希与校验(仅用于 Web 用户注册/登录,与后台管理员密码无关)
|
||
* 使用 Node crypto.scrypt,存储格式 saltHex:hashHex,兼容旧明文密码
|
||
*/
|
||
|
||
import { scryptSync, timingSafeEqual, randomFillSync } from 'crypto'
|
||
|
||
const SALT_LEN = 16
|
||
const KEYLEN = 32
|
||
|
||
function bufferToHex(buf: Buffer): string {
|
||
return buf.toString('hex')
|
||
}
|
||
|
||
function hexToBuffer(hex: string): Buffer {
|
||
return Buffer.from(hex, 'hex')
|
||
}
|
||
|
||
/**
|
||
* 对明文密码做哈希,存入数据库
|
||
* 格式: saltHex:hashHex(约 97 字符,适配 VARCHAR(100))
|
||
* 与 verifyPassword 一致:内部先 trim,保证注册/登录/重置用同一套规则
|
||
*/
|
||
export function hashPassword(plain: string): string {
|
||
const trimmed = String(plain).trim()
|
||
const salt = Buffer.allocUnsafe(SALT_LEN)
|
||
randomFillSync(salt)
|
||
const hash = scryptSync(trimmed, salt, KEYLEN, { N: 16384, r: 8, p: 1 })
|
||
return bufferToHex(salt) + ':' + bufferToHex(hash)
|
||
}
|
||
|
||
/**
|
||
* 校验密码:支持新格式(salt:hash)与旧明文(兼容历史数据)
|
||
* 与 hashPassword 一致:对输入先 trim 再参与校验
|
||
*/
|
||
export function verifyPassword(plain: string, stored: string | null | undefined): boolean {
|
||
const trimmed = String(plain).trim()
|
||
if (stored == null || stored === '') {
|
||
return trimmed === ''
|
||
}
|
||
if (stored.includes(':')) {
|
||
const [saltHex, hashHex] = stored.split(':')
|
||
if (!saltHex || !hashHex || saltHex.length !== SALT_LEN * 2 || hashHex.length !== KEYLEN * 2) {
|
||
return false
|
||
}
|
||
try {
|
||
const salt = hexToBuffer(saltHex)
|
||
const expected = hexToBuffer(hashHex)
|
||
const derived = scryptSync(trimmed, salt, KEYLEN, { N: 16384, r: 8, p: 1 })
|
||
return derived.length === expected.length && timingSafeEqual(derived, expected)
|
||
} catch {
|
||
return false
|
||
}
|
||
}
|
||
return trimmed === stored
|
||
}
|