Java实现加密(六)国密SM2算法

一、SM2 简介

1.1 概述

SM2 算法是基于 ECC(Elliptic Curve Cryptography)椭圆曲线密码非对称加密 算法,其密钥长度为 256bit。该算法由 国家密码管理局 于 2010年12月17号发布

国密算法,即 国家商用密码算法。是由 国家密码管理局 认定和公布的密码算法标准及其应用规范,其中部分密码算法已经称为国际标准。如:SM系列 密码,SM 代表 商密,即商业密码,是指用于商业的、不涉及国家秘密的密码技术。

国密算法包括:SM1(SCB2)、SM2、SM3、SM4、SM7、SM9,以及 ZUC(祖冲之密码)等。

其中:

  • 1) SM1、SM4、SM7、ZUC(祖冲之密码)属于 对称算法
  • 2) SM2、SM9 属于 非对称算法
  • 3) SM3 属于 杂凑算法

1.2 国密与国际密的对应关系

加密方式 国密 国际密
对称加密 SM1 AES(Advanced Encryption Standard)
非对称加密 SM2 RSA(Ron Rivest、Adi Shamir、Leonard Adleman)三人姓氏首字母拼在一起
摘要算法(杂凑) SM3 MD5(Message-Digest Algorithm)
SHA系列(Secure Hash Algorithm)
对称加密 SM4 DES(Data Encryption Standard)

1.3 优势

SM2 算法作为一种自主创新的密码算法,具有以下优势:

  1. 安全性高: 基于 椭圆曲线离散对数难题,安全性比较高,能够有效地防止黑客攻击。
  2. 效率高: 具有较高地运算效率,能够满足大量数据加密、解密和数字签名的需求。
  3. 灵活性好: 支持多种密钥长度,可根据实际需求灵活选择密钥长度,适用于不同的应用场景。
  4. 自主创新: SM2算法是我国自主创新的密码算法,具有独立的只是产权,能够保障国家关键信息系统的信息安全。

1.4 ECC加密算法 vs RSA加密算法

SM2算法 是基于 ECC 椭圆曲线算法 实现的,采用 256位 密钥长度,它的安全强度相对较高,在工程应用中难以实现,破译或求解难度基本上是指数级的。因此,SM2 算法可以用较少的计算能力提供比 RSA算法 更高的安全强度,而所需的密钥长度却远比 RSA算法低。

对比项目 ECC加密算法 RSA加密算法
密钥长度 246位 2048位
CPU占用 较少 较高
内存占用 较少 较高
网络小号 较低 较高
加密效率 较高 一般
破解难度 具有数据特性,破解难度大 相对ECC理论上容易些
抗攻击性 一般
可扩展性 一般
兼容范围 支持新版浏览器和操作系统,单存在少数不支持平台,例如:cPanel 广泛支持

二、SM2 应用场景

2.1 数据加密

  • 在非对称加密算法中,可对外公布的密钥称为 公钥,只有持有者所知的密钥称为 私钥。发送者使用接收者的公钥来加密信息,接收者用自己的私钥解密和读取该信息。

使用 SM2 非对称加密算法加解密数据的过程:

2.2 密钥协商

利用 SM2 算法进行密钥协商的过程:

  • 1) 会话双方生成自己的私钥(随机数);

  • 2) 会话双方由私钥、ECC椭圆曲线参数 G 各自计算出公钥;

  • 3) 会话双方将自己的公钥传递给对方,传递过程公开;

    (由于椭圆曲线的计算复杂性高,破解难度大,因此攻击者难以通过公钥和椭圆曲线参数 G 反推出私钥)

  • 4) 双方将自己的 私钥 与对方的 公钥 进行运算,最终得到相同的会话密钥,该会话密钥可作为共享密钥用于对称加密(例如:SM4算法)通信。

2.3 数字签名

数字签名 是一种用于 验证信息完整性、真实性和来源的技术手段。它通常用于确保数据在传输或存储过程中没有被篡改,并且可以追溯到特定的发送方。

  • 发送方使用 自己的私钥 对信息进行 加密,生成数字签名。
  • 接收方使用 发送方的公钥 对签名进行 解密验证,以验证消息的完整性和真实性。

三、Java 实现 SM2 的两种方式

3.1 Maven 依赖

bouncycastle - 1.57 版本之后,加入了对我国的 SM2、SM3、SM4算法的支持

<!-- SM2加密 -->
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.64</version>
</dependency>

3.2 实现方式一

1)SM2Utils.java
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.*;

