Skip to content

API接口加密认证

约 5025 字大约 17 分钟

理论手册

2026-01-03

采用 SM2非对称加密 + SM4对称加密 + SM3摘要签名 的国密算法组合,确保API调用的安全性。

SM2数字签名,用于验证数据上传对象是否合法,是否是在服务端备案的客户端。

SM4对称加密业务数据:将客户端上传的业务数据进行加密,防止业务数据明文暴露出去。

SM3消息摘要签名:对业务数据进行hash计算,生成摘要签名,防止业务数据被人篡改。

流程

客户端与服务端各自生成一对SM2公私密钥对。

服务端保留服务端的SM2私钥( serverPrivateKey ),公开服务端的SM2公钥( serverPublicKey )。

客户端保留客户端的SM2私钥( clientPrivateKey ),公开客户端的SM2公钥( clientPublicKey )。

客户端上传数据到服务端--客户端操作

  1. 生成SM4对称加密的秘钥。(得到SM4对称秘钥 encryptedKey )

  2. 使用SM4对称加密需要上传的业务数据。(得到加密业务数据 encryptedData ,和SM4的随机值 iv

  3. 使用服务端的SM2公钥加密客户端生成的SM4秘钥 encryptedKey 。(得到 encryptedSm4Key

  4. 使用SM3算法计算需要上传的业务数据摘要。(得到 requestDigest

  5. 获取当前时间的时间戳。(得到 timestamp

  6. 使用SM2签名,对(clientId客户端在服务端备案的标识 + ":" + timestamp + ":" + requestDigest)的组合数据,使用客户端私钥 clientPrivateKey 进行签名。(得到 signature

  7. 组装数据

    {
      "clientId": "客户端在服务端备案的标识",
      "timestamp": "时间戳",
      "encryptedData": "SM4加密业务数据后的结果",
      "iv": "SM4加密业务数据时的随机数",
      "encryptedKey": "被加密后的SM4秘钥 encryptedSm4Key",
      "digest": "SM3计算出的业务数据摘要requestDigest",
      "signature": "SM2签名signature"
    }
  8. 发送请求到服务端。

客户端上传数据到服务端--服务端操作

  1. 校验时间戳是否超过30秒,超过30秒拒绝接收。
  2. 判断 clientId 是否有在服务端备案,没有备案拒绝接收请求。
  3. 服务端根据 clientId 获取客户端在服务端备案的客户端公钥 clientPublicKey
  4. 验证签名: 组合请求参数里的数据(clientId + ":" + timestamp + ":" + digest),然后结合 signatureclientPublicKey ,使用SM2进行验签。如果验签不通过,则说明签名有问题,服务端接收到的这份数据不是客户端原本上传的,摘要、时间戳、客户端标识、签名有可能被人篡改了,拒绝接收请求。
  5. 使用服务端的SM2私钥 serverPrivateKey ,通过SM2算法解密 encryptedKey ,获取到客户端上传的SM4的秘钥 decryptedSm4Key
  6. 使用SM4秘钥 decryptedSm4KeyivencryptedData , 通过SM4算法解密出业务数据 decryptedData
  7. 验证业务数据的完整性,验证数据是否有被篡改。通过SM3算法,计算业务数据 decryptedData 的Hash结果,然后和客户端上传的 digest 进行比较,如果数据不一致,则说明数据被篡改,直接拒绝接收请求。
  8. 到此结束,完成了客户端身份验证,完成了数据时间有效性验证,完成了数据安全未变更验证。接下来也可正常进行业务数据的处理。

案例

1. 秘钥配置类

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * 国密安全配置
 */
@Data
@Component
@ConfigurationProperties(prefix = "sm")
public class SMProperties {

    /**
     * 国密配置
     * Map<客户端标识clientId, 客户端SM2公钥>
     */
    private Map<String, String> client;

    /**
     * 国密配置
     * 服务端SM2公钥
     */
    private String serverPublicKey;

    /**
     * 国密配置
     * 服务端SM2私钥
     */
    private String serverPrivateKey;

}

application.properties 的配置案例

sm.client.71aafd0a75704b1881507fbb6e83eccd=MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEJ9ugTx95K+nD5z2hG3ignMGF2cOr7cuheP7mU0OdTeV8Og1NLAv+O+V7QcC59n+47QL2rPMn6hSocpDfLpkfsA==

sm.server-public-key=MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEtjV5EVXKwowoqUfCjtTDgIIjyKBCmNu3w5YP28s3N83IgXUmzz+6eXxMcc7+nLwJfQUk5zyH2EoMHapk1+N9pw==

sm.server-private-key=MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgWscAvwwQOs3FrU265vq0KZHcgBS3PA0jKe4IGNBgujigCgYIKoEcz1UBgi2hRANCAAS2NXkRVcrCjCipR8KO1MOAgiPIoEKY27fDlg/byzc3zciBdSbPP7p5fExxzv6cvAl9BSTnPIfYSgwdqmTX432n

2. 请求入参公共类

安全认证基础参数类

import com.alibaba.fastjson.JSONObject;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;

/**
 * 安全认证基础参数
 */
@Data
public class BaseParams implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 客户端标识
     */
    @NotBlank(message = "客户端标识不能为空")
    private String clientId;

    /**
     * 随机值
     */
    @NotBlank(message = "随机值不能为空")
    private String iv;

    /**
     * 业务加密数据
     */
    @NotBlank(message = "业务数据不能为空")
    private String encryptedData;

    /**
     * 对称密钥
     */
    @NotBlank(message = "对称密钥不能为空")
    private String encryptedKey;

    /**
     * 业务数据摘要
     */
    @NotBlank(message = "业务数据摘要不能为空")
    private String digest;

    /**
     * 签名
     */
    @NotBlank(message = "签名不能为空")
    private String signature;

    /**
     * 时间戳
     */
    @NotNull(message = "时间戳不能为空")
    private Long timestamp;

    @Override
    public String toString() {
        return JSONObject.toJSONString(this);
    }
}

入参公共类

import com.alibaba.fastjson.JSONObject;
import lombok.Data;

import javax.validation.Valid;
import java.io.Serializable;

/**
 * 请求参数
 */
@Data
public class SmParamsDto<T> extends BaseParams implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 业务数据
     */
    @Valid
    private T data;

    @Override
    public String toString() {
        String str = super.toString();
        JSONObject jsonObject = JSONObject.parseObject(str);
        jsonObject.put("data", data);
        return jsonObject.toJSONString();
    }
}

3. SM算法工具类

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.util.Base64;

/**
 * 国密算法工具类
 */
public class SMUtil {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    private static final String PROVIDER = "BC";
    private static final String SM2_CURVE_NAME = "sm2p256v1";
    private static final String SM4_ALGORITHM = "SM4";
    private static final String SM4_MODE = "SM4/CBC/PKCS5Padding";
    private static final int SM4_KEY_SIZE = 128;
    private static final int SM4_IV_LENGTH = 16;

    // ==================== SM2 非对称加密 ====================

    /**
     * 生成SM2密钥对
     */
    public static KeyPair generateSM2KeyPair() throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", PROVIDER);
        ECGenParameterSpec sm2Spec = new ECGenParameterSpec(SM2_CURVE_NAME);
        keyPairGenerator.initialize(sm2Spec);
        return keyPairGenerator.generateKeyPair();
    }

    /**
     * SM2加密
     */
    public static String sm2Encrypt(byte[] data, PublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance("SM2", PROVIDER);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] encrypted = cipher.doFinal(data);
        return Base64.getEncoder().encodeToString(encrypted);
    }

    /**
     * SM2解密
     */
    public static byte[] sm2Decrypt(String encryptedData, PrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance("SM2", PROVIDER);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] data = Base64.getDecoder().decode(encryptedData);
        return cipher.doFinal(data);
    }

    /**
     * SM2签名
     */
    public static String sm2Sign(byte[] data, PrivateKey privateKey) throws Exception {
        Signature signature = Signature.getInstance("SM3withSM2", PROVIDER);
        signature.initSign(privateKey);
        signature.update(data);
        byte[] sign = signature.sign();
        return Base64.getEncoder().encodeToString(sign);
    }

    /**
     * SM2验签
     */
    public static boolean sm2Verify(byte[] data, String sign, PublicKey publicKey) throws Exception {
        Signature signature = Signature.getInstance("SM3withSM2", PROVIDER);
        signature.initVerify(publicKey);
        signature.update(data);
        return signature.verify(Base64.getDecoder().decode(sign));
    }

    // ==================== SM3 摘要算法 ====================

    /**
     * SM3哈希计算
     */
    public static String sm3Hash(byte[] data) throws Exception {
        MessageDigest digest = MessageDigest.getInstance("SM3", PROVIDER);
        byte[] hash = digest.digest(data);
        return Base64.getEncoder().encodeToString(hash);
    }

    /**
     * SM3哈希计算(十六进制输出)
     */
    public static String sm3HashHex(byte[] data) throws Exception {
        MessageDigest digest = MessageDigest.getInstance("SM3", PROVIDER);
        byte[] hash = digest.digest(data);
        return bytesToHex(hash);
    }

    // ==================== SM4 对称加密 ====================

    /**
     * 生成SM4密钥
     */
    public static SecretKey generateSM4Key() throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(SM4_ALGORITHM, PROVIDER);
        keyGenerator.init(SM4_KEY_SIZE);
        return keyGenerator.generateKey();
    }

    /**
     * SM4加密
     */
    public static SM4EncryptResult sm4Encrypt(byte[] data, SecretKey key) throws Exception {
        byte[] iv = new byte[SM4_IV_LENGTH];
        SecureRandom random = new SecureRandom();
        random.nextBytes(iv);

        Cipher cipher = Cipher.getInstance(SM4_MODE, PROVIDER);
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);

        byte[] encrypted = cipher.doFinal(data);
        return new SM4EncryptResult(encrypted, iv);
    }

    /**
     * SM4解密
     */
    public static byte[] sm4Decrypt(byte[] encryptedData, byte[] iv, SecretKey key) throws Exception {
        Cipher cipher = Cipher.getInstance(SM4_MODE, PROVIDER);
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
        return cipher.doFinal(encryptedData);
    }

    // ==================== 辅助方法 ====================

    private static String bytesToHex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte b : bytes) {
            result.append(String.format("%02x", b));
        }
        return result.toString();
    }

    /**
     * 密钥序列化
     */
    public static String keyToString(Key key) {
        return Base64.getEncoder().encodeToString(key.getEncoded());
    }

    /**
     * 从字符串恢复SM4密钥
     */
    public static SecretKey sm4KeyFromString(String keyStr) {
        byte[] keyBytes = Base64.getDecoder().decode(keyStr);
        return new SecretKeySpec(keyBytes, SM4_ALGORITHM);
    }

    /**
     * SM4加密结果封装类
     */
    public static class SM4EncryptResult {
        private byte[] encryptedData;
        private byte[] iv;

        public SM4EncryptResult(byte[] encryptedData, byte[] iv) {
            this.encryptedData = encryptedData;
            this.iv = iv;
        }

        public byte[] getEncryptedData() {
            return encryptedData;
        }

        public byte[] getIv() {
            return iv;
        }
    }
}

