- 실패 처리 정책 개선: encrypt/hash 실패 시 예외 처리 추가

- decrypt 실패 시 입력 반환 및 경고 출력 방식 도입
- 실패 처리 관련 README 및 szRevision 최신화
This commit is contained in:
Rinjae
2026-08-05 17:42:34 +09:00
parent c86c980b43
commit 8335fd9159
2 changed files with 71 additions and 10 deletions
+12 -2
View File
@@ -81,7 +81,7 @@ java -jar damo-manager.jar
damo> enc hello damo> enc hello
aGVsbG8= aGVsbG8=
damo> info damo> info
mode=FAKE revision=20260604 returnCode=0 mode=FAKE revision=20260805 returnCode=0
damo> quit damo> quit
# 파라미터식: 1회 실행 # 파라미터식: 1회 실행
@@ -101,6 +101,11 @@ java -Ddamo-manager.enabled=true -jar damo-manager.jar enc "hello" # scpdb 있
# 운영: real 강제 (scpdb 없으면 기동 실패) # 운영: real 강제 (scpdb 없으면 기동 실패)
java -Ddamo-manager.require-real=true -jar damo-manager.jar info java -Ddamo-manager.require-real=true -jar damo-manager.jar info
# decrypt 실패 폴백: 암호문이 아닌 입력은 그대로 에코되고 stderr 에 경고(마스킹) 출력
java -Ddamo-manager.enabled=true -jar damo-manager.jar dec 'not-real-cipher=='
# stdout: not-real-cipher==
# stderr: [damo-manager] WARN decrypt failed in fake mode; returning input as-is ...
``` ```
명령: `enc|encrypt`, `dec|decrypt`, `sha256|hash256`, `sha512|hash512`, `info|mode`, `help|?`, (대화식) `quit|exit`. 알 수 없는 명령은 stderr 안내 후 exit code 2. 명령: `enc|encrypt`, `dec|decrypt`, `sha256|hash256`, `sha512|hash512`, `info|mode`, `help|?`, (대화식) `quit|exit`. 알 수 없는 명령은 stderr 안내 후 exit code 2.
@@ -240,6 +245,11 @@ int rc = damo2.getReturnCode(); // 0: 성공, -1:
| `String decrypt(String b64)` | `ScpDecB64(INI_FILE, "KEY1", b64, "UTF-8")` | `new String(Base64.decode(b64), "UTF-8")` | 입력 그대로 | | `String decrypt(String b64)` | `ScpDecB64(INI_FILE, "KEY1", b64, "UTF-8")` | `new String(Base64.decode(b64), "UTF-8")` | 입력 그대로 |
| `String hash(int type, String src)` | `ScpHashB64(INI_FILE, type, src)` | `MessageDigest(SHA-256/512).digest(src) → Base64` | `src` (원문) | | `String hash(int type, String src)` | `ScpHashB64(INI_FILE, type, src)` | `MessageDigest(SHA-256/512).digest(src) → Base64` | `src` (원문) |
**실패 처리 정책** (bypass 는 실패 경로 없음):
- `decrypt` 실패(real 예외·빈 결과, fake Base64 디코딩 실패): **입력을 그대로 반환**하고 `returnCode=-1`, System.err 에 경고(입력은 앞 3자+길이만 마스킹 출력). 실패 원인은 대부분 "입력이 암호문이 아님"(레거시 평문이 Base64 형식으로 오판)이므로 원문을 보존해야 호출 측 평문 폴백/재암호화가 동작한다. 빈 문자열 반환은 오판된 평문을 빈 값으로 만들어 데이터 파괴로 이어진다.
- `encrypt`/`hash` 실패(real 예외·빈 결과): **`IllegalStateException` throw.** 암호화/해시 실패는 시스템 장애(ini/키/에이전트)이며, 빈 문자열을 조용히 반환하면 빈 비밀번호/빈 암호문이 DB에 저장되므로 저장 자체를 실패시킨다.
해시 `type` 상수: 해시 `type` 상수:
| 상수 | 값 | 알고리즘 | | 상수 | 값 | 알고리즘 |
@@ -282,7 +292,7 @@ gradle runFakeSmoke # -Ddamo-manager.enabled=true (fake)
... ...
============================================================ ============================================================
revision = 20260604 revision = 20260805
bypassMode = false bypassMode = false
fakeMode = true fakeMode = true
encrypt = aGVsbG8sIOuLpOuqqA== encrypt = aGVsbG8sIOuLpOuqqA==
@@ -35,6 +35,16 @@ import java.util.Base64;
* - encrypt(s) : Base64.encode(s.getBytes(UTF-8)) -- 암호화 없음 * - encrypt(s) : Base64.encode(s.getBytes(UTF-8)) -- 암호화 없음
* - decrypt(s) : new String(Base64.decode(s), UTF-8) * - decrypt(s) : new String(Base64.decode(s), UTF-8)
* - hash(t, s) : SHA-256(71) / SHA-512(73) JRE MessageDigest -> Base64 * - hash(t, s) : SHA-256(71) / SHA-512(73) JRE MessageDigest -> Base64
*
* 실패 처리 정책 (real/fake 공통, bypass 는 실패 경로 없음):
* - decrypt 실패 (real 예외·빈 결과, fake Base64 디코딩 실패) :
* 입력을 그대로 반환하고 returnCode=-1, System.err 경고(입력은 앞 3자+길이만 마스킹 출력).
* 실패 원인은 대부분 "입력이 암호문이 아님"(레거시 평문이 Base64 형식으로 오판된 경우)이므로
* 원문을 보존해야 컨버터/마이그레이션의 평문 폴백이 동작한다. 빈 문자열을 반환하면 오판된
* 평문이 빈 값으로 읽히고 일괄 마이그레이션이 빈 값으로 UPDATE 하는 데이터 파괴로 이어진다.
* - encrypt/hash 실패 (real 예외·빈 결과) : IllegalStateException 을 던진다.
* 암호화/해시 실패는 시스템 장애(ini/키/에이전트)이며, 빈 문자열을 조용히 반환하면
* 빈 비밀번호/빈 암호문이 DB에 저장되는 무결성 파괴로 이어지므로 저장 자체를 실패시킨다.
*/ */
public class DamoManager { public class DamoManager {
@@ -141,7 +151,7 @@ public class DamoManager {
System.err.flush(); System.err.flush();
} }
private final String szRevision = "20260604"; private final String szRevision = "20260805";
private int nReturn; private int nReturn;
private final RealBridge bridge; private final RealBridge bridge;
@@ -195,6 +205,7 @@ public class DamoManager {
/** /**
* 평문 문자열을 ScpEncB64 로 암호화하여 Base64 cipher text 반환. * 평문 문자열을 ScpEncB64 로 암호화하여 Base64 cipher text 반환.
* fake 모드: 단순 Base64 인코딩. * fake 모드: 단순 Base64 인코딩.
* real 실패(예외·빈 결과) 시 IllegalStateException — 빈 암호문이 DB에 저장되는 것을 막는다.
*/ */
public String encrypt(String plain) { public String encrypt(String plain) {
if (plain == null) { if (plain == null) {
@@ -208,12 +219,19 @@ public class DamoManager {
if (REAL_MODE) { if (REAL_MODE) {
try { try {
String out = this.bridge.encB64(plain); String out = this.bridge.encB64(plain);
if (out == null || out.isEmpty()) {
this.nReturn = SCP_FAIL;
throw new IllegalStateException(
"[damo-manager] encrypt failed in real mode: ScpEncB64 returned empty result");
}
this.nReturn = SCP_OK; this.nReturn = SCP_OK;
return out; return out;
} catch (IllegalStateException e) {
throw e;
} catch (Throwable t) { } catch (Throwable t) {
t.printStackTrace();
this.nReturn = SCP_FAIL; this.nReturn = SCP_FAIL;
return ""; throw new IllegalStateException(
"[damo-manager] encrypt failed in real mode: " + t.getClass().getName(), t);
} }
} }
try { try {
@@ -228,6 +246,8 @@ public class DamoManager {
/** /**
* Base64 cipher text 를 ScpDecB64 로 복호화하여 평문 반환. * Base64 cipher text 를 ScpDecB64 로 복호화하여 평문 반환.
* fake 모드: 단순 Base64 디코딩. * fake 모드: 단순 Base64 디코딩.
* 실패(real 예외·빈 결과, fake 디코딩 실패) 시 입력을 그대로 반환하고 returnCode=-1.
* 레거시 평문이 암호문으로 오판된 경우 원문이 보존되어야 평문 폴백/재암호화가 동작한다.
*/ */
public String decrypt(String base64Cipher) { public String decrypt(String base64Cipher) {
if (base64Cipher == null) { if (base64Cipher == null) {
@@ -241,12 +261,18 @@ public class DamoManager {
if (REAL_MODE) { if (REAL_MODE) {
try { try {
String out = this.bridge.decB64(base64Cipher); String out = this.bridge.decB64(base64Cipher);
if (out == null || out.isEmpty()) {
// ScpDecB64 가 예외 없이 실패(빈 결과)하는 경우도 입력 원문 반환으로 방어
this.nReturn = SCP_FAIL;
warnDecryptFallback("real", base64Cipher, null);
return base64Cipher;
}
this.nReturn = SCP_OK; this.nReturn = SCP_OK;
return out; return out;
} catch (Throwable t) { } catch (Throwable t) {
t.printStackTrace();
this.nReturn = SCP_FAIL; this.nReturn = SCP_FAIL;
return ""; warnDecryptFallback("real", base64Cipher, t);
return base64Cipher; // 실패 시 입력 원문 반환 (레거시 평문 오판 가능성)
} }
} }
try { try {
@@ -259,13 +285,31 @@ public class DamoManager {
} }
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
this.nReturn = SCP_FAIL; this.nReturn = SCP_FAIL;
return ""; warnDecryptFallback("fake", base64Cipher, e);
return base64Cipher; // fake 도 동일: 실패 시 입력 원문 반환
} }
} }
/**
* 복호화 실패 경고. 입력이 레거시 평문(개인정보)일 수 있으므로
* 원문 전체/예외 메시지를 찍지 않고 앞 3자 + 길이, 예외 클래스명만 출력한다.
*/
private static void warnDecryptFallback(String mode, String input, Throwable cause) {
System.err.println("[damo-manager] WARN decrypt failed in " + mode
+ " mode; returning input as-is (returnCode=-1). input=" + maskForLog(input)
+ " cause=" + (cause == null ? "empty-result" : cause.getClass().getName()));
}
private static String maskForLog(String s) {
int len = s.length();
String head = (len <= 3) ? "***" : s.substring(0, 3);
return head + "...(len=" + len + ")";
}
/** /**
* 단방향 해시. type 71 = SHA-256, 73 = SHA-512. 결과는 Base64 문자열. * 단방향 해시. type 71 = SHA-256, 73 = SHA-512. 결과는 Base64 문자열.
* fake 모드: JRE MessageDigest 사용 (실제 SHA-256/512 결과를 Base64 로 반환). * fake 모드: JRE MessageDigest 사용 (실제 SHA-256/512 결과를 Base64 로 반환).
* real 실패(예외·빈 결과) 시 IllegalStateException — 빈 해시(비밀번호 등)가 DB에 저장되는 것을 막는다.
*/ */
public String hash(int type, String src) { public String hash(int type, String src) {
if (src == null) { if (src == null) {
@@ -279,12 +323,19 @@ public class DamoManager {
if (REAL_MODE) { if (REAL_MODE) {
try { try {
String out = this.bridge.hashB64(type, src); String out = this.bridge.hashB64(type, src);
if (out == null || out.isEmpty()) {
this.nReturn = SCP_FAIL;
throw new IllegalStateException(
"[damo-manager] hash failed in real mode: ScpHashB64 returned empty result (type=" + type + ")");
}
this.nReturn = SCP_OK; this.nReturn = SCP_OK;
return out; return out;
} catch (IllegalStateException e) {
throw e;
} catch (Throwable t) { } catch (Throwable t) {
t.printStackTrace();
this.nReturn = SCP_FAIL; this.nReturn = SCP_FAIL;
return ""; throw new IllegalStateException(
"[damo-manager] hash failed in real mode (type=" + type + "): " + t.getClass().getName(), t);
} }
} }
String algo; String algo;