diff --git a/elink-online-common b/elink-online-common index 3eac4ee..444b7ad 160000 --- a/elink-online-common +++ b/elink-online-common @@ -1 +1 @@ -Subproject commit 3eac4eeaa6d57be87f10d8bc8b0aef6fa033a19d +Subproject commit 444b7ad595c2c8bc6f2cdf57e33c8d76d9d68edd diff --git a/elink-online-core-jpa b/elink-online-core-jpa index 03aa7ce..9620845 160000 --- a/elink-online-core-jpa +++ b/elink-online-core-jpa @@ -1 +1 @@ -Subproject commit 03aa7ced90ee145177aaa9b60e407ea538f7a58f +Subproject commit 9620845daf825595bbd155afb19a9471af4a255d diff --git a/src/main/java/com/eactive/eai/custom/security/keyderiv/strategy/HsmContextSha256KeyDerivationStrategy.java b/src/main/java/com/eactive/eai/custom/security/keyderiv/strategy/HsmContextSha256KeyDerivationStrategy.java deleted file mode 100644 index 2f720fe..0000000 --- a/src/main/java/com/eactive/eai/custom/security/keyderiv/strategy/HsmContextSha256KeyDerivationStrategy.java +++ /dev/null @@ -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 params, Map 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 params, Map runtimeContext) { - String contextKey = params.getOrDefault(PARAM_CONTEXT_KEY, ""); - String contextValue = runtimeContext.getOrDefault(contextKey, ""); - return cryptoName + ":" + contextValue; - } - - private String required(Map 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); - } -} diff --git a/src/test/java/com/eactive/eai/custom/security/keyderiv/HsmContextSha256KeyDerivationStrategyTest.java b/src/test/java/com/eactive/eai/custom/security/keyderiv/HsmContextSha256KeyDerivationStrategyTest.java deleted file mode 100644 index eace92e..0000000 --- a/src/test/java/com/eactive/eai/custom/security/keyderiv/HsmContextSha256KeyDerivationStrategyTest.java +++ /dev/null @@ -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 params; - private Map 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); - } -}