4. 接口加密校验注解

import java.lang.annotation.*;

/**
 * 国密安全加密认证
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SMSecurity {

    /**
     * 参数是集合
     *
     * @return true:集合  false:非集合
     */
    boolean isList() default false;

    /**
     * 参数类型
     *
     * @return 参数类型
     */
    Class<?> type() default String.class;
}

5. 请求参数校验与解密切面类

import com.alibaba.fastjson.JSONObject;
import net.sf.json.JSONArray;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.lang.reflect.Field;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
import java.util.stream.Collectors;

/**
 * SMSecurity国密注解增强
 *
 * @author 3hgh
 * @date 2025/10/15 10:59
 */
@Aspect
@Component
public class SMSecurityAspect {

    private final SMProperties smProperties;

    public SMSecurityAspect(SMProperties smProperties) {
        this.smProperties = smProperties;
    }

    @Pointcut(value = "@annotation(security)")
    public void pointcut(SMSecurity security) {

    }

    @Around(value = "pointcut(security)")
    public Object around(ProceedingJoinPoint joinPoint, SMSecurity security) throws Throwable {
        Object[] args = joinPoint.getArgs();
        if (args == null || args.length == 0) {
            throw new RuntimeException("参数不能为空");
        }
        String paramsStr = args[0].toString();
        BaseParams baseParams = JSONObject.parseObject(paramsStr, BaseParams.class);

        if (baseParams == null) {
            throw new BusinessException("参数不能为空");
        }
        // 校验时间戳距离当前是否大于30秒
        if (System.currentTimeMillis() - baseParams.getTimestamp() > 30 * 1000) {
            throw new BusinessException("数据存在风险,拒绝接收");
        }

        // 判断是否有给这个客户端配置密钥
        if (!smProperties.getClient().containsKey(baseParams.getClientId())) {
            throw new BusinessException(baseParams.getClientId() + "客户端未备案,不可访问接口");
        }

        Security.addProvider(new BouncyCastleProvider());
        KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");

        // 服务端私钥
        String serverPrivateKeyStr = smProperties.getServerPrivateKey();
        byte[] serverPrivateKeyBytes = Base64.getDecoder().decode(serverPrivateKeyStr); // 解码Base64私钥
        PrivateKey serverPrivateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(serverPrivateKeyBytes));   // 创建SM2私钥对象