/**
 * 国密SM2算法工具类
 **/
@Slf4j
public class SM2Utils {
   
    private static final Charset CHARSETS = StandardCharsets.UTF_8;

    private static final SM2Engine.Mode DIGEST = SM2Engine.Mode.C1C3C2;

    /**
     * 私钥转换为 {@link ECPrivateKeyParameters}
     *
     * @param key key
     * @return
     * @throws InvalidKeyException
     */
    public static ECPrivateKeyParameters privateKeyToParams(byte[] key) throws InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException {
   
        if (key == null) {
   
            throw new RuntimeException("key must be not null !");
        }
        PrivateKey privateKey = generatePrivateKey(key);
        return (ECPrivateKeyParameters) ECUtil.generatePrivateKeyParameter(privateKey);
    }

    /**
     * 生成私钥
     *
     * @param key key
     * @return
     */
    public static PrivateKey generatePrivateKey(byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException {
   
        if (key == null) {
   
            throw new RuntimeException("key must be not null !");
        }
        KeySpec keySpec = new PKCS8EncodedKeySpec(key);
        return getKeyFactory().generatePrivate(keySpec);
    }

    /**
     * 公钥转换为 {@link ECPublicKeyParameters}
     *
     * @param key key
     * @return
     * @throws InvalidKeyException
     */
    public static ECPublicKeyParameters publicKeyToParams(byte[] key) throws InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException {
   
        if (key == null) {
   
            throw new RuntimeException("key must be not null !");
        }

        // 生成公钥
        KeySpec keySpec = new X509EncodedKeySpec(key);
        PublicKey publicKey = getKeyFactory().generatePublic(keySpec);
        return (ECPublicKeyParameters) ECUtil.generatePublicKeyParameter(publicKey);
    }

    /**
     * 获取{@link KeyFactory}
     *
     * @return {@link KeyFactory}
     */
    private static KeyFactory getKeyFactory() throws NoSuchAlgorithmException {
   
        final Provider provider = new BouncyCastleProvider();
        return KeyFactory.getInstance("EC", provider);
    }

    /**
     * 加密
     *
     * @param dataStr   数据
     * @param publicKey 公钥
     * @return 加密之后的数据
     */
    public static String encrypt(String dataStr, String publicKey) {
   
        try {
   
            return Base64.toBase64String(encrypt(dataStr.getBytes(StandardCharsets.UTF_8), Base64.decode(publicKey)));
        } catch (Exception e) {
   
            throw new RuntimeException("参数加密异常");
        }
    }

    public static byte[] encrypt(byte[] data, byte[] publicKey) throws Exception {
   
        CipherParameters pubKeyParameters = new ParametersWithRandom(publicKeyToParams(publicKey));
        SM2Engine engine = new SM2Engine(DIGEST);
        engine.init(true, pubKeyParameters);
        return engine.processBlock(data, 0, data.length);
    }


    /**
     * 解密
     *
     * @param base64Data       数据
     * @param base64PrivateKey 私钥
     * @return 解密之后的数据
     */
    public static String decrypt(String base64Data, String base64PrivateKey) throws Exception {
   
        byte[] data = Base64.decode(base64Data);
        byte[] privateKey = Base64.decode(base64PrivateKey);
        return new String(decrypt(data, privateKey), CHARSETS);
    }

    public static byte[] decrypt(byte[] data, byte[] privateKey) throws Exception {
   
        CipherParameters privateKeyParameters = privateKeyToParams(privateKey);
        SM2Engine engine = new SM2Engine(DIGEST);
        engine.init(false, privateKeyParameters);
        return engine.processBlock(data, 0, data.length);
    }

    /**
     * 签名
     *
     * @param data 数据
     * @return 签名
     */
    public static byte[] sign(byte[] data, byte[] privateKey) throws Exception {
   
        SM2Signer signer = new SM2Signer();
        CipherParameters param = new ParametersWithRandom(privateKeyToParams(privateKey));
        signer.init(true, param);
        signer.update(data, 0, data.length);
        return signer.generateSignature();
    }

    /**
     * 用公钥检验数字签名的合法性
     *
     * @param data      数据
     * @param sign      签名
     * @param publicKey 公钥
     * @return 是否验证通过
     */
    public static boolean verify(byte[] data, byte[] sign, byte[] publicKey) throws Exception {
   
        SM2Signer signer = new SM2Signer();
        CipherParameters param = publicKeyToParams
  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不愿放下技术的小赵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值