125 lines
3.5 KiB
JavaScript
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();
|