        // 客户端公钥
        String clientPublicKeyStr = smProperties.getClient().get(baseParams.getClientId());
        byte[] clientPublicKeyBytes = Base64.getDecoder().decode(clientPublicKeyStr);   // 解码Base64公钥
        PublicKey clientPublicKey = keyFactory.generatePublic(new X509EncodedKeySpec(clientPublicKeyBytes));    // 创建SM2公钥对象

        // 验证签名
        String signatureValidStr = baseParams.getClientId() + ":" + baseParams.getTimestamp() + ":" + baseParams.getDigest();
        boolean signValid = SMUtil.sm2Verify(signatureValidStr.getBytes("UTF-8"), baseParams.getSignature(), clientPublicKey);
        if (!signValid) {
            // 数据签名验证失败
            throw new BusinessException("数据存在风险,拒绝接收");
        }

        // 解密SM4密钥(用服务端SM2私钥,因为客户端是用服务端的SM2公钥进行的加密)
        byte[] decryptedKeyBytes = SMUtil.sm2Decrypt(baseParams.getEncryptedKey(), serverPrivateKey);
        SecretKey decryptedSm4Key = new SecretKeySpec(decryptedKeyBytes, "SM4");

        // 解密业务数据
        byte[] decryptedData = SMUtil.sm4Decrypt(
                Base64.getDecoder().decode(baseParams.getEncryptedData()),
                Base64.getDecoder().decode(baseParams.getIv()),
                decryptedSm4Key
        );

        // 验证数据完整性
        String decryptedDigest = SMUtil.sm3HashHex(decryptedData);
        boolean digestValid = decryptedDigest.equals(baseParams.getDigest());
        if (!digestValid) {
            // 数据被篡改
            throw new BusinessException("数据存在风险,拒绝接收");
        }

