/** * 加密工具函数 * 提供密码加密、验证等安全相关功能 */ const crypto = require('crypto'); // 配置参数 const SALT_LENGTH = 16; // salt 长度 const KEY_LENGTH = 64; // 密钥长度 const ITERATIONS = 100000; // 迭代次数 const DIGEST = 'sha256'; // 摘要算法 /** * 生成密码哈希 * @param {string} password - 明文密码 * @returns {Promise} 加密后的密码字符串 (格式: salt$hash) */ async function hashPassword(password) { if (!password || typeof password !== 'string') { throw new Error('密码不能为空'); } return new Promise((resolve, reject) => { // 生成随机 salt const salt = crypto.randomBytes(SALT_LENGTH).toString('hex'); // 使用 pbkdf2 生成密钥 crypto.pbkdf2(password, salt, ITERATIONS, KEY_LENGTH, DIGEST, (err, derivedKey) => { if (err) { reject(err); } else { const hash = derivedKey.toString('hex'); // 返回格式: salt$hash resolve(`${salt}$${hash}`); } }); }); } /** * 验证密码 * @param {string} password - 明文密码 * @param {string} hashedPassword - 加密后的密码 (格式: salt$hash) * @returns {Promise} 是否匹配 */ async function verifyPassword(password, hashedPassword) { if (!password || !hashedPassword) { return false; } return new Promise((resolve, reject) => { try { // 解析 salt 和 hash const parts = hashedPassword.split('$'); if (parts.length !== 2) { resolve(false); return; } const [salt, originalHash] = parts; // 使用相同的 salt 生成密钥 crypto.pbkdf2(password, salt, ITERATIONS, KEY_LENGTH, DIGEST, (err, derivedKey) => { if (err) { reject(err); } else { const hash = derivedKey.toString('hex'); // 使用恒定时间比较,防止时序攻击 resolve(crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(originalHash))); } }); } catch (error) { reject(error); } }); } /** * 生成安全的随机 token * @param {number} length - token 长度(字节数),默认 32 * @returns {string} 十六进制字符串 */ function generateToken(length = 32) { return crypto.randomBytes(length).toString('hex'); } /** * 生成设备 ID * @param {string} prefix - 前缀,默认 'device' * @returns {string} 设备 ID */ function generateDeviceId(prefix = 'device') { const timestamp = Date.now(); const random = crypto.randomBytes(8).toString('hex'); return `${prefix}_${timestamp}_${random}`; } /** * 验证设备 ID 格式 * @param {string} deviceId - 设备 ID * @returns {boolean} 是否有效 */ function validateDeviceId(deviceId) { if (!deviceId || typeof deviceId !== 'string') { return false; } // 检查格式: prefix_timestamp_randomhex const pattern = /^[a-z]+_\d{13}_[a-f0-9]{16}$/i; return pattern.test(deviceId); } /** * 脱敏处理 - 手机号 * @param {string} phone - 手机号 * @returns {string} 脱敏后的手机号 */ function maskPhone(phone) { if (!phone || typeof phone !== 'string') { return ''; } if (phone.length < 11) { return phone; } return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'); } /** * 脱敏处理 - 邮箱 * @param {string} email - 邮箱 * @returns {string} 脱敏后的邮箱 */ function maskEmail(email) { if (!email || typeof email !== 'string') { return ''; } const parts = email.split('@'); if (parts.length !== 2) { return email; } const [username, domain] = parts; if (username.length <= 2) { return `*@${domain}`; } const masked = username[0] + '***' + username[username.length - 1]; return `${masked}@${domain}`; } /** * 脱敏处理 - 通用对象(用于日志) * @param {Object} obj - 要脱敏的对象 * @param {Array} sensitiveFields - 敏感字段列表 * @returns {Object} 脱敏后的对象 */ function maskSensitiveData(obj, sensitiveFields = ['password', 'pwd', 'token', 'secret', 'key']) { if (!obj || typeof obj !== 'object') { return obj; } const masked = { ...obj }; for (const field of sensitiveFields) { if (masked[field]) { masked[field] = '***MASKED***'; } } return masked; } module.exports = { hashPassword, verifyPassword, generateToken, generateDeviceId, validateDeviceId, maskPhone, maskEmail, maskSensitiveData };