feat: CryptoFilter GCM AAD 지원 추가 및 단위테스트 작성

- CryptoFilter: crypto.aad.header 프로퍼티 + buildAad() 메서드 추가
- CryptoFilter: 기본 scope SCOPE_BODY → SCOPE_FIELD 변경
- CryptoFilter: decryptBody/decryptField/encryptBody/encryptField aad 파라미터 적용
- BaseCryptoFilterTest: BODY/FIELD scope, buildAad, AAD 헤더 연동 단위테스트 신규 작성
- CryptoModuleServiceTest: STATIC/DYNAMIC GCM AAD 테스트 섹션 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curry772
2026-05-12 18:15:40 +09:00
parent 085dd024d8
commit 444b7ad595
3 changed files with 505 additions and 21 deletions
@@ -54,11 +54,13 @@ class CryptoModuleServiceTest {
SecretKey masterKey = new SecretKeySpec(KEY_128, "AES");
when(mockHsm.getSecretKey(anyString())).thenReturn(masterKey);
CryptoModuleConfig aes = buildStatic("AES_SVC", "AES", "CBC", "PKCS5Padding");
CryptoModuleConfig aria = buildStatic("ARIA_SVC", "ARIA", "CBC", "PKCS5Padding");
CryptoModuleConfig ecb = buildStaticNoIv("AES_ECB_SVC", "AES", "ECB", "NoPadding");
CryptoModuleConfig dyn = buildDynamic("DYN_SVC", "AES", "CBC", "PKCS5Padding");
when(mockLoader.findAll()).thenReturn(Arrays.asList(aes, aria, ecb, dyn));
CryptoModuleConfig aes = buildStatic("AES_SVC", "AES", "CBC", "PKCS5Padding");
CryptoModuleConfig aria = buildStatic("ARIA_SVC", "ARIA", "CBC", "PKCS5Padding");
CryptoModuleConfig ecb = buildStaticNoIv("AES_ECB_SVC", "AES", "ECB", "NoPadding");
CryptoModuleConfig dyn = buildDynamic("DYN_SVC", "AES", "CBC", "PKCS5Padding");
CryptoModuleConfig gcm = buildStatic("AES_GCM_SVC", "AES", "GCM", "NoPadding");
CryptoModuleConfig gcmDyn = buildDynamic("DYN_GCM_SVC", "AES", "GCM", "NoPadding");
when(mockLoader.findAll()).thenReturn(Arrays.asList(aes, aria, ecb, dyn, gcm, gcmDyn));
Constructor<CryptoModuleManager> ctor = CryptoModuleManager.class.getDeclaredConstructor();
ctor.setAccessible(true);
@@ -200,6 +202,124 @@ class CryptoModuleServiceTest {
"NoPadding 모드에서 블록 크기 미정렬 평문은 예외가 발생해야 한다");
}
// =========================================================================
// 4. STATIC AAD — encrypt(name, aad, plain) / decrypt(name, aad, cipher)
// =========================================================================
@Test
@DisplayName("4-1. STATIC AES/GCM — AAD 명시 encrypt → decrypt 라운드트립")
void testStaticGcm_explicitAad_roundTrip() throws Exception {
byte[] aad = "request-header-ctx".getBytes(StandardCharsets.UTF_8);
byte[] plain = "GCM AAD 서비스 테스트".getBytes(StandardCharsets.UTF_8);
byte[] encrypted = service.encrypt("AES_GCM_SVC", aad, plain);
byte[] decrypted = service.decrypt("AES_GCM_SVC", aad, encrypted);
assertArrayEquals(plain, decrypted);
}
@Test
@DisplayName("4-2. STATIC AES/GCM — AAD null이면 IV를 AAD로 사용하는 기본 동작")
void testStaticGcm_nullAad_usesIvAsAad() throws Exception {
byte[] plain = "AAD null 기본 동작 테스트".getBytes(StandardCharsets.UTF_8);
byte[] encrypted = service.encrypt("AES_GCM_SVC", (byte[]) null, plain);
byte[] decrypted = service.decrypt("AES_GCM_SVC", (byte[]) null, encrypted);
assertArrayEquals(plain, decrypted);
}
@Test
@DisplayName("4-3. STATIC AES/GCM — 다른 AAD로 복호화 시 인증 실패 예외")
void testStaticGcm_wrongAad_throwsAuthException() throws Exception {
byte[] aad = "correct-aad-value".getBytes(StandardCharsets.UTF_8);
byte[] wrongAad = "wrong-aad-value!!".getBytes(StandardCharsets.UTF_8);
byte[] plain = "GCM 인증 실패 테스트".getBytes(StandardCharsets.UTF_8);
byte[] encrypted = service.encrypt("AES_GCM_SVC", aad, plain);
assertThrows(Exception.class,
() -> service.decrypt("AES_GCM_SVC", wrongAad, encrypted),
"잘못된 AAD로 GCM 복호화 시 인증 실패 예외가 발생해야 한다");
}
@Test
@DisplayName("4-4. STATIC CBC — AAD 파라미터는 무시되고 정상 복호화")
void testStaticCbc_aadIgnored_normalDecrypt() throws Exception {
byte[] aad = "ignored-aad".getBytes(StandardCharsets.UTF_8);
byte[] plain = "CBC AAD 무시 테스트".getBytes(StandardCharsets.UTF_8);
byte[] encrypted = service.encrypt("AES_SVC", aad, plain);
byte[] decrypted = service.decrypt("AES_SVC", aad, encrypted);
assertArrayEquals(plain, decrypted, "CBC 모드에서 AAD는 무시되고 정상 암복호화되어야 한다");
}
// =========================================================================
// 5. DYNAMIC AAD — encrypt(name, ctx, aad, plain) / decrypt(name, ctx, aad, cipher)
// =========================================================================
@Test
@DisplayName("5-1. DYNAMIC AES/GCM — runtimeContext + AAD 명시 encrypt → decrypt 라운드트립")
void testDynamicGcm_explicitAad_roundTrip() throws Exception {
Map<String, String> runtimeCtx = new HashMap<>();
runtimeCtx.put("X-Api-Enc-Key", "clientKey0000001");
byte[] aad = "dynamic-gcm-aad-ctx".getBytes(StandardCharsets.UTF_8);
byte[] plain = "DYNAMIC GCM AAD 테스트".getBytes(StandardCharsets.UTF_8);
byte[] encrypted = service.encrypt("DYN_GCM_SVC", runtimeCtx, aad, plain);
byte[] decrypted = service.decrypt("DYN_GCM_SVC", runtimeCtx, aad, encrypted);
assertArrayEquals(plain, decrypted);
}
@Test
@DisplayName("5-2. DYNAMIC AES/GCM — AAD null이면 IV를 AAD로 사용하는 기본 동작")
void testDynamicGcm_nullAad_usesIvAsAad() throws Exception {
Map<String, String> runtimeCtx = new HashMap<>();
runtimeCtx.put("X-Api-Enc-Key", "clientKey0000001");
byte[] plain = "DYNAMIC GCM AAD null 테스트".getBytes(StandardCharsets.UTF_8);
byte[] encrypted = service.encrypt("DYN_GCM_SVC", runtimeCtx, null, plain);
byte[] decrypted = service.decrypt("DYN_GCM_SVC", runtimeCtx, null, encrypted);
assertArrayEquals(plain, decrypted);
}
@Test
@DisplayName("5-3. DYNAMIC AES/GCM — 다른 AAD로 복호화 시 인증 실패 예외")
void testDynamicGcm_wrongAad_throwsAuthException() throws Exception {
Map<String, String> runtimeCtx = new HashMap<>();
runtimeCtx.put("X-Api-Enc-Key", "clientKey0000001");
byte[] aad = "correct-aad-value".getBytes(StandardCharsets.UTF_8);
byte[] wrongAad = "wrong-aad-value!!".getBytes(StandardCharsets.UTF_8);
byte[] plain = "DYNAMIC GCM 인증 실패".getBytes(StandardCharsets.UTF_8);
byte[] encrypted = service.encrypt("DYN_GCM_SVC", runtimeCtx, aad, plain);
assertThrows(Exception.class,
() -> service.decrypt("DYN_GCM_SVC", runtimeCtx, wrongAad, encrypted),
"잘못된 AAD로 DYNAMIC GCM 복호화 시 인증 실패 예외가 발생해야 한다");
}
@Test
@DisplayName("5-4. DYNAMIC CBC — AAD 파라미터는 무시되고 정상 복호화")
void testDynamicCbc_aadIgnored_normalDecrypt() throws Exception {
Map<String, String> runtimeCtx = new HashMap<>();
runtimeCtx.put("X-Api-Enc-Key", "clientKey0000001");
byte[] aad = "ignored-aad".getBytes(StandardCharsets.UTF_8);
byte[] plain = "DYNAMIC CBC AAD 무시 테스트".getBytes(StandardCharsets.UTF_8);
byte[] encrypted = service.encrypt("DYN_SVC", runtimeCtx, aad, plain);
byte[] decrypted = service.decrypt("DYN_SVC", runtimeCtx, aad, encrypted);
assertArrayEquals(plain, decrypted, "DYNAMIC CBC 모드에서 AAD는 무시되고 정상 암복호화되어야 한다");
}
// =========================================================================
// 헬퍼
// =========================================================================