diff --git a/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java b/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java index 6162ddf..0117fd4 100644 --- a/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java +++ b/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java @@ -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(); } diff --git a/src/test/java/com/cisd/tms/modules/log/service/OperationAuditServiceTest.java b/src/test/java/com/cisd/tms/modules/log/service/OperationAuditServiceTest.java index 0f26969..a5d0224 100644 --- a/src/test/java/com/cisd/tms/modules/log/service/OperationAuditServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/log/service/OperationAuditServiceTest.java @@ -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(); + } } \ No newline at end of file