HSM 암복화 테스트 중 추가 기능 수정

This commit is contained in:
curry772
2026-06-29 13:18:02 +09:00
parent c12ead1f97
commit 918c4b5c3e
11 changed files with 861 additions and 21 deletions
@@ -2,8 +2,12 @@ package com.eactive.eai.custom.adapter.http.client.impl.filter;
import java.util.Properties;
import com.eactive.eai.adapter.http.client.impl.HttpClient5AdapterServiceRest;
import com.eactive.eai.adapter.http.client.impl.filter.HttpClientAdapterFilter;
import com.eactive.eai.adapter.http.client.impl.filter.OutCryptoFilter;
import com.eactive.eai.util.JsonPathUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* 제주은행 Open API 기본 암복호화 필더
*/
@@ -20,7 +24,20 @@ public class EncFieldOutCryptoFilter implements HttpClientAdapterFilter {
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
return filter.doPreFilter(adptGrpName, adptName, prop, message, setProp(tempProp));
String headerGroupName = prop.getProperty(HttpClient5AdapterServiceRest.HEADER_GROUP);
JsonNode root = JsonPathUtil.toTree(message);
ObjectNode rootObjectNode = (ObjectNode) root;
JsonNode header = rootObjectNode.get(headerGroupName);
rootObjectNode.remove(headerGroupName);
Object enc = filter.doPreFilter(adptGrpName, adptName, prop, rootObjectNode, setProp(tempProp));
ObjectNode encJson = (ObjectNode)JsonPathUtil.toTree(enc);
encJson.set(headerGroupName, header);
return encJson;
}
@Override
@@ -0,0 +1,88 @@
package com.eactive.eai.custom.adapter.http.dynamic.filter;
import java.util.Base64;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONObject;
import org.springframework.http.HttpStatus;
import com.eactive.eai.adapter.http.dynamic.filter.FilterException;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
import com.eactive.eai.common.security.AESCryptoModuleExtension;
import com.eactive.eai.common.security.ARIACryptoModuleExtension;
import com.eactive.eai.common.security.CryptoModuleExtension;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.util.HexaConverter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class DecrytTestFilter implements HttpAdapterFilter {
private static final Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@Override
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
ObjectNode rootNode = null;
String jsonStr = null;
if (message instanceof String) {
jsonStr = (String) message;
} else if (message instanceof JSONObject) {
jsonStr = ((JSONObject) message).toJSONString();
} else {
return message;
}
rootNode = (ObjectNode) OBJECT_MAPPER.readTree(jsonStr);
JsonNode alg = rootNode.get("alg");
JsonNode mode = rootNode.get("mode");
JsonNode padding = rootNode.get("padding");
JsonNode aadNode = rootNode.get("aad");
JsonNode iv = rootNode.get("iv");
byte[] binIv = null;
if(iv != null) {
String strIv = iv.asText();
binIv = HexaConverter.hexToBin(strIv);
}
JsonNode encKey = rootNode.get("encKey");
String strEncKey = encKey.asText();
byte[] binEncKey = HexaConverter.hexToBin(strEncKey);
CryptoModuleExtension ext = "ARIA".equalsIgnoreCase(alg.asText()) ? new ARIACryptoModuleExtension()
: new AESCryptoModuleExtension();
ext.init(alg.asText(), mode.asText(), padding.asText(), binIv, binEncKey, binEncKey);
JsonNode encryptedData = rootNode.get("encryptedData");
byte[] cipherBytes = Base64.getDecoder().decode(encryptedData.asText().trim());
byte[] decryptedBytes = null;
if(rootNode.has("aad"))
decryptedBytes = ext.decrypt(cipherBytes, HexaConverter.hexToBin(aadNode.asText()));
else
decryptedBytes = ext.decrypt(cipherBytes);
rootNode.put("hsmKeyRaw", new String(decryptedBytes));
String jsonString = OBJECT_MAPPER.writeValueAsString(rootNode);
return jsonString;
} catch (Exception e) {
logger.error("복호화 실패", e);
throw new FilterException("복호화 실패", ERROR_PRE_FAIL, HttpStatus.INTERNAL_SERVER_ERROR.value(), e);
}
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
return resultMessage;
}
}
@@ -0,0 +1,147 @@
package com.eactive.eai.custom.adapter.http.dynamic.filter;
import java.security.KeyStore;
import java.util.Base64;
import java.util.Properties;
import javax.crypto.SecretKey;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONObject;
import org.springframework.http.HttpStatus;
import com.eactive.eai.adapter.http.dynamic.filter.FilterException;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
import com.eactive.eai.common.hsm.HsmManager;
import com.eactive.eai.common.security.AESCryptoModuleExtension;
import com.eactive.eai.common.security.ARIACryptoModuleExtension;
import com.eactive.eai.common.security.CryptoModuleExtension;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.util.HexaConverter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class EncrytTestFilter implements HttpAdapterFilter {
private static final Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@Override
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
ObjectNode rootNode = null;
String jsonStr = null;
if (message instanceof String) {
jsonStr = (String) message;
} else if (message instanceof JSONObject) {
jsonStr = ((JSONObject) message).toJSONString();
} else {
return message;
}
rootNode = (ObjectNode) OBJECT_MAPPER.readTree(jsonStr);
// 1. HSM에서 키 가져오기
byte[] hsmKeyBytes = null;
JsonNode hsmKeyAlias = rootNode.get("hsmKeyAlias");
if (hsmKeyAlias != null) {
String hsmKeyAliasValue = hsmKeyAlias.textValue();
KeyStore keyStore = HsmManager.getInstance().getKeyStore();
java.security.Key key = keyStore.getKey(hsmKeyAliasValue, null);
if (key instanceof SecretKey) {
SecretKey secretKey = (SecretKey) key;
hsmKeyBytes = secretKey.getEncoded();
if (hsmKeyBytes != null) {
String hsmKeyBase64 = Base64.getEncoder().encodeToString(hsmKeyBytes);
String hsmKeyHex = HexaConverter.binToHex(hsmKeyBytes);
logger.debug("HsmManager] HSM - {} : [{}]", hsmKeyAliasValue, hsmKeyHex);
rootNode.put("hsmKeyBase64", hsmKeyBase64);
rootNode.put("hsmKeyHex", hsmKeyHex);
rootNode.put("hsmKeyRaw", new String(hsmKeyBytes));
} else {
logger.debug("HsmManager] HSM - secretKey null {} : [{}]", hsmKeyAliasValue, secretKey);
}
}
}
// 2. 암호화 파라미터가 있으면 암호화 후 복호화 검증
JsonNode alg = rootNode.get("alg");
JsonNode mode = rootNode.get("mode");
JsonNode padding = rootNode.get("padding");
JsonNode enc = rootNode.get("enc");
String strEnc = "BASE64";
if(enc != null)
strEnc = enc.asText();
JsonNode ivNode = rootNode.get("iv");
JsonNode aadNode = rootNode.get("aad");
JsonNode encKeyNode = rootNode.get("encKey");
JsonNode dataNode = rootNode.get("data");
if (alg != null && mode != null && padding != null && encKeyNode != null
&& dataNode != null) {
byte[] binIv = null;
if(ivNode != null) {
String strIvValue = ivNode.asText();
if(strIvValue.length() == 32||strEnc.equalsIgnoreCase("HEX"))
binIv = HexaConverter.hexToBin(strIvValue);
else
binIv = Base64.getDecoder().decode(strIvValue.trim());
}
String strEncKeyValue = encKeyNode.asText();
byte[] binEncKey = null;
if(strEnc.equalsIgnoreCase("BASE64"))
binEncKey = Base64.getDecoder().decode(strEncKeyValue.trim());
else
binEncKey = HexaConverter.hexToBin(strEncKeyValue);
CryptoModuleExtension ext = "ARIA".equalsIgnoreCase(alg.asText()) ? new ARIACryptoModuleExtension()
: new AESCryptoModuleExtension();
ext.init(alg.asText(), mode.asText(), padding.asText(), binIv, binEncKey, binEncKey);
byte[] plainBytes = dataNode.asText().getBytes();
// 암호화
byte[] encryptedBytes = null;
if(rootNode.has("aad"))
encryptedBytes = ext.encrypt(plainBytes, HexaConverter.hexToBin(aadNode.asText()));
else
encryptedBytes = ext.encrypt(plainBytes);
String encryptedBase64 = Base64.getEncoder().encodeToString(encryptedBytes);
rootNode.put("encryptedData", encryptedBase64);
logger.debug("암호화 결과: [{}]", encryptedBase64);
// 복호화 검증
byte[] decryptedBytes = null;
if(rootNode.has("aad"))
decryptedBytes = ext.decrypt(encryptedBytes, HexaConverter.hexToBin(aadNode.asText()));
else
decryptedBytes = ext.decrypt(encryptedBytes);
rootNode.put("decryptedVerify", new String(decryptedBytes));
logger.debug("복호화 검증: [{}]", new String(decryptedBytes));
}
String jsonString = OBJECT_MAPPER.writeValueAsString(rootNode);
return jsonString;
} catch (Exception e) {
throw new FilterException("HSM key 암호화/복호화 검증 실패", ERROR_PRE_FAIL, HttpStatus.INTERNAL_SERVER_ERROR.value(), e);
}
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
return resultMessage;
}
}
@@ -0,0 +1,117 @@
package com.eactive.eai.custom.adapter.http.dynamic.filter;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONObject;
import org.springframework.http.HttpStatus;
import com.eactive.eai.adapter.http.dynamic.filter.FilterException;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
import com.eactive.eai.common.security.keyderiv.DerivedKey;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.custom.common.security.keyderiv.HsmContextSha256KeyDerivationStrategy;
import com.eactive.eai.custom.common.security.keyderiv.HsmKeyAndIvSliceDerivationStrategy;
import com.eactive.eai.util.HexaConverter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class GenKeyFilter implements HttpAdapterFilter {
private static final Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@Override
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
ObjectNode rootNode = null;
String jsonStr = null;
if (message instanceof String) {
jsonStr = (String) message;
} else if (message instanceof JSONObject) {
jsonStr = ((JSONObject) message).toJSONString();
} else {
return message;
}
rootNode = (ObjectNode) OBJECT_MAPPER.readTree(jsonStr);
JsonNode hsmKey = rootNode.get("hsmKey");
JsonNode hsmIv = rootNode.get("hsmIv");
JsonNode contextKey = rootNode.get("contextKey");
JsonNode type = rootNode.get("type");
JsonNode enc = rootNode.get("enc");
String strEnc = "BASE64";
if(enc != null)
strEnc = enc.asText();
byte[] binIv = null;
if(hsmIv != null) {
String strIvValue = hsmIv.asText();
if(strIvValue.length() == 32||strEnc.equalsIgnoreCase("HEX"))
binIv = HexaConverter.hexToBin(strIvValue);
else
binIv = Base64.getDecoder().decode(strIvValue.trim());
}
String strEncKeyValue = hsmKey.asText();
byte[] binEncKey = null;
if(strEnc.equalsIgnoreCase("BASE64"))
binEncKey = Base64.getDecoder().decode(strEncKeyValue.trim());
else
binEncKey = HexaConverter.hexToBin(strEncKeyValue);
DerivedKey derivedKey = null;
if(type.asText().equals("douzone")) {
Map<String, String> params = new HashMap<>();
Map<String, String> runtimeContext = new HashMap<>();
params.put("contextKey", "contextKey");
runtimeContext.put("contextKey", contextKey.asText());
HsmContextSha256KeyDerivationStrategy douzoneStrategy = new HsmContextSha256KeyDerivationStrategy();
derivedKey = douzoneStrategy.deriveKey(params, runtimeContext, binEncKey);
} else if(type.asText().equals("kakaobank")) {
HsmKeyAndIvSliceDerivationStrategy kakaobank = new HsmKeyAndIvSliceDerivationStrategy();
derivedKey = kakaobank.deriveKey(binEncKey, binIv);
}
JsonNode aad = rootNode.get("aad");
if (aad != null) {
rootNode.put("generatedAdd", HexaConverter.binToHex(aad.asText().getBytes()));
}
String encBase64 = Base64.getEncoder().encodeToString(derivedKey.getDecKey());
logger.debug("generated KEY {} : [{}]", type.asText(), encBase64);
rootNode.put("generatedKeyBase64", encBase64);
rootNode.put("generatedKeyHex", HexaConverter.binToHex(derivedKey.getDecKey()));
rootNode.put("generatedKeyRaw", new String(derivedKey.getDecKey()));
if(derivedKey.getIv() != null) {
String encBase64Iv = Base64.getEncoder().encodeToString(derivedKey.getIv());
logger.debug("generated IV {} : [{}]", type.asText(), encBase64Iv);
rootNode.put("generatedIvBase64", encBase64Iv);
rootNode.put("generatedIvHex", HexaConverter.binToHex(derivedKey.getIv()));
rootNode.put("generatedIvRaw", new String(derivedKey.getIv()));
}
String jsonString = OBJECT_MAPPER.writeValueAsString(rootNode);
return jsonString;
} catch (Exception e) {
throw new FilterException("HSM key 암호화/복호화 검증 실패", ERROR_PRE_FAIL, HttpStatus.INTERNAL_SERVER_ERROR.value(), e);
}
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
return resultMessage;
}
}
@@ -15,6 +15,7 @@ import com.eactive.eai.adapter.http.dynamic.filter.FilterException;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
import com.eactive.eai.common.hsm.HsmManager;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.util.HexaConverter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -53,7 +54,7 @@ public class HSMKeyCryptoFilter implements HttpAdapterFilter {
String encBase64 = Base64.getEncoder().encodeToString(encoded);
logger.debug("HsmManager] HSM - {} : [{}]", hsmKeyAliasValue, encBase64);
rootNode.put("hsmKeyBase64", encBase64);
rootNode.put("hsmKeyRaw", new String(encoded));
rootNode.put("hsmKeyRaw", HexaConverter.binToHex(encoded));
} else {
logger.debug("HsmManager] HSM - secretKey null {} : [{}]", hsmKeyAliasValue, secretKey);
}
@@ -2,14 +2,11 @@ package com.eactive.eai.custom.common.security.keyderiv;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import javax.crypto.SecretKey;
import com.eactive.eai.common.hsm.HsmCryptoService;
import com.eactive.eai.common.security.keyderiv.DerivedKey;
import com.eactive.eai.common.security.keyderiv.KeyDerivationStrategy;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.common.security.keyderiv.strategy.BaseHsmKeyDerivationStrategy;
/**
* HSM 마스터키 + 런타임 컨텍스트값을 연결(concatenate)한 뒤 SHA-256 해싱으로 키를 도출하는 전략.
@@ -21,15 +18,18 @@ import com.eactive.eai.common.util.ApplicationContextProvider;
* "contextKey" : "X-Api-Group-Seq" -- runtimeContext에서 꺼낼 키 이름
* }
*/
public class HsmContextSha256KeyDerivationStrategy implements KeyDerivationStrategy {
public class HsmContextSha256KeyDerivationStrategy extends BaseHsmKeyDerivationStrategy {
@Override
public DerivedKey deriveKey(Map<String, String> params, Map<String, String> runtimeContext) throws Exception {
String hsmKeyAlias = required(params, PARAM_HSM_KEY_ALIAS);
String contextKey = required(params, PARAM_CONTEXT_KEY);
byte[] masterKeyBytes = super.getHsmKey(params);
return deriveKey(params, runtimeContext, masterKeyBytes);
}
SecretKey masterKey = hsmCryptoService().getSecretKey(hsmKeyAlias);
byte[] masterKeyBytes = masterKey.getEncoded();
public DerivedKey deriveKey(Map<String, String> params, Map<String, String> runtimeContext, byte[] masterKeyBytes)
throws NoSuchAlgorithmException {
String contextKey = required(params, PARAM_CONTEXT_KEY);
byte[] contextBytes = runtimeContext.getOrDefault(contextKey, "")
.getBytes(StandardCharsets.UTF_8);
@@ -55,8 +55,4 @@ public class HsmContextSha256KeyDerivationStrategy implements KeyDerivationStrat
}
return value;
}
private HsmCryptoService hsmCryptoService() {
return ApplicationContextProvider.getContext().getBean(HsmCryptoService.class);
}
}
@@ -24,7 +24,11 @@ public class HsmKeyAndIvSliceDerivationStrategy extends BaseHsmKeyDerivationStra
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);
return deriveKey(keyBytes, rawIv);
}
public DerivedKey deriveKey(byte[] keyBytes, byte[] rawIv) {
String ivHex = DatatypeConverter.printHexBinary(rawIv);
if (ivHex.length() < IV_HEX_END) {
throw new IllegalArgumentException(
"hsmIvAlias hex 길이 부족: 최소 " + IV_HEX_END + "자 필요, 실제=" + ivHex.length()
@@ -32,7 +36,7 @@ public class HsmKeyAndIvSliceDerivationStrategy extends BaseHsmKeyDerivationStra
}
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) {