        // 将解密后的数据作为参数传递给目标方法
        Field field = joinPoint.getArgs()[0].getClass().getDeclaredField("data");
        field.setAccessible(true);
        String params = JSONObject.parseObject(decryptedData, String.class);
        Class<?> type = security.type();
        if (security.isList()) {
            field.set(joinPoint.getArgs()[0], JSONObject.parseArray(params, type));
        } else {
            field.set(joinPoint.getArgs()[0], JSONObject.parseObject(params, type));
        }

        // 重新触发Spring验证
        // 获取 Validator 实例
        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        for (Object arg : args) {
            if (arg != null && hasValidationAnnotations(arg)) {
                Set<ConstraintViolation<Object>> violations = validator.validate(arg);
                if (!violations.isEmpty()) {
                    String errorMsg = violations.stream()
                            .map(ConstraintViolation::getMessage)
                            .collect(Collectors.joining("; "));
                    throw new BusinessException(errorMsg);
                }
            }
        }

        return joinPoint.proceed();
    }

    private boolean hasValidationAnnotations(Object obj) {
        // 检查对象是否有验证注解
        return Arrays.stream(obj.getClass().getDeclaredFields())
                .anyMatch(field -> field.getAnnotations().length > 0);
    }
}

6. 服务端数据接收接口

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 数据上传接口
 */
@RestController
@RequestMapping("/demo")
public class DemoController {

    private final DemoService demoService;

    public DemoController(DemoService demoService) {
        this.demoService = demoService;
    }

    /**
     * 数据上传接口
     */
    @SMSecurity(type = DemoDto.class)
    @PostMapping("/upload")
    public Result upload(@RequestBody SmParamsDto<DemoDto> dto) {
        String id = demoService.save(dto);
        return Result.ok(id);
    }
}

7. 客户端请求示例

	public void demo(@RequestBody JSONObject json) throws Exception {
        Security.addProvider(new BouncyCastleProvider());
        KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");

        String clientId = "71aafd0a75704b1881507fbb6e83eccd";
        String timestamp = String.valueOf(System.currentTimeMillis());

        // 客户端私钥
        String clientPrivateKeyStr = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQg6Is85KPS/q1s5V6y1ycF6/7rTWSvsRHlXiAKBxuKluigCgYIKoEcz1UBgi2hRANCAAQn26BPH3kr6cPnPaEbeKCcwYXZw6vty6F4/uZTQ51N5Xw6DU0sC/475XtBwLn2f7jtAvas8yfqFKhykN8umR+w";
        byte[] clientPrivateKeyBytes = Base64.getDecoder().decode(clientPrivateKeyStr); // 解码Base64私钥
        PrivateKey clientPrivateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(clientPrivateKeyBytes));   // 创建SM2私钥对象
        // 服务端公钥
        String serverPublicKeyStr = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEtjV5EVXKwowoqUfCjtTDgIIjyKBCmNu3w5YP28s3N83IgXUmzz+6eXxMcc7+nLwJfQUk5zyH2EoMHapk1+N9pw==";
        byte[] serverPublicKeyBytes = Base64.getDecoder().decode(serverPublicKeyStr);   // 解码Base64公钥
        PublicKey serverPublicKey = keyFactory.generatePublic(new X509EncodedKeySpec(serverPublicKeyBytes));    // 创建SM2公钥对象

        // 1. 客户端构建请求
        String requestJson = JSONObject.toJSONString(json);

        // 2. 使用SM4加密业务数据
        SecretKey sm4Key = SMUtil.generateSM4Key();
        SMUtil.SM4EncryptResult sm4Result = SMUtil.sm4Encrypt(requestJson.getBytes("UTF-8"), sm4Key);

        // 3. 使用SM2加密SM4密钥(密钥交换)(使用服务端的SM2公钥)
        String encryptedSm4Key = SMUtil.sm2Encrypt(sm4Key.getEncoded(), serverPublicKey);

        // 4. 使用SM3计算请求摘要
        String requestDigest = SMUtil.sm3HashHex(requestJson.getBytes("UTF-8"));

        // 5. 使用SM2对摘要签名(客户端的SM2私钥)
        String signatureStr = clientId + ":" + timestamp + ":" + requestDigest;
        String signature = SMUtil.sm2Sign(signatureStr.getBytes("UTF-8"), clientPrivateKey);

        // 客户端发送到服务端的数据
        Map<String, String> clientData = new HashMap<String, String>();
        // 加密业务数据
        clientData.put("encryptedData", Base64.getEncoder().encodeToString(sm4Result.getEncryptedData()));
        // SM4的随机值,确保相同明文加密结果不同
        clientData.put("iv", Base64.getEncoder().encodeToString(sm4Result.getIv()));
        // 被加密后的SM4密钥
        clientData.put("encryptedKey", encryptedSm4Key);
        // 业务数据摘要
        clientData.put("digest", requestDigest);
        // 客户端签名
        clientData.put("signature", signature);
        // 客户端Id
        clientData.put("clientId", clientId);
        // 时间戳
        clientData.put("timestamp", timestamp);

				JSONObject json = JSONObject.parseObject(JSONObject.toJSONString(clientData));
    ResponseEntity<JSONObject> exchange = new RestTemplate().postForEntity("http://xxxx/demo/upload", params, JSONObject.class);
    }

