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

This commit is contained in:
curry772
2026-05-27 13:47:58 +09:00
parent 29a486bf9b
commit 6809bf66b8
7 changed files with 225 additions and 3 deletions
@@ -144,8 +144,12 @@ public class CryptoModuleManager implements Lifecycle {
dynamicKeyCache.put(cacheKey, new CachedDerivedKey(derivedKey, ttl));
}
}
byte[] iv = vo.getIvHex() != null ? DatatypeConverter.parseHexBinary(vo.getIvHex()) : null;
byte[] iv = null;
if(derivedKey.getIv() != null) {
iv = derivedKey.getIv();
} else if (vo.getIvHex() != null){
iv = DatatypeConverter.parseHexBinary(vo.getIvHex());
}
return buildExtension(vo, iv, derivedKey.getEncKey(), derivedKey.getDecKey());
}
@@ -4,10 +4,19 @@ public class DerivedKey {
private final byte[] encKey;
private final byte[] decKey;
private final byte[] iv;
public DerivedKey(byte[] encKey, byte[] decKey) {
this.encKey = encKey;
this.decKey = decKey != null ? decKey : encKey;
this.iv = null;
}
public DerivedKey(byte[] encKey, byte[] decKey, byte[] iv) {
this.encKey = encKey;
this.decKey = decKey != null ? decKey : encKey;
this.iv = iv;
}
public byte[] getEncKey() {
@@ -17,4 +26,8 @@ public class DerivedKey {
public byte[] getDecKey() {
return decKey;
}
public byte[] getIv() {
return iv;
}
}
@@ -5,7 +5,8 @@ import java.util.Map;
public interface KeyDerivationStrategy {
public static final String PARAM_HSM_KEY_ALIAS = "hsmKeyAlias";
public static final String PARAM_CONTEXT_KEY = "contextKey";
public static final String PARAM_HSM_IV_ALIAS = "hsmIvAlias";
public static final String PARAM_CONTEXT_KEY = "contextKey";
/**
@@ -24,6 +24,11 @@ public abstract class BaseHsmKeyDerivationStrategy implements KeyDerivationStrat
return masterKey.getEncoded();
}
protected byte[] getHsmIv(Map<String, String> params) throws HsmException {
String hsmKeyAlias = required(params, PARAM_HSM_IV_ALIAS);
return getHsmKey(hsmKeyAlias);
}
private String required(Map<String, String> params, String key) {
String value = params.get(key);
if (value == null || value.trim().isEmpty()) {
@@ -0,0 +1,25 @@
package com.eactive.eai.common.security.keyderiv.strategy;
import java.util.Map;
import com.eactive.eai.common.security.keyderiv.DerivedKey;
/**
* HSM 마스터키를 사용하는 전략
*
* key_deriv_params (JSON) 예시: { "hsmKeyAlias" : "MASTER_KEY_AES", "hsmIvAlias" : "MASTER_IV_AES"}
*/
public class HsmKeyAndIvDerivationStrategy extends BaseHsmKeyDerivationStrategy {
@Override
public DerivedKey deriveKey(Map<String, String> params, Map<String, String> runtimeContext) throws Exception {
byte[] masterKeyBytes = super.getHsmKey(params);
byte[] iv = super.getHsmIv(params);
return new DerivedKey(masterKeyBytes, masterKeyBytes, iv);
}
@Override
public String buildCacheKey(String cryptoName, Map<String, String> params, Map<String, String> runtimeContext) {
return cryptoName;
}
}
@@ -36,4 +36,29 @@ class DerivedKeyTest {
assertSame(dk.getEncKey(), dk.getDecKey());
}
@Test
@DisplayName("1-4. 2-arg 생성자 — iv는 null")
void testTwoArgConstructor_ivIsNull() {
DerivedKey dk = new DerivedKey(new byte[]{1}, new byte[]{2});
assertNull(dk.getIv(), "2-arg 생성자로 생성 시 iv는 null이어야 한다");
}
@Test
@DisplayName("1-5. 3-arg 생성자 — iv 반환")
void testThreeArgConstructor_ivReturned() {
byte[] iv = {0x01, 0x02, 0x03};
DerivedKey dk = new DerivedKey(new byte[]{1}, new byte[]{2}, iv);
assertArrayEquals(iv, dk.getIv());
}
@Test
@DisplayName("1-6. 3-arg 생성자 — iv null 허용")
void testThreeArgConstructor_nullIv() {
DerivedKey dk = new DerivedKey(new byte[]{1}, new byte[]{2}, null);
assertNull(dk.getIv());
}
}
@@ -0,0 +1,149 @@
package com.eactive.eai.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 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.strategy.HsmKeyAndIvDerivationStrategy;
import com.eactive.eai.common.util.ApplicationContextProvider;
/**
* HsmKeyAndIvDerivationStrategy 단위 테스트
*/
@TestMethodOrder(MethodOrderer.DisplayName.class)
class HsmKeyAndIvDerivationStrategyTest {
private static final String HSM_KEY_ALIAS = "MASTER_KEY_AES";
private static final String HSM_IV_ALIAS = "MASTER_IV_AES";
private static final byte[] MASTER_KEY = new byte[]{0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,
0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10};
private static final byte[] MASTER_IV = new byte[]{0x10,0x20,0x30,0x40,0x50,0x60,0x70,(byte)0x80,
0x11,0x21,0x31,0x41,0x51,0x61,0x71,(byte)0x81};
private static GenericApplicationContext ctx;
private static HsmCryptoService mockHsmCryptoService;
private static HsmKeyAndIvDerivationStrategy 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 HsmKeyAndIvDerivationStrategy();
}
@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 반환, key와 iv 모두 non-null")
void testDeriveKey_validParams_returnsKeyAndIv() throws Exception {
DerivedKey dk = strategy.deriveKey(params, runtimeContext);
assertNotNull(dk);
assertNotNull(dk.getEncKey(), "encKey는 null이 아니어야 한다");
assertNotNull(dk.getDecKey(), "decKey는 null이 아니어야 한다");
assertNotNull(dk.getIv(), "iv는 null이 아니어야 한다");
}
@Test
@DisplayName("1-2. encKey == decKey (대칭 방식)")
void testDeriveKey_encKeyEqualsDecKey() throws Exception {
DerivedKey dk = strategy.deriveKey(params, runtimeContext);
assertArrayEquals(dk.getEncKey(), dk.getDecKey());
}
@Test
@DisplayName("1-3. encKey는 hsmKeyAlias에 해당하는 HSM 키")
void testDeriveKey_encKeyMatchesMasterKey() throws Exception {
DerivedKey dk = strategy.deriveKey(params, runtimeContext);
assertArrayEquals(MASTER_KEY, dk.getEncKey(), "encKey는 hsmKeyAlias로 가져온 마스터키여야 한다");
}
@Test
@DisplayName("1-4. iv는 hsmIvAlias에 해당하는 HSM 키")
void testDeriveKey_ivMatchesMasterIv() throws Exception {
DerivedKey dk = strategy.deriveKey(params, runtimeContext);
assertArrayEquals(MASTER_IV, dk.getIv(), "iv는 hsmIvAlias로 가져온 HSM 값이어야 한다");
}
@Test
@DisplayName("1-5. 필수 파라미터 누락(hsmKeyAlias) — IllegalArgumentException")
void testDeriveKey_missingHsmKeyAlias_throwsException() {
params.remove("hsmKeyAlias");
assertThrows(IllegalArgumentException.class,
() -> strategy.deriveKey(params, runtimeContext));
}
@Test
@DisplayName("1-6. 필수 파라미터 누락(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() {
String cacheKey = strategy.buildCacheKey("CRYPTO_A", params, runtimeContext);
assertEquals("CRYPTO_A", cacheKey);
}
@Test
@DisplayName("2-2. 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);
}
}