- damo-manager Gradle 프로젝트 초기 구성

- scpdb 부재 시 fake 모드 자동 fallback
- ScpEncB64/DecB64/HashB64 래핑 + 유틸 보존
- RealBridge 격리로 NoClassDefFoundError 방지

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rinjae
2026-06-04 14:07:01 +09:00
commit 6c0a57c237
7 changed files with 662 additions and 0 deletions
@@ -0,0 +1,363 @@
package com.eactive.apim.custom.djb.damomanager;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
/**
* 운영(penta scpdb 라이브러리 존재) / 로컬(scpdb 부재) 양쪽에서 동작하는 단일 클래스.
*
* 클래스패스에 {@code com.penta.scpdb.ScpDbAgent} 가 존재하면 실 호출, 아니면 fake 모드로 자동 fallback.
* fake 모드 진입 시 클래스 로딩 시점에 System.err 로 강한 경고 배너 출력.
*
* fake 모드 동작:
* - encrypt(s) : Base64.encode(s.getBytes(EUC-KR)) -- 암호화 없음
* - decrypt(s) : new String(Base64.decode(s), EUC-KR)
* - hash(t, s) : SHA-256(71) / SHA-512(73) JRE MessageDigest -> Base64
*/
public class DamoManager {
public static final int SHA256 = 71;
public static final int SHA512 = 73;
private static final String INI_FILE = "/prod/damo/ba_scp/scpdb_agent.ini";
private static final String KEY = "KEY1";
private static final String CHARSET = "EUC-KR";
private static final int SCP_OK = 0;
private static final int SCP_FAIL = -1;
private static final boolean REAL_MODE;
static {
boolean real;
try {
Class.forName("com.penta.scpdb.ScpDbAgent", false,
DamoManager.class.getClassLoader());
real = true;
} catch (Throwable t) {
real = false;
printFakeBanner();
}
REAL_MODE = real;
}
private static void printFakeBanner() {
String line = "============================================================";
System.err.println();
System.err.println(line);
System.err.println(" *** DAMO-MANAGER FAKE MODE ***");
System.err.println(" com.penta.scpdb.ScpDbAgent NOT FOUND on classpath.");
System.err.println(" Fallback:");
System.err.println(" encrypt/decrypt -> Base64 (NO ENCRYPTION)");
System.err.println(" hash -> JRE MessageDigest (SHA-256 / SHA-512)");
System.err.println(" *** DO NOT use this build for production! ***");
System.err.println(line);
System.err.println();
System.err.flush();
}
private final String szRevision = "20160503";
private int nReturn;
private final RealBridge bridge;
public DamoManager() {
if (REAL_MODE) {
this.bridge = new RealBridge();
int rc = this.bridge.init();
if (rc == 0 || rc == 118) {
this.nReturn = SCP_OK;
} else {
this.nReturn = rc;
}
} else {
this.bridge = null;
this.nReturn = SCP_OK;
}
}
public String getRevision() {
return this.szRevision;
}
public int getReturnCode() {
return this.nReturn;
}
public boolean isFakeMode() {
return !REAL_MODE;
}
// -----------------------------------------------------------------
// 운영: ScpEncB64 / ScpDecB64 / ScpHashB64 래핑
// -----------------------------------------------------------------
/**
* 평문 문자열을 ScpEncB64 로 암호화하여 Base64 cipher text 반환.
* fake 모드: 단순 Base64 인코딩.
*/
public String encrypt(String plain) {
if (plain == null) {
this.nReturn = SCP_FAIL;
return "";
}
if (REAL_MODE) {
try {
String out = this.bridge.encB64(plain);
this.nReturn = SCP_OK;
return out;
} catch (Throwable t) {
t.printStackTrace();
this.nReturn = SCP_FAIL;
return "";
}
}
try {
this.nReturn = SCP_OK;
return Base64.getEncoder().encodeToString(plain.getBytes(CHARSET));
} catch (UnsupportedEncodingException e) {
this.nReturn = SCP_FAIL;
return Base64.getEncoder().encodeToString(plain.getBytes());
}
}
/**
* Base64 cipher text 를 ScpDecB64 로 복호화하여 평문 반환.
* fake 모드: 단순 Base64 디코딩.
*/
public String decrypt(String base64Cipher) {
if (base64Cipher == null) {
this.nReturn = SCP_FAIL;
return "";
}
if (REAL_MODE) {
try {
String out = this.bridge.decB64(base64Cipher);
this.nReturn = SCP_OK;
return out;
} catch (Throwable t) {
t.printStackTrace();
this.nReturn = SCP_FAIL;
return "";
}
}
try {
byte[] decoded = Base64.getDecoder().decode(base64Cipher);
this.nReturn = SCP_OK;
try {
return new String(decoded, CHARSET);
} catch (UnsupportedEncodingException e) {
return new String(decoded);
}
} catch (IllegalArgumentException e) {
this.nReturn = SCP_FAIL;
return "";
}
}
/**
* 단방향 해시. type 71 = SHA-256, 73 = SHA-512. 결과는 Base64 문자열.
* fake 모드: JRE MessageDigest 사용 (실제 SHA-256/512 결과를 Base64 로 반환).
*/
public String hash(int type, String src) {
if (src == null) {
this.nReturn = SCP_FAIL;
return "";
}
if (REAL_MODE) {
try {
String out = this.bridge.hashB64(type, src);
this.nReturn = SCP_OK;
return out;
} catch (Throwable t) {
t.printStackTrace();
this.nReturn = SCP_FAIL;
return "";
}
}
String algo;
if (type == SHA256) {
algo = "SHA-256";
} else if (type == SHA512) {
algo = "SHA-512";
} else {
this.nReturn = SCP_FAIL;
return "";
}
try {
MessageDigest md = MessageDigest.getInstance(algo);
byte[] bytes;
try {
bytes = md.digest(src.getBytes(CHARSET));
} catch (UnsupportedEncodingException e) {
bytes = md.digest(src.getBytes());
}
this.nReturn = SCP_OK;
return Base64.getEncoder().encodeToString(bytes);
} catch (NoSuchAlgorithmException e) {
this.nReturn = SCP_FAIL;
return "";
}
}
// -----------------------------------------------------------------
// Utility (원본 DamoManager 에서 그대로 보존)
// -----------------------------------------------------------------
public String Base64Encode(byte[] var1) {
char[] var2 = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
StringBuilder var3 = new StringBuilder();
int var4 = 0;
for (int var5 = 0; var5 < var1.length; var5 += 3) {
int var6 = (var1[var5] & 255) << 16 & 16777215;
if (var5 + 1 < var1.length) {
var6 |= (var1[var5 + 1] & 255) << 8;
} else {
++var4;
}
if (var5 + 2 < var1.length) {
var6 |= var1[var5 + 2] & 255;
} else {
++var4;
}
for (int var7 = 0; var7 < 4 - var4; ++var7) {
int var8 = (var6 & 16515072) >> 18;
var3.append(var2[var8]);
var6 <<= 6;
}
}
for (int var9 = 0; var9 < var4; ++var9) {
var3.append("=");
}
return var3.toString();
}
public byte[] Base64Decode(String var1) {
int[] var2 = new int[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
byte[] var3 = var1.getBytes();
ByteArrayOutputStream var4 = new ByteArrayOutputStream();
int var5 = 0;
while (var5 < var3.length) {
int var6 = 0;
if (var2[var3[var5]] != -1) {
var6 = (var2[var3[var5]] & 255) << 18;
int var7 = 0;
if (var5 + 1 < var3.length && var2[var3[var5 + 1]] != -1) {
var6 |= (var2[var3[var5 + 1]] & 255) << 12;
++var7;
}
if (var5 + 2 < var3.length && var2[var3[var5 + 2]] != -1) {
var6 |= (var2[var3[var5 + 2]] & 255) << 6;
++var7;
}
if (var5 + 3 < var3.length && var2[var3[var5 + 3]] != -1) {
var6 |= var2[var3[var5 + 3]] & 255;
++var7;
}
while (var7 > 0) {
int var8 = (var6 & 16711680) >> 16;
var4.write((char) var8);
var6 <<= 8;
--var7;
}
var5 += 4;
} else {
++var5;
}
}
return var4.toByteArray();
}
public String byteToHex(byte[] var1) {
String var2 = "";
String var3 = "";
for (int var4 = 0; var4 < var1.length; ++var4) {
var3 = Integer.toHexString(var1[var4]);
if (var3.length() >= 2) {
var3 = var3.substring(var3.length() - 2);
} else {
for (int var5 = 0; var5 < 2 - var3.length(); ++var5) {
var3 = "0" + var3;
}
}
var2 = var2 + " " + var3;
}
return var2.toUpperCase();
}
public String byteArrayToHex(byte[] var1) {
StringBuilder var2 = new StringBuilder();
for (byte var6 : var1) {
var2.append(String.format("%02x ", var6 & 255));
}
return var2.toString();
}
public boolean checkHangul(char var1) {
return var1 >= '가' && var1 <= '힣';
}
// -----------------------------------------------------------------
// 운영 ScpDbAgent 호출 격리. REAL_MODE == true 일 때만 인스턴스화되어
// ScpDbAgent 심볼이 JVM 에 resolve 된다. fake 모드에서는 이 클래스 자체가
// 로딩되지 않으므로 NoClassDefFoundError 가 발생하지 않는다.
// -----------------------------------------------------------------
private static final class RealBridge {
private final com.penta.scpdb.ScpDbAgent agt = new com.penta.scpdb.ScpDbAgent();
int init() {
return agt.AgentInit(INI_FILE);
}
String encB64(String src) {
return agt.ScpEncB64(INI_FILE, KEY, src, CHARSET);
}
String decB64(String src) {
return agt.ScpDecB64(INI_FILE, KEY, src, CHARSET);
}
String hashB64(int type, String src) {
return agt.ScpHashB64(type, src, CHARSET);
}
}
// -----------------------------------------------------------------
// Smoke test 용 main. 운영 코드에서는 호출되지 않음.
// -----------------------------------------------------------------
public static void main(String[] args) {
DamoManager m = new DamoManager();
System.out.println("revision = " + m.getRevision());
System.out.println("fakeMode = " + m.isFakeMode());
String plain = "hello, 다모";
String enc = m.encrypt(plain);
String dec = m.decrypt(enc);
System.out.println("encrypt = " + enc);
System.out.println("decrypt = " + dec);
System.out.println("roundtrip = " + plain.equals(dec));
String h256 = m.hash(SHA256, plain);
String h512 = m.hash(SHA512, plain);
System.out.println("sha256 b64 = " + h256 + " (len=" + h256.length() + ")");
System.out.println("sha512 b64 = " + h512 + " (len=" + h512.length() + ")");
}
}