理论说明

1. 国密算法组件替换

算法对应关系

国际算法国密算法用途
RSASM2非对称加密、数字签名
AESSM4对称加密
SHA256SM3消息摘要
-SM9标识密码算法(可选)

2. 核心实现方案

2.1 国密算法工具类

首先添加国密算法依赖:

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15to18</artifactId>
    <version>1.72</version>
</dependency>
@Component
public class SM2CryptoService {
    private static final String PROVIDER = "BC";
    private static final String SM2_ALGORITHM = "SM2";
    private static final String SM4_ALGORITHM = "SM4";
    private static final String SM4_MODE = "SM4/CBC/PKCS5Padding";
    
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    
    /**
     * 生成SM2密钥对
     */
    public KeyPair generateSM2KeyPair() {
        try {
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance(SM2_ALGORITHM, PROVIDER);
            keyGen.initialize(256); // SM2使用256位密钥
            return keyGen.generateKeyPair();
        } catch (Exception e) {
            throw new RuntimeException("生成SM2密钥对失败", e);
        }
    }
    
    /**
     * SM2加密
     */
    public byte[] sm2Encrypt(byte[] data, PublicKey publicKey) {
        try {
            Cipher cipher = Cipher.getInstance(SM2_ALGORITHM, PROVIDER);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return cipher.doFinal(data);
        } catch (Exception e) {
            throw new RuntimeException("SM2加密失败", e);
        }
    }
    
    /**
     * SM2解密
     */
    public byte[] sm2Decrypt(byte[] data, PrivateKey privateKey) {
        try {
            Cipher cipher = Cipher.getInstance(SM2_ALGORITHM, PROVIDER);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return cipher.doFinal(data);
        } catch (Exception e) {
            throw new RuntimeException("SM2解密失败", e);
        }
    }
}

2.2 SM4对称加密服务

@Service
public class SM4CryptoService {
    private static final String PROVIDER = "BC";
    private static final String SM4_ALGORITHM = "SM4";
    private static final String SM4_MODE = "SM4/CBC/PKCS5Padding";
    private static final int IV_LENGTH = 16; // SM4 IV长度为16字节
    
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    
    /**
     * 生成SM4密钥
     */
    public SecretKey generateSM4Key() {
        try {
            KeyGenerator keyGen = KeyGenerator.getInstance(SM4_ALGORITHM, PROVIDER);
            keyGen.init(128); // SM4使用128位密钥
            return keyGen.generateKey();
        } catch (Exception e) {
            throw new RuntimeException("生成SM4密钥失败", e);
        }
    }
    
    /**
     * 从字节数组生成SM4密钥
     */
    public SecretKey generateSM4Key(byte[] keyBytes) {
        return new SecretKeySpec(keyBytes, SM4_ALGORITHM);
    }
    
    /**
     * SM4加密
     */
    public SM4EncryptResult sm4Encrypt(byte[] data, SecretKey key) {
        try {
            // 生成IV
            byte[] iv = new byte[IV_LENGTH];
            SecureRandom random = new SecureRandom();
            random.nextBytes(iv);
            
            Cipher cipher = Cipher.getInstance(SM4_MODE, PROVIDER);
            IvParameterSpec ivSpec = new IvParameterSpec(iv);
            cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
            
            byte[] encrypted = cipher.doFinal(data);
            return new SM4EncryptResult(encrypted, iv);
        } catch (Exception e) {
            throw new RuntimeException("SM4加密失败", e);
        }
    }
    
    /**
     * SM4解密
     */
    public byte[] sm4Decrypt(byte[] encryptedData, byte[] iv, SecretKey key) {
        try {
            Cipher cipher = Cipher.getInstance(SM4_MODE, PROVIDER);
            IvParameterSpec ivSpec = new IvParameterSpec(iv);
            cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
            return cipher.doFinal(encryptedData);
        } catch (Exception e) {
            throw new RuntimeException("SM4解密失败", e);
        }
    }
    
    @Data
    @AllArgsConstructor
    public static class SM4EncryptResult {
        private byte[] encryptedData;
        private byte[] iv;
    }
}

2.3 SM3签名服务

@Service
public class SM3SignatureService {
    private static final String PROVIDER = "BC";
    private static final String SM3_ALGORITHM = "SM3";
    private static final String SM2_SIGN_ALGORITHM = "SM3withSM2";
    
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    
    /**
     * 生成SM3摘要
     */
    public byte[] sm3Hash(byte[] data) {
        try {
            MessageDigest digest = MessageDigest.getInstance(SM3_ALGORITHM, PROVIDER);
            return digest.digest(data);
        } catch (Exception e) {
            throw new RuntimeException("SM3摘要计算失败", e);
        }
    }
    
