Dynamic IV 도출 지원 — DerivedKey iv 필드 추가, HsmKeyAndIvDerivationStrategy 구현

This commit is contained in:
curry772
2026-05-27 13:48:23 +09:00
parent c95307901e
commit c74d1777df
3 changed files with 217 additions and 1 deletions
@@ -0,0 +1,175 @@
package com.eactive.eai.custom.common.security.keyderiv;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
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;
/**
* HsmKeyAndIvSliceDerivationStrategy 단위 테스트
*/
@TestMethodOrder(MethodOrderer.DisplayName.class)
class HsmKeyAndIvSliceDerivationStrategyTest {
private static final String HSM_KEY_ALIAS = "MASTER_KEY_AES";
private static final String HSM_IV_ALIAS = "MASTER_IV_AES";
/** 32바이트 암호화 키 (AES-256) */
private static final byte[] MASTER_KEY = new byte[32];
/** 32바이트 IV 원본 → hex 64자 → substring(32,64) → 16바이트 IV */
private static final byte[] MASTER_IV = new byte[32];
static {
for (int i = 0; i < MASTER_KEY.length; i++) MASTER_KEY[i] = (byte) (i + 1);
for (int i = 0; i < MASTER_IV.length; i++) MASTER_IV[i] = (byte) (i + 0x10);
}
private static GenericApplicationContext ctx;
private static HsmCryptoService mockHsmCryptoService;
private static HsmKeyAndIvSliceDerivationStrategy strategy;
private Map<String, String> params;
private Map<String, String> runtimeContext;
@BeforeAll
static void setUpClass() throws Exception {
mockHsmCryptoService = mock(HsmCryptoService.class);
when(mockHsmCryptoService.getSecretKey(HSM_KEY_ALIAS))
.thenReturn(new SecretKeySpec(MASTER_KEY, "AES"));
when(mockHsmCryptoService.getSecretKey(HSM_IV_ALIAS))
.thenReturn(new SecretKeySpec(MASTER_IV, "AES"));
ctx = new GenericApplicationContext();
ctx.getBeanFactory().registerSingleton("hsmCryptoService", mockHsmCryptoService);
ctx.registerBeanDefinition("applicationContextProvider",
BeanDefinitionBuilder.genericBeanDefinition(ApplicationContextProvider.class)
.getBeanDefinition());
ctx.refresh();
strategy = new HsmKeyAndIvSliceDerivationStrategy();
}
@BeforeEach
void setUpParams() {
params = new HashMap<>();
params.put("hsmKeyAlias", HSM_KEY_ALIAS);
params.put("hsmIvAlias", HSM_IV_ALIAS);
runtimeContext = new HashMap<>();
}
// =========================================================================
// 1. deriveKey
// =========================================================================
@Test
@DisplayName("1-1. 정상 파라미터 — DerivedKey 반환, encKey/iv 모두 non-null")
void testDeriveKey_validParams_returnsKeyAndIv() throws Exception {
DerivedKey dk = strategy.deriveKey(params, runtimeContext);
assertNotNull(dk);
assertNotNull(dk.getEncKey());
assertNotNull(dk.getIv());
}
@Test
@DisplayName("1-2. encKey는 hsmKeyAlias에 해당하는 HSM 키")
void testDeriveKey_encKeyMatchesMasterKey() throws Exception {
DerivedKey dk = strategy.deriveKey(params, runtimeContext);
assertArrayEquals(MASTER_KEY, dk.getEncKey());
}
@Test
@DisplayName("1-3. encKey == decKey (대칭 방식)")
void testDeriveKey_encKeyEqualsDecKey() throws Exception {
DerivedKey dk = strategy.deriveKey(params, runtimeContext);
assertArrayEquals(dk.getEncKey(), dk.getDecKey());
}
@Test
@DisplayName("1-4. iv는 HSM IV hex 문자열의 substring(32,64)와 일치")
void testDeriveKey_ivIsHexSubstring32To64() throws Exception {
DerivedKey dk = strategy.deriveKey(params, runtimeContext);
String ivHex = DatatypeConverter.printHexBinary(MASTER_IV);
byte[] expected = DatatypeConverter.parseHexBinary(ivHex.substring(32, 64));
assertArrayEquals(expected, dk.getIv(), "iv는 HSM IV hex substring(32,64) 파싱 결과여야 한다");
}
@Test
@DisplayName("1-5. iv 길이 == 16바이트 (hex 32자 = 16바이트)")
void testDeriveKey_ivLength16() throws Exception {
DerivedKey dk = strategy.deriveKey(params, runtimeContext);
assertEquals(16, dk.getIv().length);
}
@Test
@DisplayName("1-6. hsmIvAlias hex 길이가 64자 미만 — IllegalArgumentException")
void testDeriveKey_ivTooShort_throwsException() throws Exception {
byte[] shortIv = new byte[15]; // hex 30자 → 64자 미만
when(mockHsmCryptoService.getSecretKey("SHORT_IV"))
.thenReturn(new SecretKeySpec(shortIv, "AES"));
params.put("hsmIvAlias", "SHORT_IV");
assertThrows(IllegalArgumentException.class,
() -> strategy.deriveKey(params, runtimeContext),
"IV hex 길이 부족 시 IllegalArgumentException이 발생해야 한다");
}
@Test
@DisplayName("1-7. 필수 파라미터 누락(hsmKeyAlias) — IllegalArgumentException")
void testDeriveKey_missingHsmKeyAlias_throwsException() {
params.remove("hsmKeyAlias");
assertThrows(IllegalArgumentException.class,
() -> strategy.deriveKey(params, runtimeContext));
}
@Test
@DisplayName("1-8. 필수 파라미터 누락(hsmIvAlias) — IllegalArgumentException")
void testDeriveKey_missingHsmIvAlias_throwsException() {
params.remove("hsmIvAlias");
assertThrows(IllegalArgumentException.class,
() -> strategy.deriveKey(params, runtimeContext));
}
// =========================================================================
// 2. buildCacheKey
// =========================================================================
@Test
@DisplayName("2-1. buildCacheKey — cryptoName 단독 반환")
void testBuildCacheKey_returnsCryptoName() {
assertEquals("CRYPTO_A", strategy.buildCacheKey("CRYPTO_A", params, runtimeContext));
}
@Test
@DisplayName("2-2. buildCacheKey — 다른 cryptoName은 다른 캐시 키")
void testBuildCacheKey_differentCryptoName_differentKey() {
assertNotEquals(
strategy.buildCacheKey("CRYPTO_A", params, runtimeContext),
strategy.buildCacheKey("CRYPTO_B", params, runtimeContext));
}
}