암복호화 적용

This commit is contained in:
curry772
2026-06-23 09:54:07 +09:00
parent 02e282c7ba
commit 253716f4b2
3 changed files with 300 additions and 11 deletions
@@ -12,8 +12,8 @@ import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.server.Keys;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.common.util.Logger;
import com.eactive.ext.djb.DamoManager;
import com.eactive.ext.kjb.safedb.KjbSafedbWrapper;
import com.eactive.ext.kjb.safedb.Utils;
@Component
public class EncryptionManager implements Lifecycle {
@@ -130,7 +130,7 @@ public class EncryptionManager implements Lifecycle {
if (StringUtils.isNotEmpty(plainText)) {
KjbSafedbWrapper wrapper = KjbSafedbWrapper.getInstance();
plainText = wrapper.encryptNotRnnoString(plainText);
if (logger.isInfo()) {
// originalAttribute 홀수 글자 마스킹
StringBuilder sb = new StringBuilder();
@@ -161,14 +161,19 @@ public class EncryptionManager implements Lifecycle {
public String decryptDBData(String dbData) {
if (StringUtils.isNotEmpty(dbData)) {
if (this.isEncrypted(dbData)) {
KjbSafedbWrapper safeDBWrapper = KjbSafedbWrapper.getInstance();
try {
dbData = safeDBWrapper.decryptNotRnno(dbData);
} catch (Exception e) {
// 복호화 실패시 원본 반환
String logData = dbData.length() <= 3 ? dbData : dbData.substring(0, 3) + "...";
logger.warn("DB 복호화 실패: 복호화하지 않고 원본 반환. dbData={} (Length : {})", logData, dbData.length());
return dbData;
if ("DAMO".equals(dbEncryptSolutionName)) {
com.eactive.ext.djb.DamoManager damoManager = new com.eactive.ext.djb.DamoManager();
return damoManager.decrypt(dbData);
} else if ("SAFEDB".equals(dbEncryptSolutionName)) {
KjbSafedbWrapper safeDBWrapper = KjbSafedbWrapper.getInstance();
try {
dbData = safeDBWrapper.decryptNotRnno(dbData);
} catch (Exception e) {
// 복호화 실패시 원본 반환
String logData = dbData.length() <= 3 ? dbData : dbData.substring(0, 3) + "...";
logger.warn("DB 복호화 실패: 복호화하지 않고 원본 반환. dbData={} (Length : {})", logData, dbData.length());
return dbData;
}
}
} else {
logger.debug("DB 복호화 경고: Base64 형식이 아님. 복호화하지 않고 원본 반환. dbData={}", dbData);
@@ -197,7 +202,7 @@ public class EncryptionManager implements Lifecycle {
data = data.trim();
// Base64 형식 체크
if (!Utils.isBase64(data)) {
if (!isBase64(data)) {
return false;
}
@@ -208,4 +213,13 @@ public class EncryptionManager implements Lifecycle {
return true;
}
public boolean isBase64(String str) {
if (str == null || str.isEmpty()) {
return false;
}
String base64Pattern = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$";
return str.matches(base64Pattern);
}
}
@@ -0,0 +1,187 @@
package com.eactive.eai.agent.encryption;
import static org.junit.jupiter.api.Assertions.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
/**
* EncryptionManager 단위 테스트 — DAMO 솔루션 모드.
*
* damo-manager.jar 가 DAMO 라이브러리(com.penta.scpdb.ScpDbAgent) 없이 로드되면
* 자동으로 FAKE MODE 로 동작한다:
* encrypt(plain) → Base64 인코딩
* decrypt(b64) → Base64 디코딩
* 따라서 encrypt → decrypt 라운드트립이 완전히 검증 가능하다.
*
* EncryptionManager 생성자가 private 이므로 리플렉션으로 인스턴스를 생성하고
* 필드를 직접 주입한다.
*/
class EncryptionManagerTest {
private EncryptionManager manager;
@BeforeEach
void setUp() throws Exception {
Constructor<EncryptionManager> ctor = EncryptionManager.class.getDeclaredConstructor();
ctor.setAccessible(true);
manager = ctor.newInstance();
}
// -------------------------------------------------------------------------
// 헬퍼
// -------------------------------------------------------------------------
private void setField(String name, Object value) throws Exception {
Field f = EncryptionManager.class.getDeclaredField(name);
f.setAccessible(true);
f.set(manager, value);
}
private void enableDamo() throws Exception {
setField("encryptYN", "Y");
setField("dbEncryptSolutionName", "DAMO");
}
private void disableEncrypt() throws Exception {
setField("encryptYN", "N");
setField("dbEncryptSolutionName", "DAMO");
}
// =========================================================================
// 1. DAMO 모드 — encrypt + decrypt 라운드트립
// =========================================================================
@Test
@DisplayName("1-1. DAMO 암호화 후 복호화 시 원문 복원 — 한글")
void testRoundTrip_korean() throws Exception {
enableDamo();
String plain = "홍길동";
String encrypted = manager.encryptDBData(plain);
assertNotEquals(plain, encrypted, "암호화 결과가 원문과 달라야 한다");
assertEquals(plain, manager.decryptDBData(encrypted));
}
@Test
@DisplayName("1-2. DAMO 암호화 후 복호화 시 원문 복원 — 영문/숫자 혼합")
void testRoundTrip_alphanumeric() throws Exception {
enableDamo();
String plain = "test@example.com";
String encrypted = manager.encryptDBData(plain);
assertNotEquals(plain, encrypted);
assertEquals(plain, manager.decryptDBData(encrypted));
}
@Test
@DisplayName("1-3. DAMO 암호화 후 복호화 시 원문 복원 — 주민등록번호 형식")
void testRoundTrip_idNumber() throws Exception {
enableDamo();
String plain = "900101-1234567";
String encrypted = manager.encryptDBData(plain);
// 숫자와 '-' 만으로 이루어진 원문은 암호화되어 Base64 형식으로 변환된다
assertNotEquals(plain, encrypted);
assertEquals(plain, manager.decryptDBData(encrypted));
}
@Test
@DisplayName("1-4. DAMO 암호화 후 복호화 시 원문 복원 — 다양한 입력 일괄 검증")
void testRoundTrip_multipleValues() throws Exception {
enableDamo();
String[] inputs = {
"홍길동",
"test@example.com",
"900101-1234567",
"ABC테스트123",
"special chars: !@#$%",
"긴문자열긴문자열긴문자열긴문자열긴문자열긴문자열긴문자열긴문자열긴문자열긴문자열"
};
for (String input : inputs) {
String encrypted = manager.encryptDBData(input);
String decrypted = manager.decryptDBData(encrypted);
assertEquals(input, decrypted, "라운드트립 불일치: [" + input + "]");
}
}
// =========================================================================
// 2. 암호화 비활성(encryptYN=N)
// =========================================================================
@Test
@DisplayName("2-1. encryptYN=N 이면 encryptDBData 가 원문 그대로 반환")
void testEncrypt_disabled_returnsPlain() throws Exception {
disableEncrypt();
String plain = "홍길동";
assertEquals(plain, manager.encryptDBData(plain));
}
@Test
@DisplayName("2-2. encryptYN=N 상태의 원문은 isEncrypted=false 이므로 decryptDBData 도 원문 반환")
void testDecrypt_plainTextNotBase64_returnsOriginal() throws Exception {
disableEncrypt();
String plain = "홍길동";
assertEquals(plain, manager.decryptDBData(plain));
}
// =========================================================================
// 3. 특수 입력 처리
// =========================================================================
@Test
@DisplayName("3-1. decryptDBData — null 입력 시 null 반환")
void testDecrypt_null() throws Exception {
enableDamo();
assertNull(manager.decryptDBData(null));
}
@Test
@DisplayName("3-2. decryptDBData — 빈 문자열 입력 시 빈 문자열 반환")
void testDecrypt_empty() throws Exception {
enableDamo();
assertEquals("", manager.decryptDBData(""));
}
@Test
@DisplayName("3-3. decryptDBData — 숫자로만 구성된 값은 복호화하지 않고 원문 반환 (연락처)")
void testDecrypt_numericOnly_returnsOriginal() throws Exception {
enableDamo();
assertEquals("01012345678", manager.decryptDBData("01012345678"));
assertEquals("0101234-5678", manager.decryptDBData("0101234-5678"));
}
@Test
@DisplayName("3-4. decryptDBData — Base64 형식이 아닌 문자열은 원문 반환")
void testDecrypt_nonBase64_returnsOriginal() throws Exception {
enableDamo();
String plain = "이름: 홍길동"; // 공백 포함, Base64 문자셋 아님
assertEquals(plain, manager.decryptDBData(plain));
}
// =========================================================================
// 4. isEncrypt() / getEncryptYN() 상태 확인
// =========================================================================
@Test
@DisplayName("4-1. encryptYN=Y 이면 isEncrypt()=true")
void testIsEncrypt_true() throws Exception {
setField("encryptYN", "Y");
assertTrue(manager.isEncrypt());
}
@Test
@DisplayName("4-2. encryptYN=N 이면 isEncrypt()=false")
void testIsEncrypt_false() throws Exception {
setField("encryptYN", "N");
assertFalse(manager.isEncrypt());
}
@Test
@DisplayName("4-3. getDBEncryptSolutionName() 반환값 확인")
void testGetDBEncryptSolutionName() throws Exception {
setField("dbEncryptSolutionName", "DAMO");
assertEquals("DAMO", manager.getDBEncryptSolutionName());
}
}
@@ -0,0 +1,88 @@
package com.eactive.eai.common.hsm;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.security.KeyStore;
import java.security.Provider;
import java.security.Security;
import java.util.Base64;
import javax.crypto.SecretKey;
public class HsmManagerSingleTest {
/**
* JDK 버전에 따라 SunPKCS11 Provider 를 생성한다.
*
* JDK 8 : SunPKCS11(InputStream) 생성자를 리플렉션으로 호출
* JDK 9+: Provider.configure(configFilePath) 를 리플렉션으로 호출
*
* 두 경로 모두 리플렉션을 사용하므로 컴파일 타임에 JDK 버전 의존성이 없다.
*/
static Provider createProvider(String cfgContent) throws Exception {
String javaVersion = System.getProperty("java.version");
if (javaVersion.startsWith("1.")) {
// JDK 8: new SunPKCS11(InputStream)
InputStream is = new ByteArrayInputStream(cfgContent.getBytes("UTF-8"));
return (Provider) Class.forName("sun.security.pkcs11.SunPKCS11")
.getConstructor(InputStream.class)
.newInstance(is);
} else {
// JDK 9+: Security.getProvider("SunPKCS11").configure(configFilePath)
// configure() 는 JDK 9 에서 추가된 메서드이므로 리플렉션으로 호출
File tmp = File.createTempFile("pkcs11-hsm-", ".cfg");
tmp.deleteOnExit();
Files.write(tmp.toPath(), cfgContent.getBytes("UTF-8"));
Provider base = Security.getProvider("SunPKCS11");
Method configure = Provider.class.getMethod("configure", String.class);
return (Provider) configure.invoke(base, tmp.getAbsolutePath());
}
}
public static void main(String[] args) throws Exception {
// String config = "name = ProtectServer\r\n"
// + "library = /usr/safenet/protecttoolkit5/ptk/lib/libcryptoki.so\r\n"
// + "slot = 0";
String config = "name = SoftHSM\r\nlibrary = C:/SoftHSM2/lib/softhsm2-x64.dll\r\nslotListIndex = 0\r\n";
KeyStore keyStore;
// Provider pkcs11Provider = HsmManagerSingleTest.createProvider(config);
InputStream is = new ByteArrayInputStream(config.getBytes("UTF-8"));
Provider pkcs11Provider = (Provider) Class.forName("sun.security.pkcs11.SunPKCS11")
.getConstructor(InputStream.class)
.newInstance(is);
Provider existing = Security.getProvider(pkcs11Provider.getName());
Security.addProvider(pkcs11Provider);
String pin = "1234";
keyStore = KeyStore.getInstance("PKCS11", pkcs11Provider);
char[] pinChars = (pin != null) ? pin.toCharArray() : null;
keyStore.load(null, pinChars);
System.out.println("HsmManager] 초기화 완료. Provider=" + pkcs11Provider.getName());
java.util.Enumeration<String> aliases = keyStore.aliases();
StringBuilder aliasList = new StringBuilder();
while (aliases.hasMoreElements()) {
if (aliasList.length() > 0) aliasList.append(", ");
String alias = aliases.nextElement();
aliasList.append(alias);
java.security.Key key = keyStore.getKey(alias, null);
if (key instanceof SecretKey) {
SecretKey secretKey = (SecretKey) key;
if(secretKey.getEncoded() != null) {
String encBase64 = Base64.getEncoder().encodeToString(secretKey.getEncoded());
System.out.println("HsmManager] HSM - alias:" + alias +", base64:"+ encBase64);
} else {
System.out.println("HsmManager] HSM - secretKey null - alias:" + alias + ", secretKey:" + secretKey);
}
}
}
System.out.println("HsmManager] HSM 키 목록: [" + aliasList + "]");
}
}