'use strict'; const bcrypt = require('bcryptjs'); const crypto = require('crypto'); module.exports = app => { class Bcrypt extends app.BaseService { /** * 生成 Bcrypt 加盐哈希(自动生成随机盐) * @param {string} plainPassword 明文密码 * @return {Promise} bcrypt 加密后的哈希字符串 */ async generateBcryptHash(plainPassword) { try { const salt = await bcrypt.genSalt(10); // 纯 JS 异步生成,无阻塞 // 2. 生成密码哈希(盐值自动拼接在哈希结果中,无需单独存储盐值) const hashedPassword = await bcrypt.hash(plainPassword, salt); return hashedPassword; } catch (error) { console.error('Bcrypt 哈希生成失败:', error); throw new Error('密码加密失败'); // 生产环境可封装为自定义错误 } } /** * 验证 Bcrypt 哈希 * @param {string} plainPassword 明文密码 * @param {array} storedBcryptHash 数据库存储的哈希字段数组 * @return {Promise} 验证结果 */ async verifyBcryptHash(plainPassword, storedBcryptHash) { // 支持传入单个哈希字符串(推荐),也兼容老的数组形式 const candidates = Array.isArray(storedBcryptHash) ? storedBcryptHash : [storedBcryptHash]; if (!Array.isArray(candidates) || candidates.length === 0) return false; for (const raw of candidates) { if (!raw || typeof raw !== 'string') continue; const bcryptHash = raw; // 如果以 bcrypt 前缀开头,则按 bcrypt hash 验证;否则视为加密的明文(使用 decrypt 解密后直接比较明文) if (bcryptHash.startsWith('$2')) { try { const isMatch = await bcrypt.compare(plainPassword, bcryptHash); if (isMatch) return true; } catch (error) { if (app && app.logger && app.logger.warn) app.logger.warn('bcrypt compare error', error); } } else { try { const decrypted = this.decrypt(bcryptHash); if (decrypted === plainPassword) return true; } catch (err) { if (app && app.logger && app.logger.warn) app.logger.warn('bcrypt decrypt failed for candidate'); continue; } } } return false; } /** * AES-256-CBC 加密函数(用于存储副密码) * @param {string} plainText - 明文(客户副密码) * @return {string} - 加密后的密文(base64 格式,便于数据库存储) */ encrypt(plainText) { if (!plainText || typeof plainText !== 'string') { throw new Error('明文必须为非空字符串'); } // 创建加密器 const cipher = crypto.createCipheriv( app.config.aes.algorithm, Buffer.from(app.config.aes.secretKey, 'utf8'), Buffer.from(app.config.aes.secretIv, 'utf8') ); // 执行加密 let encrypted = cipher.update(plainText, 'utf8', 'base64'); encrypted += cipher.final('base64'); return encrypted; } /** * AES-256-CBC 解密函数(用于销售查询副密码) * @param {string} cipherText - 密文(数据库中存储的加密后副密码) * @return {string} - 解密后的明文(客户副密码) */ decrypt(cipherText) { if (!cipherText || typeof cipherText !== 'string') { throw new Error('密文必须为非空字符串'); } // 创建解密器 const decipher = crypto.createDecipheriv( app.config.aes.algorithm, Buffer.from(app.config.aes.secretKey, 'utf8'), Buffer.from(app.config.aes.secretIv, 'utf8') ); // 执行解密 let decrypted = decipher.update(cipherText, 'base64', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } } return Bcrypt; };