Files
elink-online-common/src/main/java/com/eactive/eai/common/security/AESCryptoModuleExtension.java
T
curry772 bf1135f9ad feat: 암호화모듈 프레임워크 신규 구현
- CryptoModuleExtension 인터페이스: BC 프로바이더, AAD, 편의 메서드 오버로드 추가
- AESCryptoModuleExtension: CBC/GCM/ECB/FF1 지원, GCM 랜덤 nonce, AAD 처리
- ARIACryptoModuleExtension: ARIA 알고리즘 지원
- CryptoModuleConfigVO: JPA Entity 분리를 위한 Lombok VO
- CryptoModuleManager: VO 패턴 적용, FQCN 기반 전략 동적 로딩(Class.forName)
- CryptoModuleService: STATIC/DYNAMIC × AAD 조합 8가지 오버로드
- CryptoModuleConfigMapper: MapStruct Entity→VO 매퍼
- KeyDerivationStrategy 인터페이스 및 DerivedKey
- HsmContextXorKeyDerivationStrategy: HSM 마스터키 XOR 컨텍스트값 전략
- ReloadCryptoModuleCommand: 설정 런타임 재로드 커맨드
- build.gradle: BouncyCastle bcprov-jdk15on:1.70 의존성 추가
- 단위 테스트 전체 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 16:49:33 +09:00

174 lines
7.4 KiB
Java