    /**
     * 生成SM2签名(使用SM3摘要)
     */
    public String sm2Sign(String data, PrivateKey privateKey) {
        try {
            Signature signature = Signature.getInstance(SM2_SIGN_ALGORITHM, PROVIDER);
            signature.initSign(privateKey);
            signature.update(data.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(signature.sign());
        } catch (Exception e) {
            throw new RuntimeException("SM2签名失败", e);
        }
    }
    
    /**
     * 验证SM2签名
     */
    public boolean sm2Verify(String data, String signature, PublicKey publicKey) {
        try {
            Signature sig = Signature.getInstance(SM2_SIGN_ALGORITHM, PROVIDER);
            sig.initVerify(publicKey);
            sig.update(data.getBytes(StandardCharsets.UTF_8));
            return sig.verify(Base64.getDecoder().decode(signature));
        } catch (Exception e) {
            throw new RuntimeException("SM2签名验证失败", e);
        }
    }
    
    /**
     * 生成带时间戳的签名字符串
     */
    public String buildSignString(String clientId, String timestamp, String nonce, 
                                 String encryptedData, String apiKey, String method, String path) {
        // 按照固定顺序拼接参数,确保签名一致性
        return String.join("|", clientId, timestamp, nonce, encryptedData, apiKey, method, path);
    }
    
    /**
     * 生成请求摘要(用于完整性校验)
     */
    public String generateRequestDigest(Map<String, String> params) {
        try {
            // 对参数进行排序后拼接
            String paramString = params.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .map(entry -> entry.getKey() + "=" + entry.getValue())
                .collect(Collectors.joining("&"));
            
            byte[] digest = sm3Hash(paramString.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(digest);
        } catch (Exception e) {
            throw new RuntimeException("生成请求摘要失败", e);
        }
    }
}

2.4 国密密钥管理器

@Component
public class SMKeyManager {
    
    private final Map<String, SMClientInfo> clientRepository;
    private final KeyPair serverKeyPair;
    
    @Autowired
    private SM2CryptoService sm2CryptoService;
    
    public SMKeyManager() {
        this.serverKeyPair = generateServerKeyPair();
        this.clientRepository = initClientRepository();
    }
    
    /**
     * 客户端信息(国密版)
     */
    @Data
    public static class SMClientInfo {
        private String clientId;           // 客户端ID
        private String clientName;         // 客户端名称
        private String apiKey;             // API密钥
        private String apiSecret;          // API密钥(SM4加密存储)
        private PublicKey sm2PublicKey;    // 客户端SM2公钥
        private PrivateKey sm2PrivateKey;  // 客户端SM2私钥(加密存储)
        private Date issueTime;            // 颁发时间
        private Date expireTime;           // 过期时间
        private List<String> permissions;  // 权限列表
        private boolean enabled;           // 是否启用
        private String keyVersion;         // 密钥版本(支持轮换)
    }
    
    /**
     * 生成服务端SM2密钥对
     */
    private KeyPair generateServerKeyPair() {
        return sm2CryptoService.generateSM2KeyPair();
    }
    
    /**
     * 注册新客户端
     */
    public SMClientInfo registerClient(String clientName, List<String> permissions) {
        try {
            // 生成客户端SM2密钥对
            KeyPair clientKeyPair = sm2CryptoService.generateSM2KeyPair();
            
            // 生成API Key和Secret
            String apiKey = generateApiKey();
            String apiSecret = generateApiSecret();
            
            SMClientInfo clientInfo = new SMClientInfo();
            clientInfo.setClientId(generateClientId());
            clientInfo.setClientName(clientName);
            clientInfo.setApiKey(apiKey);
            clientInfo.setApiSecret(encryptApiSecret(apiSecret));
            clientInfo.setSm2PublicKey(clientKeyPair.getPublic());
            clientInfo.setSm2PrivateKey(encryptPrivateKey(clientKeyPair.getPrivate()));
            clientInfo.setIssueTime(new Date());
            clientInfo.setExpireTime(Date.from(Instant.now().plus(365, ChronoUnit.DAYS)));
            clientInfo.setPermissions(permissions);
            clientInfo.setEnabled(true);
            clientInfo.setKeyVersion("v1");
            
            clientRepository.put(clientInfo.getClientId(), clientInfo);
            return clientInfo;
            
        } catch (Exception e) {
            throw new RuntimeException("注册客户端失败", e);
        }
    }
    
    /**
     * 获取客户端公钥证书(X.509格式)
     */
    public byte[] getClientCertificate(String clientId) {
        try {
            SMClientInfo clientInfo = clientRepository.get(clientId);
            if (clientInfo == null) {
                throw new RuntimeException("客户端不存在");
            }
            
            // 生成简单的X.509证书(实际应使用CA签发)
            return generateSM2Certificate(clientInfo);
        } catch (Exception e) {
            throw new RuntimeException("获取客户端证书失败", e);
        }
    }
    
    private byte[] generateSM2Certificate(SMClientInfo clientInfo) throws Exception {
        // 简化的证书生成,实际应使用完整的X.509v3证书
        String certInfo = String.format(
            "CN=%s,OU=API Client,O=Company,C=CN", 
            clientInfo.getClientId()
        );
        return certInfo.getBytes(StandardCharsets.UTF_8);
    }
    
    // 其他辅助方法...
    private String generateClientId() {
        return "CLIENT_" + UUID.randomUUID().toString().replace("-", "").substring(0, 16).toUpperCase();
    }
    
    private String generateApiKey() {
        return "AK_" + UUID.randomUUID().toString().replace("-", "").substring(0, 24).toUpperCase();
    }
    
    private String generateApiSecret() {
        return UUID.randomUUID().toString().replace("-", "") + 
               UUID.randomUUID().toString().replace("-", "").substring(0, 16);
    }
    
    private String encryptApiSecret(String secret) {
        // 使用服务端SM2私钥加密存储(实际应使用HSM)
        byte[] encrypted = sm2CryptoService.sm2Encrypt(
            secret.getBytes(StandardCharsets.UTF_8), serverKeyPair.getPublic());
        return Base64.getEncoder().encodeToString(encrypted);
    }
    
    private PrivateKey encryptPrivateKey(PrivateKey privateKey) {
        // 私钥加密存储逻辑
        return privateKey;
    }
}

2.5 国密API认证拦截器

@Component
public class SMApiAuthInterceptor implements HandlerInterceptor {
    
    @Autowired private SMKeyManager smKeyManager;
    @Autowired private SM2CryptoService sm2CryptoService;
    @Autowired private SM4CryptoService sm4CryptoService;
    @Autowired private SM3SignatureService sm3SignatureService;
    @Autowired private ReplayAttackService replayAttackService;
    
    private static final Set<String> ALLOWED_SIGNATURE_METHODS = 
        Set.of("SM3withSM2", "SM3");
    private static final Set<String> ALLOWED_ENCRYPT_METHODS = 
        Set.of("SM4", "SM2");
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, Object handler) throws Exception {
        
        try {
            // 1. 解析国密API请求
            SMApiRequest apiRequest = parseRequest(request);
            
            // 2. 基础验证
            if (!validateRequest(apiRequest)) {
                return buildErrorResponse(response, "INVALID_REQUEST", "请求格式错误");
            }
            
            // 3. 验证客户端
            SMKeyManager.SMClientInfo clientInfo = smKeyManager.getClientInfo(
                apiRequest.getHeader().getClientId());
            if (clientInfo == null || !clientInfo.isEnabled()) {
                return buildErrorResponse(response, "INVALID_CLIENT", "客户端不存在或已禁用");
            }
            
            // 4. 验证API Key
            if (!validateApiKey(apiRequest, clientInfo)) {
                return buildErrorResponse(response, "INVALID_API_KEY", "API Key验证失败");
            }
            
            // 5. 验证时间戳和Nonce
            SMApiRequest.RequestHeader header = apiRequest.getHeader();
            if (!replayAttackService.validateTimestampAndNonce(header.getTimestamp(), header.getNonce())) {
                return buildErrorResponse(response, "REPLAY_ATTACK", "请求可能为重放攻击");
            }
            
            // 6. 解密SM4密钥
            SecretKey sm4Key = decryptSm4Key(header.getEncryptKey(), clientInfo);
            
            // 7. 验证SM2签名
            if (!validateSMSignature(apiRequest, clientInfo, sm4Key, request)) {
                return buildErrorResponse(response, "INVALID_SIGNATURE", "SM2签名验证失败");
            }
            
            // 8. 解密业务数据
            String decryptedData = decryptBusinessData(apiRequest.getEncryptedData(), sm4Key, header);
            request.setAttribute("decryptedData", decryptedData);
            request.setAttribute("clientInfo", clientInfo);
            
            // 9. 记录审计日志
            logAuditInfo(request, clientInfo, true);
            
            return true;
            
        } catch (Exception e) {
            logAuditInfo(request, null, false);
            return buildErrorResponse(response, "SM_AUTH_ERROR", "国密认证异常: " + e.getMessage());
        }
    }
    
    private boolean validateSMSignature(SMApiRequest apiRequest, 
                                      SMKeyManager.SMClientInfo clientInfo, 
                                      SecretKey sm4Key, 
                                      HttpServletRequest request) {
        
        SMApiRequest.RequestHeader header = apiRequest.getHeader();
        
        // 构建待签名字符串(包含请求方法路径)
        String signString = sm3SignatureService.buildSignString(
            header.getClientId(),
            header.getTimestamp(),
            header.getNonce(),
            apiRequest.getEncryptedData(),
            header.getApiKey(),
            request.getMethod(),
            request.getRequestURI()
        );
        
        // 验证SM2签名
        return sm3SignatureService.sm2Verify(signString, apiRequest.getSignature(), 
                                           clientInfo.getSm2PublicKey());
    }
    
    private SecretKey decryptSm4Key(String encryptedKeyBase64, SMKeyManager.SMClientInfo clientInfo) {
        try {
            // 使用服务端SM2私钥解密
            byte[] encryptedKey = Base64.getDecoder().decode(encryptedKeyBase64);
            byte[] sm4KeyBytes = sm2CryptoService.sm2Decrypt(encryptedKey, 
                smKeyManager.getServerPrivateKey());
            
            return sm4CryptoService.generateSM4Key(sm4KeyBytes);
        } catch (Exception e) {
            throw new RuntimeException("解密SM4密钥失败", e);
        }
    }
    
    // 其他辅助方法...
}

2.6 国密API请求响应结构

/**
 * 国密API请求封装
 */
@Data
public class SMApiRequest<T> {
    // 请求头
    private RequestHeader header;
    // SM4加密的业务数据(Base64编码)
    private String encryptedData;
    // SM2数字签名(Base64编码)
    private String signature;
    // 请求摘要(SM3,可选)
    private String digest;
    
