diff --git a/README.md b/README.md index 5d855dc..7a61372 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ java -jar damo-manager.jar damo> enc hello aGVsbG8= damo> info -mode=FAKE revision=20260604 returnCode=0 +mode=FAKE revision=20260805 returnCode=0 damo> quit # 파라미터식: 1회 실행 @@ -101,6 +101,11 @@ java -Ddamo-manager.enabled=true -jar damo-manager.jar enc "hello" # scpdb 있 # 운영: real 강제 (scpdb 없으면 기동 실패) 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. @@ -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 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` 상수: | 상수 | 값 | 알고리즘 | @@ -282,7 +292,7 @@ gradle runFakeSmoke # -Ddamo-manager.enabled=true (fake) ... ============================================================ -revision = 20260604 +revision = 20260805 bypassMode = false fakeMode = true encrypt = aGVsbG8sIOuLpOuqqA== diff --git a/src/main/java/com/eactive/ext/djb/DamoManager.java b/src/main/java/com/eactive/ext/djb/DamoManager.java index 6e25fd6..a9d274b 100644 --- a/src/main/java/com/eactive/ext/djb/DamoManager.java +++ b/src/main/java/com/eactive/ext/djb/DamoManager.java @@ -35,6 +35,16 @@ import java.util.Base64; * - encrypt(s) : Base64.encode(s.getBytes(UTF-8)) -- 암호화 없음 * - decrypt(s) : new String(Base64.decode(s), UTF-8) * - 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 { @@ -141,7 +151,7 @@ public class DamoManager { System.err.flush(); } - private final String szRevision = "20260604"; + private final String szRevision = "20260805"; private int nReturn; private final RealBridge bridge; @@ -195,6 +205,7 @@ public class DamoManager { /** * 평문 문자열을 ScpEncB64 로 암호화하여 Base64 cipher text 반환. * fake 모드: 단순 Base64 인코딩. + * real 실패(예외·빈 결과) 시 IllegalStateException — 빈 암호문이 DB에 저장되는 것을 막는다. */ public String encrypt(String plain) { if (plain == null) { @@ -208,12 +219,19 @@ public class DamoManager { if (REAL_MODE) { try { 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; return out; + } catch (IllegalStateException e) { + throw e; } catch (Throwable t) { - t.printStackTrace(); this.nReturn = SCP_FAIL; - return ""; + throw new IllegalStateException( + "[damo-manager] encrypt failed in real mode: " + t.getClass().getName(), t); } } try { @@ -228,6 +246,8 @@ public class DamoManager { /** * Base64 cipher text 를 ScpDecB64 로 복호화하여 평문 반환. * fake 모드: 단순 Base64 디코딩. + * 실패(real 예외·빈 결과, fake 디코딩 실패) 시 입력을 그대로 반환하고 returnCode=-1. + * 레거시 평문이 암호문으로 오판된 경우 원문이 보존되어야 평문 폴백/재암호화가 동작한다. */ public String decrypt(String base64Cipher) { if (base64Cipher == null) { @@ -241,12 +261,18 @@ public class DamoManager { if (REAL_MODE) { try { 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; return out; } catch (Throwable t) { - t.printStackTrace(); this.nReturn = SCP_FAIL; - return ""; + warnDecryptFallback("real", base64Cipher, t); + return base64Cipher; // 실패 시 입력 원문 반환 (레거시 평문 오판 가능성) } } try { @@ -259,13 +285,31 @@ public class DamoManager { } } catch (IllegalArgumentException e) { 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 문자열. * fake 모드: JRE MessageDigest 사용 (실제 SHA-256/512 결과를 Base64 로 반환). + * real 실패(예외·빈 결과) 시 IllegalStateException — 빈 해시(비밀번호 등)가 DB에 저장되는 것을 막는다. */ public String hash(int type, String src) { if (src == null) { @@ -279,12 +323,19 @@ public class DamoManager { if (REAL_MODE) { try { 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; return out; + } catch (IllegalStateException e) { + throw e; } catch (Throwable t) { - t.printStackTrace(); this.nReturn = SCP_FAIL; - return ""; + throw new IllegalStateException( + "[damo-manager] hash failed in real mode (type=" + type + "): " + t.getClass().getName(), t); } } String algo;