bf1135f9ad
- 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>
174 lines
7.4 KiB
Java
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);
|
|
}
|
|
}
|