    @Data
    public static class RequestHeader {
        private String clientId;           // 客户端ID
        private String apiKey;             // API密钥
        private String timestamp;          // 时间戳 ISO格式
        private String nonce;              // 随机数
        private String version;            // API版本
        private String encryptKey;         // SM2加密的SM4密钥(Base64)
        private String signatureMethod;    // 签名算法(SM3withSM2)
        private String encryptMethod;      // 加密算法(SM4/SM2)
        private String keyVersion;         // 密钥版本
    }
}

/**
 * 国密API响应封装
 */
@Data
public class SMApiResponse<T> {
    private ResponseHeader header;
    private String encryptedData;
    private String signature;
    private String digest;
    private boolean success;
    private String errorCode;
    private String errorMessage;
    
    @Data
    public static class ResponseHeader {
        private String responseId;
        private String timestamp;
        private String clientId;
        private String encryptMethod;
        private String signatureMethod;
    }
}

3. 客户端调用示例(国密版)

@Service
public class SMApiClient {
    
    @Autowired
    private SM2CryptoService sm2CryptoService;
    
    @Autowired
    private SM4CryptoService sm4CryptoService;
    
    @Autowired
    private SM3SignatureService sm3SignatureService;
    
    /**
     * 调用国密API
     */
    public <T> SMApiResponse<T> callSMApi(String url, Object requestData, 
                                         Class<T> responseType, String method, String path) {
        try {
            // 1. 准备请求数据
            String requestJson = objectMapper.writeValueAsString(requestData);
            
            // 2. 生成SM4密钥
            SecretKey sm4Key = sm4CryptoService.generateSM4Key();
            
            // 3. SM4加密业务数据
            SM4CryptoService.SM4EncryptResult encryptResult = sm4CryptoService.sm4Encrypt(
                requestJson.getBytes(StandardCharsets.UTF_8), sm4Key);
            
            // 4. SM2加密SM4密钥
            byte[] encryptedKey = sm2CryptoService.sm2Encrypt(
                sm4Key.getEncoded(), serverSM2PublicKey);
            
            // 5. 生成SM2签名
            String timestamp = Instant.now().toString();
            String nonce = UUID.randomUUID().toString();
            String encryptedDataBase64 = Base64.getEncoder().encodeToString(encryptResult.getEncryptedData());
            
            String signString = sm3SignatureService.buildSignString(
                clientId, timestamp, nonce, encryptedDataBase64, apiKey, method, path);
            String signature = sm3SignatureService.sm2Sign(signString, clientSM2PrivateKey);
            
            // 6. 生成请求摘要
            Map<String, String> params = new HashMap<>();
            params.put("clientId", clientId);
            params.put("timestamp", timestamp);
            params.put("nonce", nonce);
            String digest = sm3SignatureService.generateRequestDigest(params);
            
            // 7. 组装国密请求
            SMApiRequest<Object> smRequest = new SMApiRequest<>();
            SMApiRequest.RequestHeader header = new SMApiRequest.RequestHeader();
            header.setClientId(clientId);
            header.setApiKey(apiKey);
            header.setTimestamp(timestamp);
            header.setNonce(nonce);
            header.setVersion("1.0");
            header.setEncryptKey(Base64.getEncoder().encodeToString(encryptedKey));
            header.setSignatureMethod("SM3withSM2");
            header.setEncryptMethod("SM4");
            header.setKeyVersion("v1");
            
            smRequest.setHeader(header);
            smRequest.setEncryptedData(encryptedDataBase64);
            smRequest.setSignature(signature);
            smRequest.setDigest(digest);
            
            // 8. 发送请求
            // ... HTTP客户端调用
            
            return parseSMResponse(response, responseType);
            
        } catch (Exception e) {
            throw new RuntimeException("国密API调用失败", e);
        }
    }
}

4. 安全增强特性

4.1 国密算法优势

  • 国家认证:符合国家密码管理局标准
  • 自主可控:避免对国际算法的依赖
  • 同等安全:SM2-256与RSA-2048安全性相当
  • 性能优化:在某些场景下性能优于国际算法

4.2 密钥安全管理

  • 支持SM2密钥定期轮换
  • 使用HSM硬件保护根密钥
  • 实现密钥生命周期管理

4.3 合规性要求

  • 满足《网络安全法》要求
  • 符合金融、政务等行业密码应用要求
  • 支持国家商用密码检测认证

这个国密算法方案提供了与国际算法方案同等的安全级别,同时满足国家密码法规要求,适合在对密码算法有国产化要求的场景中使用。