Dynamic IV 도출 지원 — DerivedKey iv 필드 추가, HsmKeyAndIvDerivationStrategy 구현
This commit is contained in:
+1
-1
Submodule elink-online-common updated: 29a486bf9b...6809bf66b8
+41
@@ -0,0 +1,41 @@
|
||||
package com.eactive.eai.custom.common.security.keyderiv;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
|
||||
import com.eactive.eai.common.security.keyderiv.DerivedKey;
|
||||
import com.eactive.eai.common.security.keyderiv.strategy.BaseHsmKeyDerivationStrategy;
|
||||
|
||||
/**
|
||||
* HSM에서 키와 IV를 각각 가져오는 전략.
|
||||
* IV는 HSM에서 가져온 바이트 배열을 hex 문자열로 변환한 뒤 substring(32, 64)를 적용하여 사용한다.
|
||||
* (32바이트 HSM 키 → 64자 hex → substring(32,64) → 16바이트 IV)
|
||||
*
|
||||
* key_deriv_params (JSON) 예시:
|
||||
* { "hsmKeyAlias" : "MASTER_KEY_AES", "hsmIvAlias" : "MASTER_IV_AES" }
|
||||
*/
|
||||
public class HsmKeyAndIvSliceDerivationStrategy extends BaseHsmKeyDerivationStrategy {
|
||||
|
||||
private static final int IV_HEX_OFFSET = 32;
|
||||
private static final int IV_HEX_END = 64;
|
||||
|
||||
@Override
|
||||
public DerivedKey deriveKey(Map<String, String> params, Map<String, String> runtimeContext) throws Exception {
|
||||
byte[] keyBytes = super.getHsmKey(params);
|
||||
byte[] rawIv = super.getHsmIv(params);
|
||||
String ivHex = DatatypeConverter.printHexBinary(rawIv);
|
||||
if (ivHex.length() < IV_HEX_END) {
|
||||
throw new IllegalArgumentException(
|
||||
"hsmIvAlias hex 길이 부족: 최소 " + IV_HEX_END + "자 필요, 실제=" + ivHex.length()
|
||||
+ " (" + rawIv.length + "바이트)");
|
||||
}
|
||||
byte[] iv = DatatypeConverter.parseHexBinary(ivHex.substring(IV_HEX_OFFSET, IV_HEX_END));
|
||||
return new DerivedKey(keyBytes, keyBytes, iv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildCacheKey(String cryptoName, Map<String, String> params, Map<String, String> runtimeContext) {
|
||||
return cryptoName;
|
||||
}
|
||||
}
|
||||
+175
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user