审计日志签名

This commit is contained in:
xydkj 2026-05-21 17:01:09 +08:00
parent 06a76d5f39
commit 9ec88c3093
2 changed files with 624 additions and 28 deletions

View File

@ -12,7 +12,14 @@ import com.cisd.tms.modules.log.enums.*;
import com.cisd.tms.modules.log.repository.OperationAuditLogRepository;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -21,12 +28,17 @@ import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.method.HandlerMethod;
import java.io.StringReader;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.UUID;
@ -36,7 +48,13 @@ import java.util.UUID;
public class OperationAuditService {
private static final String PROVIDER = "BC";
private static final String SIGNATURE_ALGO = "SM3withSM2";
private static final String SM2_CURVE = "sm2p256v1";
private static final String SIGNATURE_ALGORITHM = "SM3withSM2";
private static final int GM0018_ECCREF_PUBLIC_KEY_LENGTH = 132;
private static final int GM0018_ECCREF_COMPONENT_LENGTH = 64;
private static final int SM2_COORDINATE_LENGTH = 32;
private static final String ATTR_ROLE_CODE = "CURRENT_ROLE_CODE";
private static final String ATTR_AUTH_LEVEL = "CURRENT_AUTH_LEVEL";
@ -191,14 +209,17 @@ public class OperationAuditService {
throw new IllegalArgumentException("logId不能为空");
}
OperationAuditLogEntity existLog = operationAuditLogRepository.findByLogId(logId)
.orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "未找到该logId"));
if (!AuditStatus.PENDING.equals(existLog.getAuditStatus())) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "该日志已审计,禁止重复操作");
}
String d = buildSignValueSource(existLog, req.getAuditResult());
System.out.println(d);
String auditSign = trim(req.getAuditSign());
if (auditSign.isEmpty()){
throw new IllegalArgumentException("签名值不能为空");
}
@ -214,7 +235,7 @@ public class OperationAuditService {
RoleUkeyBindingEntity roleUkeyBindingEntity = list.get(0);
String publicKey = roleUkeyBindingEntity.getUkeyPubkey();
try{
verifySm2Signature(signValueSource, auditSign, publicKey);
verifySm2Signature(publicKey, signValueSource, auditSign);
} catch (Exception e){
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), e.getMessage());
}
@ -240,57 +261,294 @@ public class OperationAuditService {
private static String buildSignValueSource(OperationAuditLogEntity existLog, AuditResult auditResult) {
String signValueSource = trim(existLog.getLogId()) + "_" +
String signValueSource = existLog.getLogId() + "_" +
existLog.getOperatorRoleCode() + "_" +
existLog.getOperatorAuthLevel() + "_" +
existLog.getModuleCode() + "_" +
existLog.getActionType() + "_" +
trim(existLog.getRemoteIp()) + "_" +
existLog.getRemoteIp() + "_" +
existLog.getOperationResult() + "_" +
trim(existLog.getSummary()) + "_" +
trim(existLog.getErrorMessage()) + "_" +
existLog.getSummary() + "_" +
existLog.getErrorMessage() + "_" +
existLog.getAuditStatus() + "_" +
auditResult + "_" +
trim(existLog.getSignValue()) + "_" +
existLog.getOccurredAt();
existLog.getSignValue() + "_" +
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(existLog.getOccurredAt());
return signValueSource;
}
public boolean verifySm2Signature(String data, String signatureBase64, String publicKeyBase64) {
public void verifySm2Signature(String pubKey, String data, String sign) {
try {
byte[] keyBytes = Base64.getDecoder().decode(publicKeyBase64);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
java.security.KeyFactory keyFactory = java.security.KeyFactory.getInstance("EC", PROVIDER);
PublicKey publicKey = keyFactory.generatePublic(keySpec);
PublicKey publicKey = parsePublicKey(pubKey);
Signature signature = Signature.getInstance(SIGNATURE_ALGO, PROVIDER);
if (data == null || data.isBlank()) {
throw new IllegalArgumentException("data不能为空");
}
byte[] signatureBytes = decodeSignature(sign);
// String signData = sm3Hex(loginSignData.getBytes(StandardCharsets.UTF_8));
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, PROVIDER);
signature.initVerify(publicKey);
signature.update(data.getBytes(StandardCharsets.UTF_8));
byte[] signatureBytes = Base64.getDecoder().decode(signatureBase64);
return signature.verify(signatureBytes);
} catch (Exception e) {
throw new RuntimeException("SM2验签失败", e);
if (!signature.verify(signatureBytes)) {
throw new BizException(
ErrorCode.UNAUTHORIZED.getCode(),
"sm2验签失败"
);
}
} catch (BizException ex) {
throw ex;
} catch (IllegalArgumentException ex) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), ex.getMessage());
} catch (RuntimeException ex) {
throw new BizException(
ErrorCode.UNAUTHORIZED.getCode(),
"signature verification 执行失败"
);
} catch (Exception ex) {
throw new BizException(
ErrorCode.UNAUTHORIZED.getCode(),
"signature verification 执行失败"
);
}
}
private byte[] decodeBlob(String value, String message) {
String normalized = value == null ? "" : value.trim();
if (normalized.isEmpty()) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), message);
private byte[] decodeSignature(String value) {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("signature不能为空");
}
if (normalized.matches("(?i)^[0-9a-f]+$") && normalized.length() % 2 == 0) {
return Hex.decode(normalized);
byte[] decoded = decodeHexOrBase64(normalize(value), "签名无效");
if (isDerSignature(decoded)) {
return decoded;
}
if (decoded.length == GM0018_ECCREF_COMPONENT_LENGTH * 2) {
return rawRsToDer(gm0018SignatureToRawRs(decoded));
}
if (decoded.length == 64) {
return rawRsToDer(decoded);
}
throw new IllegalArgumentException("签名无效");
}
private PublicKey parsePublicKey(String value) throws Exception {
String trimmed = value == null ? "" : value.trim();
if (trimmed.isEmpty()) {
throw new IllegalArgumentException("公钥无效");
}
if (trimmed.contains("BEGIN")) {
return parsePemPublicKey(trimmed);
}
String normalized = normalize(trimmed);
byte[] decoded = decodeHexOrBase64(normalized, "公钥无效");
try {
return Base64.getDecoder().decode(normalized);
} catch (IllegalArgumentException ex) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), message);
return KeyFactory
.getInstance("EC", PROVIDER)
.generatePublic(new X509EncodedKeySpec(decoded));
} catch (Exception ignored) {
return parseUncompressedPointPublicKey(toPointBytes(decoded));
}
}
private PublicKey parsePemPublicKey(String pemContent) throws Exception {
try (PEMParser parser = new PEMParser(new StringReader(pemContent))) {
Object parsed = parser.readObject();
if (!(parsed instanceof SubjectPublicKeyInfo publicKeyInfo)) {
throw new IllegalArgumentException("公钥无效");
}
return new JcaPEMKeyConverter()
.setProvider(PROVIDER)
.getPublicKey(publicKeyInfo);
}
}
private PublicKey parseUncompressedPointPublicKey(byte[] pointBytes) throws Exception {
if (pointBytes.length == 64) {
byte[] rawPointBytes = pointBytes;
pointBytes = new byte[65];
pointBytes[0] = 0x04;
System.arraycopy(rawPointBytes, 0, pointBytes, 1, rawPointBytes.length);
}
if (pointBytes.length != 65 || pointBytes[0] != 0x04) {
throw new IllegalArgumentException("公钥无效");
}
ECParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec(SM2_CURVE);
ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(
parameterSpec.getCurve().decodePoint(pointBytes),
parameterSpec
);
return KeyFactory
.getInstance("EC", PROVIDER)
.generatePublic(publicKeySpec);
}
private boolean isDerSignature(byte[] signatureBytes) {
try (ASN1InputStream inputStream = new ASN1InputStream(signatureBytes)) {
ASN1Primitive primitive = inputStream.readObject();
if (!(primitive instanceof ASN1Sequence sequence) || sequence.size() != 2) {
return false;
}
return sequence.getObjectAt(0) instanceof ASN1Integer
&& sequence.getObjectAt(1) instanceof ASN1Integer;
} catch (Exception ex) {
return false;
}
}
private byte[] rawRsToDer(byte[] rawSignature) {
if (rawSignature.length != 64) {
throw new IllegalArgumentException("签名无效");
}
byte[] r = Arrays.copyOfRange(rawSignature, 0, 32);
byte[] s = Arrays.copyOfRange(rawSignature, 32, 64);
ASN1EncodableVector vector = new ASN1EncodableVector();
vector.add(new ASN1Integer(new BigInteger(1, r)));
vector.add(new ASN1Integer(new BigInteger(1, s)));
try {
return new DERSequence(vector).getEncoded(ASN1Encoding.DER);
} catch (Exception ex) {
throw new IllegalArgumentException("签名无效", ex);
}
}
private byte[] toPointBytes(byte[] keyBytes) {
if (keyBytes.length == GM0018_ECCREF_PUBLIC_KEY_LENGTH) {
return gm0018EccRefToPointBytes(keyBytes);
}
return keyBytes;
}
private byte[] gm0018EccRefToPointBytes(byte[] keyBytes) {
int bitsLittleEndian = (keyBytes[0] & 0xff)
| ((keyBytes[1] & 0xff) << 8)
| ((keyBytes[2] & 0xff) << 16)
| ((keyBytes[3] & 0xff) << 24);
int bitsBigEndian = ((keyBytes[0] & 0xff) << 24)
| ((keyBytes[1] & 0xff) << 16)
| ((keyBytes[2] & 0xff) << 8)
| (keyBytes[3] & 0xff);
if (bitsLittleEndian != 256 && bitsBigEndian != 256) {
throw new IllegalArgumentException("公钥无效");
}
byte[] pointBytes = new byte[65];
pointBytes[0] = 0x04;
System.arraycopy(
keyBytes,
4 + GM0018_ECCREF_COMPONENT_LENGTH - SM2_COORDINATE_LENGTH,
pointBytes,
1,
SM2_COORDINATE_LENGTH
);
System.arraycopy(
keyBytes,
4 + GM0018_ECCREF_COMPONENT_LENGTH * 2 - SM2_COORDINATE_LENGTH,
pointBytes,
1 + SM2_COORDINATE_LENGTH,
SM2_COORDINATE_LENGTH
);
return pointBytes;
}
private byte[] gm0018SignatureToRawRs(byte[] signatureBytes) {
byte[] rawSignature = new byte[64];
System.arraycopy(
signatureBytes,
GM0018_ECCREF_COMPONENT_LENGTH - SM2_COORDINATE_LENGTH,
rawSignature,
0,
SM2_COORDINATE_LENGTH
);
System.arraycopy(
signatureBytes,
GM0018_ECCREF_COMPONENT_LENGTH * 2 - SM2_COORDINATE_LENGTH,
rawSignature,
SM2_COORDINATE_LENGTH,
SM2_COORDINATE_LENGTH
);
return rawSignature;
}
private byte[] decodeHexOrBase64(String value, String message) {
if (value == null || value.isEmpty()) {
throw new IllegalArgumentException(message);
}
if (value.matches("(?i)^[0-9a-f]+$") && value.length() % 2 == 0) {
return Hex.decode(value);
}
try {
return Base64.getDecoder().decode(value);
} catch (IllegalArgumentException ex) {
throw new IllegalArgumentException(message, ex);
}
}
private String normalize(String value) {
String normalized = value == null ? "" : value.trim();
if (!normalized.contains("BEGIN")) {
return normalized;
}
StringBuilder builder = new StringBuilder();
for (String line : normalized.split("\\R")) {
String trimmed = line.trim();
if (!trimmed.startsWith("-----")) {
builder.append(trimmed);
}
}
return builder.toString();
}
private static String trim(String s){
return s == null ? "" : s.trim();
}

