| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105 |
- 'use strict';
- const bcrypt = require('bcryptjs');
- const crypto = require('crypto');
- module.exports = app => {
- class Bcrypt extends app.BaseService {
- /**
- * 生成 Bcrypt 加盐哈希(自动生成随机盐)
- * @param {string} plainPassword 明文密码
- * @return {Promise<string>} 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<string>} storedBcryptHash 数据库存储的哈希字段数组
- * @return {Promise<boolean>} 验证结果
- */
- 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;
- };
|