SpringBoot集成国密SM2算法实战指南

一、SM2算法概述

SM2算法是中国国家密码管理局于2010年发布的椭圆曲线公钥密码算法,属于国密算法体系的核心组成部分。它基于椭圆曲线密码学(ECC)原理,提供数字签名、密钥交换和公钥加密三大功能,旨在替代传统的RSA等国际算法。

核心优势

高安全性:基于椭圆曲线离散对数难题(ECDLP),256位密钥即可达到RSA 3072位安全级别,当前无已知有效破解方法。

计算高效:密钥长度短,计算量小,在相同安全级别下,SM2的计算速度比RSA快,尤其在签名验证方面表现优异。

自主可控:作为国产算法,SM2避免了对外国技术的依赖,符合中国密码合规要求,已广泛应用于金融、政务等关键领域。

资源消耗低:嵌入式设备上SM2的RAM占用不超过8KB,适合物联网终端应用,而同等安全的RSA实现需要50KB以上内存。

二、SM2算法原理

椭圆曲线基础

SM2算法基于椭圆曲线方程 y2=x3+ax+b的数学难题。算法使用的椭圆曲线参数由国家密码管理局指定,确保安全性与标准化。

密钥生成

  • 私钥:随机生成的256位整数 d
  • 公钥:椭圆曲线上的点 P=[d]G,其中 G是曲线基点

加密流程

SM2加密采用”公钥协商 + 对称加密 + MAC校验”的组合机制:

  1. 生成随机数:发送方生成随机数 k,计算临时公钥 C1​=[k]G
  2. 计算共享点:S=k⋅P=(x2​,y2​)
  3. 派生密钥:使用KDF函数生成对称密钥 t=KDF(x2​∣∣y2​,klen)
  4. 加密明文:C2​=M⊕t
  5. 生成哈希值:C3​=Hash(x2​∣∣M∣∣y2​)
  6. 输出密文:C=C1​∣∣C3​∣∣C2​

解密流程

接收方使用私钥 d进行解密:

  1. 解析密文得到 C1​、C3​、C2​
  2. 计算共享点 S=d⋅C1​=(x2​,y2​)
  3. 派生密钥 t=KDF(x2​∣∣y2​,klen)
  4. 恢复明文 M=C2​⊕t
  5. 验证哈希值 Hash(x2​∣∣M∣∣y2​)==C3​

三、SpringBoot集成SM2实战

1. 项目依赖配置

在SpringBoot项目的pom.xml中添加BouncyCastle依赖:

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15to18</artifactId>
    <version>1.72</version>
</dependency>

如果使用Hutool工具包,可添加:

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.22</version>
</dependency>

2. SM2密钥对生成工具类

package com.example.util;

import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import lombok.Data;

@Data
public class SM2Key {
    /** 服务端加密公钥 */
    private String serverPublicKey;
    /** 服务端解密私钥 */
    private String serverPrivateKey;
    /** 客户端加密公钥 */
    private String clientPublicKey;
    /** 客户端解密私钥 */
    private String clientPrivateKey;
}

public class SM2Util {
    
