import { Injectable, Logger } from '@nestjs/common'; import * as crypto from 'crypto'; @Injectable() export class EncryptionService { private readonly logger = new Logger(EncryptionService.name); private readonly algorithm = 'aes-256-cbc'; private readonly keyLength = 32; // 256 bits for AES private readonly hmacKeyLength = 32; // 256 bits for HMAC private readonly ivLength = 16; // 128 bits for CBC private readonly hmacLength = 32; // 256 bits for SHA256 // 从环境变量获取密钥,或使用默认密钥(生产环境必须使用环境变量) private readonly encryptionKey: Buffer; private readonly hmacKey: Buffer; constructor() { const encryptionKeyString = process.env.ENCRYPTION_KEY || 'your-default-32-char-aes-secret-key!'; const hmacKeyString = process.env.HMAC_KEY || 'your-default-32-char-hmac-secret-key'; this.logger.log(`EncryptionService constructor encryptionKeyString: ${encryptionKeyString}`); this.logger.log(`EncryptionService constructor hmacKeyString: ${hmacKeyString}`); // 两个独立的密钥 this.encryptionKey = Buffer.from(encryptionKeyString.slice(0, 32).padEnd(32, '0'), 'utf8'); this.hmacKey = Buffer.from(hmacKeyString.slice(0, 32).padEnd(32, '0'), 'utf8'); } /** * 加密数据 * @param plaintext 要加密的明文 * @returns 加密后的base64字符串,格式:iv.ciphertext.hmac */ encrypt(plaintext: string): string { try { const iv = crypto.randomBytes(this.ivLength); const cipher = crypto.createCipheriv(this.algorithm, this.encryptionKey, iv); let ciphertext = cipher.update(plaintext, 'utf8'); ciphertext = Buffer.concat([ciphertext, cipher.final()]); // 计算 HMAC (对 iv + ciphertext 进行HMAC) const dataToHmac = Buffer.concat([iv, ciphertext]); const hmac = crypto.createHmac('sha256', this.hmacKey); hmac.update(dataToHmac); const hmacDigest = hmac.digest(); // 组合 iv + ciphertext + hmac 并编码为base64 const combined = Buffer.concat([iv, ciphertext, hmacDigest]); return combined.toString('base64'); } catch (error) { throw new Error(`加密失败: ${error.message}`); } } /** * 解密数据 * @param encryptedData 加密的base64字符串 * @returns 解密后的明文 */ decrypt(encryptedData: string): string { try { const combined = Buffer.from(encryptedData, 'base64'); // 检查数据长度是否足够 if (combined.length < this.ivLength + this.hmacLength) { throw new Error('加密数据格式无效'); } // 提取 iv, ciphertext, hmac const iv = combined.slice(0, this.ivLength); const hmacDigest = combined.slice(-this.hmacLength); const ciphertext = combined.slice(this.ivLength, -this.hmacLength); // 验证 HMAC const dataToHmac = Buffer.concat([iv, ciphertext]); const hmac = crypto.createHmac('sha256', this.hmacKey); hmac.update(dataToHmac); const expectedHmac = hmac.digest(); if (!crypto.timingSafeEqual(hmacDigest, expectedHmac)) { throw new Error('HMAC验证失败,数据可能被篡改'); } // 解密 const decipher = crypto.createDecipheriv(this.algorithm, this.encryptionKey, iv); let plaintext = decipher.update(ciphertext, undefined, 'utf8'); plaintext += decipher.final('utf8'); return plaintext; } catch (error) { throw new Error(`解密失败: ${error.message}`); } } /** * 生成新的加密密钥(用于初始化) * 返回两个独立的密钥 */ generateKey(): { encryptionKey: string; hmacKey: string } { return { encryptionKey: crypto.randomBytes(this.keyLength).toString('base64'), hmacKey: crypto.randomBytes(this.hmacKeyLength).toString('base64') }; } }