View File

@ -1,5 +1,343 @@
package com.cisd.tms.modules.log.service;
import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.common.exception.BizException;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.util.encoders.Hex;
import org.junit.jupiter.api.Test;
import java.io.StringReader;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
class OperationAuditServiceTest {
private static final String PROVIDER = "BC";
private static final String SM2_CURVE = "sm2p256v1";
private static final String SIGNATURE_ALGORITHM = "SM3withSM2";
private static final int GM0018_ECCREF_PUBLIC_KEY_LENGTH = 132;
private static final int GM0018_ECCREF_COMPONENT_LENGTH = 64;
private static final int SM2_COORDINATE_LENGTH = 32;
static {
if (Security.getProvider(PROVIDER) == null) {
Security.addProvider(new BouncyCastleProvider());
}
}
@Test
public void test() {
String data = "f9e12235524747d7be779981494d5267_AUDIT_ADMIN_FULL_AUTH_LOGIN_172.16.18.214_SUCCESS_UKey 登录 [role=AUDIT_ADMIN, principal=uid=1]__PENDING_null_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwG9NAMsrKVjm0oyYyDtDkJT8OPR03EuFTn+8cbWJevQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHJByEqhbLOINbNXQgOdGEUibWV+1Y3vTNX6SMY9+X9k=_2026-05-21 09:48:58";
String sign = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABLBYDnGWXQ1kILRDJeEx6qsQqyztWY7UNnwBfGss72UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmzc5T5LzKi/FfAT477Mt8gJM1tL3YUY3aqePWeWfSIc=";
String publickey = "AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf+ldxsRgzy7LoNRPAmkZFOPQyqm+oqCm+EMmgCifceoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANU1uz7nhgyo7vgyeCZJlY+eeH/vKRMWFFUGGdkD2xOQ";
verifyLoginSignature(publickey, data, sign);
}
public void verifyLoginSignature(String pubKey, String loginSignData, String loginSign) {
try {
PublicKey publicKey = parsePublicKey(pubKey);
if (loginSignData == null || loginSignData.isBlank()) {
throw new IllegalArgumentException("login sign data不能为空");
}
byte[] signatureBytes = decodeSignature(loginSign);
// String signData = sm3Hex(loginSignData.getBytes(StandardCharsets.UTF_8));
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, PROVIDER);
signature.initVerify(publicKey);
signature.update(loginSignData.getBytes(StandardCharsets.UTF_8));
if (!signature.verify(signatureBytes)) {
throw new BizException(
ErrorCode.UNAUTHORIZED.getCode(),
"login signature verification 执行失败"
);
} else {
System.out.println("true");
}
} catch (BizException ex) {
throw ex;
} catch (IllegalArgumentException ex) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), ex.getMessage());
} catch (RuntimeException ex) {
throw new BizException(
ErrorCode.UNAUTHORIZED.getCode(),
"login signature verification 执行失败"
);
} catch (Exception ex) {
throw new BizException(
ErrorCode.UNAUTHORIZED.getCode(),
"login signature verification 执行失败"
);
}
}
private static String sm3Hex(byte[] data) {
byte[] digest = sm3(data);
return Hex.toHexString(digest);
}
private PublicKey parsePublicKey(String value) throws Exception {
String trimmed = value == null ? "" : value.trim();
if (trimmed.isEmpty()) {
throw new IllegalArgumentException("登录公钥无效");
}
if (trimmed.contains("BEGIN")) {
return parsePemPublicKey(trimmed);
}
String normalized = normalize(trimmed);
byte[] decoded = decodeHexOrBase64(normalized, "登录公钥无效");
try {
return KeyFactory
.getInstance("EC", PROVIDER)
.generatePublic(new X509EncodedKeySpec(decoded));
} catch (Exception ignored) {
return parseUncompressedPointPublicKey(toPointBytes(decoded));
}
}
private PublicKey parsePemPublicKey(String pemContent) throws Exception {
try (PEMParser parser = new PEMParser(new StringReader(pemContent))) {
Object parsed = parser.readObject();
if (!(parsed instanceof SubjectPublicKeyInfo publicKeyInfo)) {
throw new IllegalArgumentException("登录公钥无效");
}
return new JcaPEMKeyConverter()
.setProvider(PROVIDER)
.getPublicKey(publicKeyInfo);
}
}
private PublicKey parseUncompressedPointPublicKey(byte[] pointBytes) throws Exception {
if (pointBytes.length == 64) {
byte[] rawPointBytes = pointBytes;
pointBytes = new byte[65];
pointBytes[0] = 0x04;
System.arraycopy(rawPointBytes, 0, pointBytes, 1, rawPointBytes.length);
}
if (pointBytes.length != 65 || pointBytes[0] != 0x04) {
throw new IllegalArgumentException("登录公钥无效");
}
ECParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec(SM2_CURVE);
ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(
parameterSpec.getCurve().decodePoint(pointBytes),
parameterSpec
);
return KeyFactory
.getInstance("EC", PROVIDER)
.generatePublic(publicKeySpec);
}
private static byte[] sm3(byte[] data) {
SM3Digest digest = new SM3Digest();
digest.update(data, 0, data.length);
byte[] result = new byte[digest.getDigestSize()];
digest.doFinal(result, 0);
return result;
}
private byte[] decodeSignature(String value) {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("login signature不能为空");
}
byte[] decoded = decodeHexOrBase64(normalize(value), "登录签名无效");
if (isDerSignature(decoded)) {
return decoded;
}
if (decoded.length == GM0018_ECCREF_COMPONENT_LENGTH * 2) {
return rawRsToDer(gm0018SignatureToRawRs(decoded));
}
if (decoded.length == 64) {
return rawRsToDer(decoded);
}
throw new IllegalArgumentException("登录签名无效");
}
private boolean isDerSignature(byte[] signatureBytes) {
try (ASN1InputStream inputStream = new ASN1InputStream(signatureBytes)) {
ASN1Primitive primitive = inputStream.readObject();
if (!(primitive instanceof ASN1Sequence sequence) || sequence.size() != 2) {
return false;
}
return sequence.getObjectAt(0) instanceof ASN1Integer
&& sequence.getObjectAt(1) instanceof ASN1Integer;
} catch (Exception ex) {
return false;
}
}
private byte[] rawRsToDer(byte[] rawSignature) {
if (rawSignature.length != 64) {
throw new IllegalArgumentException("登录签名无效");
}
byte[] r = Arrays.copyOfRange(rawSignature, 0, 32);
byte[] s = Arrays.copyOfRange(rawSignature, 32, 64);
ASN1EncodableVector vector = new ASN1EncodableVector();
vector.add(new ASN1Integer(new BigInteger(1, r)));
vector.add(new ASN1Integer(new BigInteger(1, s)));
try {
return new DERSequence(vector).getEncoded(ASN1Encoding.DER);
} catch (Exception ex) {
throw new IllegalArgumentException("登录签名无效", ex);
}
}
private byte[] toPointBytes(byte[] keyBytes) {
if (keyBytes.length == GM0018_ECCREF_PUBLIC_KEY_LENGTH) {
return gm0018EccRefToPointBytes(keyBytes);
}
return keyBytes;
}
private byte[] gm0018EccRefToPointBytes(byte[] keyBytes) {
int bitsLittleEndian = (keyBytes[0] & 0xff)
| ((keyBytes[1] & 0xff) << 8)
| ((keyBytes[2] & 0xff) << 16)
| ((keyBytes[3] & 0xff) << 24);
int bitsBigEndian = ((keyBytes[0] & 0xff) << 24)
| ((keyBytes[1] & 0xff) << 16)
| ((keyBytes[2] & 0xff) << 8)
| (keyBytes[3] & 0xff);
if (bitsLittleEndian != 256 && bitsBigEndian != 256) {
throw new IllegalArgumentException("登录公钥无效");
}
byte[] pointBytes = new byte[65];
pointBytes[0] = 0x04;
System.arraycopy(
keyBytes,
4 + GM0018_ECCREF_COMPONENT_LENGTH - SM2_COORDINATE_LENGTH,
pointBytes,
1,
SM2_COORDINATE_LENGTH
);
System.arraycopy(
keyBytes,
4 + GM0018_ECCREF_COMPONENT_LENGTH * 2 - SM2_COORDINATE_LENGTH,
pointBytes,
1 + SM2_COORDINATE_LENGTH,
SM2_COORDINATE_LENGTH
);
return pointBytes;
}
private byte[] gm0018SignatureToRawRs(byte[] signatureBytes) {
byte[] rawSignature = new byte[64];
System.arraycopy(
signatureBytes,
GM0018_ECCREF_COMPONENT_LENGTH - SM2_COORDINATE_LENGTH,
rawSignature,
0,
SM2_COORDINATE_LENGTH
);
System.arraycopy(
signatureBytes,
GM0018_ECCREF_COMPONENT_LENGTH * 2 - SM2_COORDINATE_LENGTH,
rawSignature,
SM2_COORDINATE_LENGTH,
SM2_COORDINATE_LENGTH
);
return rawSignature;
}
private byte[] decodeHexOrBase64(String value, String message) {
if (value == null || value.isEmpty()) {
throw new IllegalArgumentException(message);
}
if (value.matches("(?i)^[0-9a-f]+$") && value.length() % 2 == 0) {
return Hex.decode(value);
}
try {
return Base64.getDecoder().decode(value);
} catch (IllegalArgumentException ex) {
throw new IllegalArgumentException(message, ex);
}
}
private String normalize(String value) {
String normalized = value == null ? "" : value.trim();
if (!normalized.contains("BEGIN")) {
return normalized;
}
StringBuilder builder = new StringBuilder();
for (String line : normalized.split("\\R")) {
String trimmed = line.trim();
if (!trimmed.startsWith("-----")) {
builder.append(trimmed);
}
}
return builder.toString();
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b & 0xFF));
}
return sb.toString();
}
}