- 최고관리자(Supervisor) 전용 2중 보안 잠금 시스템 및 인증 UI 적용 - 데이터베이스 인프라 및 암호화 마스터 키 자가 관리 기능 구축 - 권한 계층(Supervisor > Admin > User) 기반의 메뉴 노출 및 접근 제어 로직 강화 - 시스템 버전 정보 페이지 신규 추가 및 패키지 버전 자동 연동 (v0.2.5) - 사용자 관리 UI 디자인 개선 및 폰트/스타일 일원화
112 lines
4.0 KiB
JavaScript
112 lines
4.0 KiB
JavaScript
const crypto = require('crypto');
|
|
const db = require('../db');
|
|
|
|
const ALGORITHM = 'aes-256-cbc';
|
|
const SYSTEM_INTERNAL_KEY = 'ims_system_l2_internal_protection_key_2026'; // 고정 시스템 키 (2단계 보안용)
|
|
let cachedKey = null;
|
|
|
|
const cryptoUtil = {
|
|
/**
|
|
* 내부 보안용 암호화 (마스터 키 보호용)
|
|
*/
|
|
_internalProcess(text, isEncrypt = true) {
|
|
if (!text) return text;
|
|
try {
|
|
const keyBuffer = crypto.scryptSync(SYSTEM_INTERNAL_KEY, 'ims_salt', 32);
|
|
if (isEncrypt) {
|
|
const iv = crypto.randomBytes(16);
|
|
const cipher = crypto.createCipheriv(ALGORITHM, keyBuffer, iv);
|
|
let encrypted = cipher.update(text, 'utf8', 'hex');
|
|
encrypted += cipher.final('hex');
|
|
return iv.toString('hex') + ':' + encrypted;
|
|
} else {
|
|
if (!text.includes(':')) return text; // 평문인 경우 그대로 반환
|
|
const [ivHex, cipherText] = text.split(':');
|
|
const iv = Buffer.from(ivHex, 'hex');
|
|
const decipher = crypto.createDecipheriv(ALGORITHM, keyBuffer, iv);
|
|
let decrypted = decipher.update(cipherText, 'hex', 'utf8');
|
|
decrypted += decipher.final('utf8');
|
|
return decrypted;
|
|
}
|
|
} catch (e) {
|
|
return text;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Get key from DB or Cache
|
|
*/
|
|
async getMasterKey() {
|
|
if (cachedKey) return cachedKey;
|
|
try {
|
|
const [rows] = await db.query("SELECT setting_value FROM system_settings WHERE setting_key = 'encryption_key'");
|
|
if (rows.length > 0 && rows[0].setting_value) {
|
|
const rawValue = rows[0].setting_value;
|
|
// DB에 저장된 값이 암호화된 형태(iv 포함)라면 복호화하여 사용
|
|
if (rawValue.includes(':')) {
|
|
cachedKey = this._internalProcess(rawValue, false);
|
|
} else {
|
|
cachedKey = rawValue;
|
|
}
|
|
return cachedKey;
|
|
}
|
|
} catch (e) {
|
|
console.error('CryptoUtil: Failed to fetch key', e);
|
|
}
|
|
return process.env.ENCRYPTION_KEY || 'smartasset_secret_key_0123456789';
|
|
},
|
|
|
|
/**
|
|
* 마스터 키를 DB에 저장하기 전 암호화하는 함수
|
|
*/
|
|
encryptMasterKey(plainKey) {
|
|
return this._internalProcess(plainKey, true);
|
|
},
|
|
|
|
clearCache() {
|
|
cachedKey = null;
|
|
},
|
|
|
|
/**
|
|
* Encrypt with specific key (optional, defaults to master)
|
|
*/
|
|
async encrypt(text, customKey = null) {
|
|
if (!text) return text;
|
|
try {
|
|
const secret = customKey || await this.getMasterKey();
|
|
const keyBuffer = crypto.scryptSync(secret, 'salt', 32);
|
|
const iv = crypto.randomBytes(16);
|
|
const cipher = crypto.createCipheriv(ALGORITHM, keyBuffer, iv);
|
|
let encrypted = cipher.update(text, 'utf8', 'hex');
|
|
encrypted += cipher.final('hex');
|
|
return iv.toString('hex') + ':' + encrypted;
|
|
} catch (e) {
|
|
console.error('Encryption failed:', e.message);
|
|
return text;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Decrypt with specific key (optional, defaults to master)
|
|
*/
|
|
async decrypt(text, customKey = null) {
|
|
if (!text || !text.includes(':')) return text;
|
|
try {
|
|
const secret = customKey || await this.getMasterKey();
|
|
const keyBuffer = crypto.scryptSync(secret, 'salt', 32);
|
|
const [ivHex, cipherText] = text.split(':');
|
|
if (!ivHex || ivHex.length !== 32) return text;
|
|
|
|
const iv = Buffer.from(ivHex, 'hex');
|
|
const decipher = crypto.createDecipheriv(ALGORITHM, keyBuffer, iv);
|
|
let decrypted = decipher.update(cipherText, 'hex', 'utf8');
|
|
decrypted += decipher.final('utf8');
|
|
return decrypted;
|
|
} catch (e) {
|
|
return text;
|
|
}
|
|
}
|
|
};
|
|
|
|
module.exports = cryptoUtil;
|