smart_ims/server/utils/cryptoUtil.js
choibk 8b2589b6fa feat: 플랫폼 보안 강화, 권한 계층 시스템 도입 및 버전 관리 통합 (v0.2.5)
- 최고관리자(Supervisor) 전용 2중 보안 잠금 시스템 및 인증 UI 적용
- 데이터베이스 인프라 및 암호화 마스터 키 자가 관리 기능 구축
- 권한 계층(Supervisor > Admin > User) 기반의 메뉴 노출 및 접근 제어 로직 강화
- 시스템 버전 정보 페이지 신규 추가 및 패키지 버전 자동 연동 (v0.2.5)
- 사용자 관리 UI 디자인 개선 및 폰트/스타일 일원화
2026-01-24 17:17:33 +09:00

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;