package com.eactive.eai.common.security;
import java.nio.ByteBuffer;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jcajce.spec.FPEParameterSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class AESCryptoModuleExtension implements CryptoModuleExtension {
private static final int GCM_IV_LENGTH = 12;
private static final int GCM_TAG_LENGTH = 128;
private static final String DEFAULT_PROVIDER = "BC";
private Cipher encryptEngine;
private Cipher decryptEngine;
private String mode;
private String pad;
private byte[] iv;
private byte[] encKey;
private byte[] decKey;
/** 명시적 프로바이더. null이면 모든 모드에서 BC(DEFAULT_PROVIDER)를 사용한다. */
private String provider;
// ------------------------------------------------------------------
// init — 모든 오버로드는 최종적으로 6+provider 버전에 위임한다
// ------------------------------------------------------------------
@Override
public void init(String alg, String mode, String pad, byte[] encKey, byte[] decKey) throws Exception {
init(alg, mode, pad, null, encKey, decKey, null);
}
@Override
public void init(String alg, String mode, String pad, byte[] iv, byte[] encKey, byte[] decKey) throws Exception {
init(alg, mode, pad, iv, encKey, decKey, null);
}
@Override
public void init(String alg, String mode, String pad, byte[] encKey, byte[] decKey, String provider) throws Exception {
init(alg, mode, pad, null, encKey, decKey, provider);
}
@Override
public void init(String alg, String mode, String pad, byte[] iv, byte[] encKey, byte[] decKey, String provider) throws Exception {
this.mode = mode;
this.pad = pad;
this.iv = iv;
this.encKey = encKey;
this.decKey = decKey;
this.provider = (provider != null && !provider.isEmpty()) ? provider : null;
// GCM 포함 모든 모드에서 BC 프로바이더를 사용 — 기본값 BC
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}
if (!mode.equalsIgnoreCase("GCM")) {
String m = mode != null ? mode : "";
String p = pad != null ? pad : "";
String transformation = String.format("AES/%s/%s", m, p);
Key encKeySpec = new SecretKeySpec(encKey, "AES");
Key decKeySpec = new SecretKeySpec(decKey, "AES");
AlgorithmParameterSpec param = iv != null ? new IvParameterSpec(iv) : null;
if (m.toLowerCase().contains("ff")) {
param = new FPEParameterSpec(256, iv);
}
String prov = this.provider != null ? this.provider : DEFAULT_PROVIDER;
this.encryptEngine = Cipher.getInstance(transformation, prov);
this.decryptEngine = Cipher.getInstance(transformation, prov);
this.encryptEngine.init(Cipher.ENCRYPT_MODE, encKeySpec, param, (SecureRandom) null);
this.decryptEngine.init(Cipher.DECRYPT_MODE, decKeySpec, param, (SecureRandom) null);
}
}
// ------------------------------------------------------------------
// encrypt
// ------------------------------------------------------------------
@Override
public int encrypt(byte[] src, int sindex, int slength, byte[] dst, int dindex) throws Exception {
return this.encryptEngine.doFinal(src, sindex, slength, dst, dindex);
}
@Override
public byte[] encrypt(byte[] src, int sindex, int slength) throws Exception {
return encrypt(src, sindex, slength, null);
}
/**
* AAD를 명시한 암호화.
* GCM 모드에서 aad != null 이면 해당 값을 사용하고, null 이면 iv를 AAD로 사용하는 기본 동작을 따른다.
* 비-GCM 모드는 AAD를 무시하고 사전 초기화된 엔진으로 처리한다.
*/
@Override
public byte[] encrypt(byte[] src, int sindex, int slength, byte[] aad) throws Exception {
if (this.mode.equalsIgnoreCase("GCM")) {
Key key = new SecretKeySpec(this.encKey, "AES");
byte[] nonce = new byte[GCM_IV_LENGTH];
new SecureRandom().nextBytes(nonce);
GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, nonce);
String prov = this.provider != null ? this.provider : DEFAULT_PROVIDER;
Cipher cipher = Cipher.getInstance(String.format("AES/%s/%s", this.mode, this.pad), prov);
cipher.init(Cipher.ENCRYPT_MODE, key, gcmSpec);
byte[] effectiveAad = aad != null ? aad : this.iv;
if (effectiveAad != null) cipher.updateAAD(effectiveAad);
byte[] cipherTextWithTag = cipher.doFinal(src, sindex, slength);
byte[] output = new byte[GCM_IV_LENGTH + cipherTextWithTag.length];
System.arraycopy(nonce, 0, output, 0, GCM_IV_LENGTH);
System.arraycopy(cipherTextWithTag, 0, output, GCM_IV_LENGTH, cipherTextWithTag.length);
return output;
}
int outLen = this.encryptEngine.getOutputSize(slength);
byte[] dst = new byte[outLen];
int actual = encrypt(src, sindex, slength, dst, 0);
return ByteBuffer.allocate(actual).put(dst, 0, actual).array();
}
// ------------------------------------------------------------------
// decrypt
// ------------------------------------------------------------------
@Override
public int decrypt(byte[] src, int sindex, int slength, byte[] dst, int dindex) throws Exception {
return this.decryptEngine.doFinal(src, sindex, slength, dst, dindex);
}
@Override
public byte[] decrypt(byte[] src, int sindex, int slength) throws Exception {
return decrypt(src, sindex, slength, null);
}
/**
* AAD를 명시한 복호화.
* GCM 모드에서 aad != null 이면 해당 값을 사용하고, null 이면 iv를 AAD로 사용하는 기본 동작을 따른다.
* 비-GCM 모드는 AAD를 무시하고 사전 초기화된 엔진으로 처리한다.
*/
@Override
public byte[] decrypt(byte[] src, int sindex, int slength, byte[] aad) throws Exception {
if (this.mode.equalsIgnoreCase("GCM")) {
Key key = new SecretKeySpec(this.decKey, "AES");
byte[] nonce = new byte[GCM_IV_LENGTH];
System.arraycopy(src, sindex, nonce, 0, GCM_IV_LENGTH);
byte[] cipherTextWithTag = new byte[slength - GCM_IV_LENGTH];
System.arraycopy(src, sindex + GCM_IV_LENGTH, cipherTextWithTag, 0, cipherTextWithTag.length);
GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, nonce);
String prov = this.provider != null ? this.provider : DEFAULT_PROVIDER;
Cipher cipher = Cipher.getInstance(String.format("AES/%s/%s", this.mode, this.pad), prov);
cipher.init(Cipher.DECRYPT_MODE, key, gcmSpec);
byte[] effectiveAad = aad != null ? aad : this.iv;
if (effectiveAad != null) cipher.updateAAD(effectiveAad);
return cipher.doFinal(cipherTextWithTag);
}
return this.decryptEngine.doFinal(src, sindex, slength);
}
}