Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 158987b367 | |||
| 8ace28675b | |||
| 1ab36c077b | |||
| 547232bb26 | |||
| ebd89afa0d | |||
| 407eccc827 | |||
| cebdfd6a19 | |||
| 1fd8712de0 | |||
| be204abb5e |
+1
-1
Submodule elink-online-common updated: e64319e7d2...e208a2d64b
+1
-1
Submodule elink-online-core-jpa updated: 03aa7ced90...9620845daf
+1
-1
Submodule elink-online-transformer updated: 17f6df5068...82c7c2bc8c
@@ -7,6 +7,7 @@ import com.eactive.eai.adapter.Keys;
|
||||
import com.eactive.eai.adapter.http.HttpMemoryLogger;
|
||||
import com.eactive.eai.adapter.http.HttpMethodType;
|
||||
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.CryptoFilter;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthFilter;
|
||||
import com.eactive.eai.common.exception.ExceptionUtil;
|
||||
import com.eactive.eai.common.message.MessageType;
|
||||
@@ -142,7 +143,8 @@ public class DJErpApiAdapterService extends HttpAdapterServiceSupport {
|
||||
transactionProp.put(INBOUND_TOKEN, inboundToken);
|
||||
transactionProp.put(ENCRYPT_AES256_IV, httpProp.getProperty(ENCRYPT_AES256_IV, "")); //jwhong
|
||||
transactionProp.put(ENCRYPT_AES256_KEY, httpProp.getProperty(ENCRYPT_AES256_KEY, "")); //jwhong
|
||||
|
||||
|
||||
transactionProp.put(CryptoFilter.PROP_MODULE_NAME, httpProp.getProperty(CryptoFilter.PROP_MODULE_NAME, ""));
|
||||
|
||||
// SEED 컬럼암호하 시 Key로 사용함
|
||||
String seedkey = getHeaders(request).getOrDefault("x-obp-partnercode", "").toString();
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.eactive.eai.custom.adapter.http.dynamic.filter;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.BaseCryptoFilter;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
|
||||
|
||||
/**
|
||||
* CryptoFilter 프로퍼티를 멤버변수로 관리하는 HttpAdapterFilter 구현체.
|
||||
*
|
||||
* 멤버변수에 세팅된 값을 Properties로 변환하여 {@link BaseCryptoFilter}에 위임한다.
|
||||
* 값이 blank인 멤버변수는 Properties에 포함하지 않으며, CryptoFilter의 기본값이 적용된다.
|
||||
*/
|
||||
public class EncFieldCryptoFilter implements HttpAdapterFilter {
|
||||
|
||||
private final BaseCryptoFilter filter = new BaseCryptoFilter();
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
return filter.doPreFilter(adptGrpName, adptName, message, setProp(prop), request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
return filter.doPostFilter(adptGrpName, adptName, resultMessage, setProp(prop), request, response);
|
||||
}
|
||||
|
||||
private Properties setProp(Properties p) {
|
||||
p.setProperty(BaseCryptoFilter.PROP_DEC_FROM_PATH, "/encryptedData");
|
||||
p.setProperty(BaseCryptoFilter.PROP_ENC_TO_PATH, "/encryptedData");
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
package com.eactive.eai.custom.security.keyderiv.strategy;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import com.eactive.eai.common.hsm.HsmCryptoService;
|
||||
import com.eactive.eai.common.security.keyderiv.DerivedKey;
|
||||
import com.eactive.eai.common.security.keyderiv.KeyDerivationStrategy;
|
||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||
|
||||
/**
|
||||
* HSM 마스터키 + 런타임 컨텍스트값을 연결(concatenate)한 뒤 SHA-256 해싱으로 키를 도출하는 전략.
|
||||
* SHA-256 출력은 항상 32바이트(AES-256)이므로 별도 keyLength 파라미터가 불필요하다.
|
||||
*
|
||||
* key_deriv_params (JSON) 예시:
|
||||
* {
|
||||
* "hsmKeyAlias" : "MASTER_KEY_AES",
|
||||
* "contextKey" : "X-Api-Group-Seq" -- runtimeContext에서 꺼낼 키 이름
|
||||
* }
|
||||
*/
|
||||
public class HsmContextSha256KeyDerivationStrategy implements KeyDerivationStrategy {
|
||||
|
||||
/** DB key_deriv_strategy 컬럼에 사용하는 FQCN 참조용 상수. */
|
||||
public static final String STRATEGY_CLASS = HsmContextSha256KeyDerivationStrategy.class.getName();
|
||||
|
||||
private static final String PARAM_HSM_KEY_ALIAS = "hsmKeyAlias";
|
||||
private static final String PARAM_CONTEXT_KEY = "contextKey";
|
||||
|
||||
@Override
|
||||
public DerivedKey deriveKey(Map<String, String> params, Map<String, String> runtimeContext) throws Exception {
|
||||
String hsmKeyAlias = required(params, PARAM_HSM_KEY_ALIAS);
|
||||
String contextKey = required(params, PARAM_CONTEXT_KEY);
|
||||
|
||||
SecretKey masterKey = hsmCryptoService().getSecretKey(hsmKeyAlias);
|
||||
byte[] masterKeyBytes = masterKey.getEncoded();
|
||||
byte[] contextBytes = runtimeContext.getOrDefault(contextKey, "")
|
||||
.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
byte[] combined = new byte[masterKeyBytes.length + contextBytes.length];
|
||||
System.arraycopy(masterKeyBytes, 0, combined, 0, masterKeyBytes.length);
|
||||
System.arraycopy(contextBytes, 0, combined, masterKeyBytes.length, contextBytes.length);
|
||||
|
||||
byte[] derived = MessageDigest.getInstance("SHA-256").digest(combined);
|
||||
return new DerivedKey(derived, derived);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildCacheKey(String cryptoName, Map<String, String> params, Map<String, String> runtimeContext) {
|
||||
String contextKey = params.getOrDefault(PARAM_CONTEXT_KEY, "");
|
||||
String contextValue = runtimeContext.getOrDefault(contextKey, "");
|
||||
return cryptoName + ":" + contextValue;
|
||||
}
|
||||
|
||||
private String required(Map<String, String> params, String key) {
|
||||
String value = params.get(key);
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("key_deriv_params 필수값 누락: " + key);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private HsmCryptoService hsmCryptoService() {
|
||||
return ApplicationContextProvider.getContext().getBean(HsmCryptoService.class);
|
||||
}
|
||||
}
|
||||
-179
@@ -1,179 +0,0 @@
|
||||
package com.eactive.eai.custom.security.keyderiv;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
|
||||
import com.eactive.eai.common.hsm.HsmCryptoService;
|
||||
import com.eactive.eai.common.security.keyderiv.DerivedKey;
|
||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||
import com.eactive.eai.custom.security.keyderiv.strategy.HsmContextSha256KeyDerivationStrategy;
|
||||
|
||||
/**
|
||||
* HsmContextSha256KeyDerivationStrategy 단위 테스트
|
||||
*/
|
||||
@TestMethodOrder(MethodOrderer.DisplayName.class)
|
||||
class HsmContextSha256KeyDerivationStrategyTest {
|
||||
|
||||
private static final String HSM_KEY_ALIAS = "MASTER_KEY_AES";
|
||||
private static final byte[] MASTER_KEY = "0123456789abcdef".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
private static GenericApplicationContext ctx;
|
||||
private static HsmCryptoService mockHsmCryptoService;
|
||||
private static HsmContextSha256KeyDerivationStrategy strategy;
|
||||
|
||||
private Map<String, String> params;
|
||||
private Map<String, String> runtimeContext;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpClass() throws Exception {
|
||||
mockHsmCryptoService = mock(HsmCryptoService.class);
|
||||
SecretKey mockSecretKey = new SecretKeySpec(MASTER_KEY, "AES");
|
||||
when(mockHsmCryptoService.getSecretKey(HSM_KEY_ALIAS)).thenReturn(mockSecretKey);
|
||||
|
||||
ctx = new GenericApplicationContext();
|
||||
ctx.getBeanFactory().registerSingleton("hsmCryptoService", mockHsmCryptoService);
|
||||
ctx.registerBeanDefinition("applicationContextProvider",
|
||||
BeanDefinitionBuilder.genericBeanDefinition(ApplicationContextProvider.class)
|
||||
.getBeanDefinition());
|
||||
ctx.refresh();
|
||||
|
||||
strategy = new HsmContextSha256KeyDerivationStrategy();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUpParams() {
|
||||
params = new HashMap<>();
|
||||
params.put("hsmKeyAlias", HSM_KEY_ALIAS);
|
||||
params.put("contextKey", "X-Api-Group-Seq");
|
||||
|
||||
runtimeContext = new HashMap<>();
|
||||
runtimeContext.put("X-Api-Group-Seq", "GROUP_001");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 1. deriveKey
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("1-1. 정상 파라미터 — DerivedKey 반환, 길이 32바이트(SHA-256 고정 출력)")
|
||||
void testDeriveKey_validParams_returns32ByteKey() throws Exception {
|
||||
DerivedKey dk = strategy.deriveKey(params, runtimeContext);
|
||||
|
||||
assertNotNull(dk);
|
||||
assertNotNull(dk.getEncKey());
|
||||
assertNotNull(dk.getDecKey());
|
||||
assertEquals(32, dk.getEncKey().length, "SHA-256 출력은 항상 32바이트여야 한다");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("1-2. SHA-256(masterKey || contextValue) 계산 결과 검증")
|
||||
void testDeriveKey_sha256ResultIsCorrect() throws Exception {
|
||||
DerivedKey dk = strategy.deriveKey(params, runtimeContext);
|
||||
|
||||
byte[] contextBytes = "GROUP_001".getBytes(StandardCharsets.UTF_8);
|
||||
byte[] combined = new byte[MASTER_KEY.length + contextBytes.length];
|
||||
System.arraycopy(MASTER_KEY, 0, combined, 0, MASTER_KEY.length);
|
||||
System.arraycopy(contextBytes, 0, combined, MASTER_KEY.length, contextBytes.length);
|
||||
byte[] expected = MessageDigest.getInstance("SHA-256").digest(combined);
|
||||
|
||||
assertArrayEquals(expected, dk.getEncKey(), "SHA-256 해싱 결과가 일치해야 한다");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("1-3. 대칭 알고리즘 — encKey == decKey")
|
||||
void testDeriveKey_encKeyEqualsDecKey() throws Exception {
|
||||
DerivedKey dk = strategy.deriveKey(params, runtimeContext);
|
||||
|
||||
assertArrayEquals(dk.getEncKey(), dk.getDecKey(), "대칭 방식이므로 encKey와 decKey가 동일해야 한다");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("1-4. runtimeContext 값 없음 — 빈 문자열로 처리")
|
||||
void testDeriveKey_missingContextValue_emptyStringUsed() throws Exception {
|
||||
runtimeContext.remove("X-Api-Group-Seq");
|
||||
DerivedKey dk = strategy.deriveKey(params, runtimeContext);
|
||||
|
||||
byte[] expected = MessageDigest.getInstance("SHA-256").digest(MASTER_KEY);
|
||||
|
||||
assertArrayEquals(expected, dk.getEncKey(), "컨텍스트 값 없으면 masterKey만 해싱해야 한다");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("1-5. 다른 runtimeContext 값 — 다른 DerivedKey 생성")
|
||||
void testDeriveKey_differentContextValue_differentKey() throws Exception {
|
||||
DerivedKey dk1 = strategy.deriveKey(params, runtimeContext);
|
||||
|
||||
runtimeContext.put("X-Api-Group-Seq", "GROUP_002");
|
||||
DerivedKey dk2 = strategy.deriveKey(params, runtimeContext);
|
||||
|
||||
assertFalse(java.util.Arrays.equals(dk1.getEncKey(), dk2.getEncKey()),
|
||||
"컨텍스트 값이 다르면 도출된 키도 달라야 한다");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("1-6. 필수 파라미터 누락(hsmKeyAlias) — IllegalArgumentException")
|
||||
void testDeriveKey_missingHsmKeyAlias_throwsException() {
|
||||
params.remove("hsmKeyAlias");
|
||||
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> strategy.deriveKey(params, runtimeContext),
|
||||
"hsmKeyAlias 누락 시 IllegalArgumentException이 발생해야 한다");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("1-7. 필수 파라미터 누락(contextKey) — IllegalArgumentException")
|
||||
void testDeriveKey_missingContextKey_throwsException() {
|
||||
params.remove("contextKey");
|
||||
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> strategy.deriveKey(params, runtimeContext),
|
||||
"contextKey 누락 시 IllegalArgumentException이 발생해야 한다");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 2. buildCacheKey
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("2-1. buildCacheKey — cryptoName:contextValue 형식")
|
||||
void testBuildCacheKey_format() {
|
||||
String cacheKey = strategy.buildCacheKey("CRYPTO_A", params, runtimeContext);
|
||||
|
||||
assertEquals("CRYPTO_A:GROUP_001", cacheKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("2-2. buildCacheKey — contextKey 값 없으면 빈 문자열")
|
||||
void testBuildCacheKey_missingContextValue_emptyString() {
|
||||
runtimeContext.remove("X-Api-Group-Seq");
|
||||
String cacheKey = strategy.buildCacheKey("CRYPTO_A", params, runtimeContext);
|
||||
|
||||
assertEquals("CRYPTO_A:", cacheKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("2-3. buildCacheKey — 다른 cryptoName은 다른 캐시 키")
|
||||
void testBuildCacheKey_differentCryptoName_differentKey() {
|
||||
String key1 = strategy.buildCacheKey("CRYPTO_A", params, runtimeContext);
|
||||
String key2 = strategy.buildCacheKey("CRYPTO_B", params, runtimeContext);
|
||||
|
||||
assertNotEquals(key1, key2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user