    /**
     * 生成前后端加解密密钥对
     */
    public static SM2Key generate() {
        SM2Key sm2Key = new SM2Key();
        
        // 生成服务端密钥对
        SM2 sm2 = SmUtil.sm2();
        sm2Key.setServerPublicKey(HexUtil.encodeHexStr(
            ((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false)));
        sm2Key.setClientPrivateKey(HexUtil.encodeHexStr(
            BCUtil.encodeECPrivateKey(sm2.getPrivateKey())));
        
        // 生成客户端密钥对
        sm2 = SmUtil.sm2();
        sm2Key.setClientPublicKey(HexUtil.encodeHexStr(
            ((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false)));
        sm2Key.setServerPrivateKey(HexUtil.encodeHexStr(
            BCUtil.encodeECPrivateKey(sm2.getPrivateKey())));
        
        return sm2Key;
    }
    
    /**
     * SM2加密
     * @param publicKey 公钥
     * @param data 明文数据
     * @return 密文
     */
    public static String encrypt(String publicKey, String data) {
        return SmUtil.sm2(null, publicKey)
                .encryptHex(data.getBytes(), KeyType.PublicKey)
                .substring(2); // 去掉04前缀
    }
    
    /**
     * SM2解密
     * @param privateKey 私钥
     * @param data 密文数据
     * @return 明文
     */
    public static String decrypt(String privateKey, String data) {
        return SmUtil.sm2(privateKey, null)
                .decryptStr(data.startsWith("04") ? data : "04" + data, 
                           KeyType.PrivateKey);
    }
}

3. 数字签名与验签

import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;

public class SM2SignUtil {
    
    /**
     * 生成数字签名
     */
    public static byte[] sign(byte[] message, ECPrivateKeyParameters privateKey) {
        SM2Signer signer = new SM2Signer();
        signer.init(true, privateKey);
        signer.update(message, 0, message.length);
        return signer.generateSignature();
    }
    
    /**
     * 验证数字签名
     */
    public static boolean verify(byte[] message, byte[] signature, 
                               ECPublicKeyParameters publicKey) {
        SM2Signer signer = new SM2Signer();
        signer.init(false, publicKey);
        signer.update(message, 0, message.length);
        return signer.verifySignature(signature);
    }
}

4. 请求响应加解密拦截器

@Component
public class SM2EncryptInterceptor implements HandlerInterceptor {
    
    @Autowired
    private SM2Util sm2Util;
    
    @Value("${sm2.client-public-key}")
    private String clientPublicKey;
    
    @Value("${sm2.server-private-key}")
    private String serverPrivateKey;
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, Object handler) {
        // 解密请求参数
        String encryptedData = request.getParameter("data");
        if (StringUtils.hasText(encryptedData)) {
            String decryptedData = sm2Util.decrypt(serverPrivateKey, encryptedData);
            // 将解密后的数据放入请求属性中
            request.setAttribute("decryptedData", decryptedData);
        }
        return true;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, 
                          Object handler, ModelAndView modelAndView) {
        // 加密响应数据
        if (modelAndView != null && modelAndView.getModel() != null) {
            Object data = modelAndView.getModel().get("data");
            if (data != null) {
                String encryptedData = sm2Util.encrypt(clientPublicKey, data.toString());
                modelAndView.getModel().put("data", encryptedData);
            }
        }
    }
}

5. 配置拦截器

@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Autowired
    private SM2EncryptInterceptor sm2EncryptInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(sm2EncryptInterceptor)
                .addPathPatterns("/api/**") // 需要加密的接口路径
                .excludePathPatterns("/api/public/**"); // 不需要加密的接口
    }
}

6. Controller示例

@RestController
@RequestMapping("/api/user")
public class UserController {
    
    @PostMapping("/login")
    public Result login(@RequestBody LoginRequest request) {
        // 请求数据已在拦截器中自动解密
        // 业务逻辑处理
        UserInfo userInfo = userService.login(request);
        
        // 返回结果会自动加密
        return Result.success(userInfo);
    }
}

四、SM2 vs RSA对比

维度SM2(256位)RSA(2048位)
安全级别等同RSA 3072位中等
密钥长度256位2048位
计算速度快(签名/验签效率高)
资源消耗低(适合移动设备)
签名长度64字节384字节
带宽占用减少83%
合规性中国国家标准国际标准

五、应用场景

1. 金融支付领域

  • 银行卡交易、网上银行系统
  • 数字人民币中的身份认证与数据加密
  • 银联芯片卡规范明确将SM2作为首选非对称算法

2. 电子政务

  • 政府公文流转、电子证照
  • 税务系统中的数字签名
  • 省级政务云平台采用SM2实现跨部门数据安全共享

3. 物联网与车联网

  • 设备身份认证、数据加密传输
  • 智能家居、智能交通等IoT场景
  • 电力调度系统使用SM2实现厂站端身份认证

4. SSL证书

  • 国密SSL证书(如SM2-WiSSL)用于网站HTTPS加密
  • 国产浏览器访问https://开头的网站

六、注意事项

1. 密钥管理

  • 公钥可以硬编码到前端,私钥必须放在服务器环境变量或配置中心
  • 生产环境建议使用密钥管理系统(KMS)管理私钥

2. 随机数安全

  • 每次加密/签名必须使用不同的随机数k,否则会泄露私钥
  • 必须使用国家认证的随机数发生器

3. 参数规范

  • 必须使用国密指定的椭圆曲线参数,禁止自定义曲线
  • 金融、政务场景需通过国家密码管理局的合规性测试

4. 格式处理

  • SM2密文格式通常为C1C3C2(国密标准)或C1C2C3(部分js库使用)
  • 前后端需要统一密文格式,避免加解密失败

5. 性能优化

  • 建议对敏感接口(登录、支付、修改密码等)全部启用双向SM2
  • 签名建议只签业务关键字段,防止重放攻击仍需配合timestamp + nonce

七、总结

SM2算法作为中国自主研发的密码算法,在安全性、效率和自主可控性方面具有显著优势。通过SpringBoot集成SM2,可以快速构建符合国密标准的安全应用系统。在实际项目中,需要重点关注密钥管理、随机数安全、参数规范等关键点,确保系统的安全性和合规性。

随着《密码法》的实施和信创产业的推进,SM2算法将在更多关键领域替代RSA等国际算法,成为数字中国建设的重要安全基石。


作 者:南烛
链 接:https://www.itnotes.top/archives/1377
来 源:IT笔记
文章版权归作者所有,转载请注明出处!


上一篇
下一篇