Files
2026-02-11 15:34:22 +08:00

125 lines
3.5 KiB
JavaScript

const crypto = require('crypto');
const dotenv = require('dotenv');
dotenv.config();
const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 16;
const AUTH_TAG_LENGTH = 16;
class EncryptionService {
constructor() {
let key = (process.env.ENCRYPTION_KEY || '').trim();
if (/^[0-9a-fA-F]{64}$/.test(key)) {
key = key.substring(0, 32);
}
if (!key || key.length !== 32) {
const raw = (process.env.ENCRYPTION_KEY || '').trim();
const preview = raw ? (raw.slice(0, 4) + '...' + raw.slice(-4)) : '(empty)';
const len = raw ? raw.length : 0;
let fingerprint = '';
try { fingerprint = crypto.createHash('sha256').update(raw).digest('hex').slice(0, 16); } catch {}
console.error('ENCRYPTION_KEY_RAW=', raw);
console.error('Invalid ENCRYPTION_KEY: length=%s, preview=%s, hex64=%s, fingerprint=%s', String(len), preview, /^[0-9a-fA-F]{64}$/.test(raw) ? 'true' : 'false', fingerprint);
if (String(process.env.ALLOW_WEAK_ENCRYPTION).toLowerCase() === 'true') {
const weak = (raw || '').padEnd(32, '0').slice(0, 32);
console.error('ALLOW_WEAK_ENCRYPTION enabled: using fallback key of length 32 to keep service alive');
this.encryptionKey = weak;
return;
}
throw new Error('ENCRYPTION_KEY must be exactly 32 bytes long');
}
this.encryptionKey = key;
}
/**
* 生成用户特定的加密密钥(基于用户ID)
*/
generateUserKey(userId) {
const userKey = crypto.createHash('sha256')
.update(this.encryptionKey + userId.toString())
.digest('hex')
.substring(0, 32);
return userKey;
}
/**
* 加密数据
*/
encrypt(data, userId) {
if (!data) return null;
const userKey = this.generateUserKey(userId);
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv(ALGORITHM, userKey, iv);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
// 返回 IV + AuthTag + EncryptedData
return iv.toString('hex') + authTag.toString('hex') + encrypted;
}
/**
* 解密数据
*/
decrypt(encryptedData, userId) {
if (!encryptedData) return null;
try {
const userKey = this.generateUserKey(userId);
// 提取 IV、AuthTag 和加密数据
const iv = Buffer.from(encryptedData.substring(0, IV_LENGTH * 2), 'hex');
const authTag = Buffer.from(
encryptedData.substring(IV_LENGTH * 2, IV_LENGTH * 2 + AUTH_TAG_LENGTH * 2),
'hex'
);
const encrypted = encryptedData.substring(IV_LENGTH * 2 + AUTH_TAG_LENGTH * 2);
const decipher = crypto.createDecipheriv(ALGORITHM, userKey, iv);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
} catch (error) {
console.error('Decryption failed:', error);
return null;
}
}
/**
* 加密服务器密码
*/
encryptPassword(password, userId) {
return this.encrypt(password, userId);
}
/**
* 解密服务器密码
*/
decryptPassword(encryptedPassword, userId) {
return this.decrypt(encryptedPassword, userId);
}
/**
* 加密私钥
*/
encryptPrivateKey(privateKey, userId) {
return this.encrypt(privateKey, userId);
}
/**
* 解密私钥
*/
decryptPrivateKey(encryptedPrivateKey, userId) {
return this.decrypt(encryptedPrivateKey, userId);
}
}
module.exports = new EncryptionService();