审计日志签名
This commit is contained in:
parent
06a76d5f39
commit
9ec88c3093
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user