锋盈数科-知识库 Logo
首页
软件开发
计算机基础
Hello Halo
新手必读
关于本知识库
登录 →
锋盈数科-知识库 Logo
首页 软件开发 计算机基础 Hello Halo 新手必读 关于本知识库
登录
  1. 首页
  2. 软件开发
  3. 国密SM2算法进行数据的加密、签名和验签、解密

国密SM2算法进行数据的加密、签名和验签、解密

0
  • 软件开发
  • 发布于 2024-09-29
  • 16 次阅读
黄健
黄健

写在前面:下面的依赖是基于JDK17的,如果自己的JDK版本过低,适当降低依赖的版本,具体适配去Maven官方仓库查询

一、算法介绍

SM2算法是中国密码学研究所(中国国家加密管理局)发布的一种非对称加密算法,适用于数字签名、密钥交换和公钥加密等场景。该算法基于椭圆曲线密码学,使用椭圆曲线上的点进行运算。

SM2算法的安全性主要基于椭圆曲线离散对数问题的难解性。它采用的是一个特定的椭圆曲线参数集,该参数集已经在国际上得到了广泛的认可。SM2算法的安全性与RSA算法和Diffie-Hellman算法相当。

SM2算法的主要特点如下:

  1. 安全性高:SM2算法基于椭圆曲线离散对数问题,具有较高的安全性。
  2. 算法效率高:SM2算法的计算量相对较小,适合在资源受限的环境中使用。
  3. 适用性广泛:SM2算法可用于数字签名、密钥交换和公钥加密等多种密码应用场景。
  4. 算法标准化:SM2算法已被国际电信联盟(ITU-T)和国际标准化组织(ISO)认可为国际标准。

总的来说,SM2算法是一种安全性高、效率高且广泛应用的非对称加密算法,被广泛应用于各种密码场景中。

二、引入pom依赖

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.70</version>
</dependency>
<!--注意检查是否已经引入lombok依赖,已经引入则不需要此依赖-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>provided</scope>
</dependency>

主要是引入了工具包和lombok依赖

三、密钥对工具类

package com.hl.sm2demo.util;

import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Map;

/**
 * @ Description 国密公私钥对工具类
 */
@Slf4j
public class KeyUtils {
    public static final String PUBLIC_KEY = "publicKey";

    public static final String PRIVATE_KEY = "privateKey";

    /**
     * 生成国密公私钥对
     */
    public static Map<String, String> generateSmKey() throws Exception {
        KeyPairGenerator keyPairGenerator = null;
        SecureRandom secureRandom = new SecureRandom();
        ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
        keyPairGenerator = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
        keyPairGenerator.initialize(sm2Spec);
        keyPairGenerator.initialize(sm2Spec, secureRandom);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();
        PublicKey publicKey = keyPair.getPublic();
        String publicKeyStr = new String(Base64.getEncoder().encode(publicKey.getEncoded()));
        String privateKeyStr = new String(Base64.getEncoder().encode(privateKey.getEncoded()));
        return Map.of(PUBLIC_KEY, publicKeyStr, PRIVATE_KEY, privateKeyStr);
    }

    /**
     * 将Base64转码的公钥串,转化为公钥对象
     */
    public static PublicKey createPublicKey(String publicKey) {
        PublicKey publickey = null;
        try {
            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
            KeyFactory keyFactory = KeyFactory.getInstance("EC", new BouncyCastleProvider());
            publickey = keyFactory.generatePublic(publicKeySpec);
        } catch (Exception e) {
            log.error("将Base64转码的公钥串,转化为公钥对象异常:{}", e.getMessage(), e);
        }
        return publickey;
    }

    /**
     * 将Base64转码的私钥串,转化为私钥对象
     */
    public static PrivateKey createPrivateKey(String privateKey) {
        PrivateKey publickey = null;
        try {
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
            KeyFactory keyFactory = KeyFactory.getInstance("EC", new BouncyCastleProvider());
            publickey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        } catch (Exception e) {
            log.error("将Base64转码的私钥串,转化为私钥对象异常:{}", e.getMessage(), e);
        }
        return publickey;
    }

}

1.生成国密公私钥对 :
方法generateSmKey()使用KeyPairGenerator生成一个基于椭圆曲线"sm2p256v1"的KeyPair,这是SM2算法所使用的曲线。
公钥和私钥分别编码为Base64字符串,然后以键值对的形式存储在Map中,键分别为PUBLIC_KEY和PRIVATE_KEY。

注意:实际开发中,我们可能会要求生成的公私钥对是稳定不变的,我们可以先提前使用上面的KeyUtils 工具类方法generateSmKey()生成好公私钥字符串,加密解密需要用到时,再使用其中的转公私钥对象方法createPublicKey()、createPrivateKey()。

2.公钥对象的转换 :
方法createPublicKey(String publicKey)接收一个Base64编码的公钥字符串,通过X509EncodedKeySpec解析成公钥对象。这里使用了KeyFactory实例化EC类型的公钥。

3.私钥对象的转换 :
方法createPrivateKey(String privateKey)类似地,接收一个Base64编码的私钥字符串,通过PKCS8EncodedKeySpec解析成私钥对象。同样使用KeyFactory实例化EC类型的私钥。

四、 SM2工具类

package com.hl.sm2demo.util;

import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;

import java.security.*;

/**
 * @ Description SM2实现工具类
 */
@Slf4j
public class Sm2Util {
/*    这行代码是在Java中用于向安全系统添加Bouncy Castle安全提供器的。
    Bouncy Castle是一个流行的开源加密库,它提供了许多密码学算法和安全协议的实现。

    通过调用Security.addProvider并传入BouncyCastleProvider对象,你可以注册Bouncy Castle提供的安全服务和算法到Java的安全框架中。
    这样一来,你就可以在你的应用程序中使用Bouncy Castle所提供的加密算法、密钥生成和管理等功能。*/
    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 根据publicKey对原始数据data,使用SM2加密
     */
    public static byte[] encrypt(byte[] data, PublicKey publicKey) {
        ECPublicKeyParameters localECPublicKeyParameters = getEcPublicKeyParameters(publicKey);
        SM2Engine localSM2Engine = new SM2Engine();
        localSM2Engine.init(true, new ParametersWithRandom(localECPublicKeyParameters, new SecureRandom()));
        byte[] arrayOfByte2;
        try {
            arrayOfByte2 = localSM2Engine.processBlock(data, 0, data.length);
            return arrayOfByte2;
        } catch (InvalidCipherTextException e) {
            log.error("SM2加密失败:{}", e.getMessage(), e);
            return null;
        }
    }

    private static ECPublicKeyParameters getEcPublicKeyParameters(PublicKey publicKey) {
        ECPublicKeyParameters localECPublicKeyParameters = null;
        if (publicKey instanceof BCECPublicKey localECPublicKey) {
            ECParameterSpec localECParameterSpec = localECPublicKey.getParameters();
            ECDomainParameters localECDomainParameters = new ECDomainParameters(localECParameterSpec.getCurve(),
                    localECParameterSpec.getG(), localECParameterSpec.getN());
            localECPublicKeyParameters = new ECPublicKeyParameters(localECPublicKey.getQ(), localECDomainParameters);
        }
        return localECPublicKeyParameters;
    }

    /**
     * 根据privateKey对加密数据encode data,使用SM2解密
     */
    public static byte[] decrypt(byte[] encodeData, PrivateKey privateKey) {
        SM2Engine localSM2Engine = new SM2Engine();
        BCECPrivateKey sm2PriK = (BCECPrivateKey) privateKey;
        ECParameterSpec localECParameterSpec = sm2PriK.getParameters();
        ECDomainParameters localECDomainParameters = new ECDomainParameters(localECParameterSpec.getCurve(),
                localECParameterSpec.getG(), localECParameterSpec.getN());
        ECPrivateKeyParameters localECPrivateKeyParameters = new ECPrivateKeyParameters(sm2PriK.getD(),
                localECDomainParameters);
        localSM2Engine.init(false, localECPrivateKeyParameters);
        try {
            return localSM2Engine.processBlock(encodeData, 0, encodeData.length);
        } catch (InvalidCipherTextException e) {
            log.error("SM2解密失败:{}", e.getMessage(), e);
            return null;
        }
    }

    /**
     * 私钥签名
     */
    public static byte[] signByPrivateKey(byte[] data, PrivateKey privateKey) throws Exception {
        Signature sig = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);
        sig.initSign(privateKey);
        sig.update(data);
        return sig.sign();
    }

    /**
     * 公钥验签
     */
    public static boolean verifyByPublicKey(byte[] data, PublicKey publicKey, byte[] signature) throws Exception {
        Signature sig = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);
        sig.initVerify(publicKey);
        sig.update(data);
        return sig.verify(signature);
    }

}

1.加密:
encrypt方法使用SM2算法对原始数据进行加密。它首先获取ECPublicKeyParameters对象,然后初始化SM2Engine并进行加密处理。如果加密过程中出现InvalidCipherTextException,则记录错误日志并返回null。

2.解密:
decrypt方法根据私钥解密加密数据。同样,初始化SM2Engine,进行解密处理。如果解密过程中出现InvalidCipherTextException,则记录错误日志并返回null。

3.签名:
signByPrivateKey方法使用私钥对数据进行签名。首先,创建一个Signature实例,指定SM2签名算法,初始化签名器,更新数据,然后生成签名。

4.验证:
verifyByPublicKey方法使用公钥验证签名。创建Signature实例,指定SM2签名算法,初始化验证器,更新数据,然后执行验证。返回验证结果,即布尔值表示签名是否有效

五、案例测试Junit

package com.hl.sm2demo;

import com.hl.sm2demo.util.KeyUtils;
import com.hl.sm2demo.util.Sm2Util;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;
import java.util.Map;

@SpringBootTest
class Sm2DemoApplicationTests {

    PublicKey publicKey = null;
    PrivateKey privateKey = null;


    @Test
    public void test() throws Exception {
        //生成公私钥对
        Map<String,String> keys = KeyUtils.generateSmKey();

        String testStr = "hello JAVA";
        System.out.println("原始字符串:" + testStr);
        System.out.println("公钥:" + keys.get(KeyUtils.PUBLIC_KEY));
        publicKey = KeyUtils.createPublicKey(keys.get(KeyUtils.PUBLIC_KEY));

        System.out.println("私钥:" + keys.get(KeyUtils.PRIVATE_KEY));
        privateKey = KeyUtils.createPrivateKey(keys.get(KeyUtils.PRIVATE_KEY));

        System.out.println();

        //公钥加密
        byte[] encrypt = Sm2Util.encrypt(testStr.getBytes(), publicKey);
        //加密转base64
        String encryptBase64Str = Base64.getEncoder().encodeToString(encrypt);
        System.out.println("加密数据:" + encryptBase64Str);
        //私钥签名,方便对方收到数据后用公钥验签
        byte[] sign = Sm2Util.signByPrivateKey(testStr.getBytes(), privateKey);
        System.out.println("数据签名:" + Base64.getEncoder().encodeToString(sign));

        //公钥验签,验证通过后再进行数据解密
        boolean b = Sm2Util.verifyByPublicKey(testStr.getBytes(), publicKey, sign);
        System.out.println("数据验签:" + b);
        //私钥解密
        byte[] decode = Base64.getDecoder().decode(encryptBase64Str);
        byte[] decrypt = Sm2Util.decrypt(decode, privateKey);
        assert decrypt != null;
        System.out.println("解密数据:" + new String(decrypt));
    }
}

测试代码主要逻辑:

密钥准备:首先生成一个公私钥对字符串,再使用工具分别转换为公私钥的java对象;

加密过程:把数据使用公钥加密,把密文转换成base64编码格式,再用私钥签名;

解密过程:拿到加密数据后用公钥验签,以确保密文数据没有被第三方拦截篡改,验证通过后,base64解码,最后使用私钥进行解密,还原数据;

测试结果如下图:

六、总结

SM2是一款优秀的国产加密算法,目前已经应用到多种领域内,保障了国家信息安全 ,我们日常开发中也可以尽量多使用SM系列的加密算法,如SM3、SM4等,支持国产从点点滴滴做起!

原文链接: https://blog.csdn.net/weixin_40141628/article/details/136057891

标签: #算法 139 #知识库 257
相关文章

万字:支付“核心系统”详解 2024-11-02 15:33

专栏作者:隐墨星辰 \| 主编:陈天宇宙 这篇文章也尝试化繁为简,探寻支付系统的本质,讲清楚在线支付系统最核心的一些概念和设计理念。 虽然支付行业已经过了风头最劲的时光,但跨境支付仍然在蓬勃发展,每年依然有很多新人进入这个行业,这篇文章尝试为这些刚入行的新人提供一点帮助。 文章只介绍一些支付行业十几

资深支付架构师视角:实战从问题定义到代码落地的完整套路 2024-11-02 15:33

前言 今天从一个实际案例入手,介绍站在架构师的角度,如何识别并定义问题,提炼需求,技术方案选型,再到详细设计,最后利用AI的能力协助写出核心的代码,验证与调优。 解决问题存在一定的模式,也可以称之为框架,总结出自己的思考和解题框架,以后再碰到同类型的问题就可以如庖丁解牛一样容易。 很多年前,我写代码

Spring 实现 3 种异步接口 2024-10-18 09:07

大家好,我是苏三~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某些场景下,如果需要接口异步处理的同时,还持续不断地

重学SpringBoot3-集成Redis(五)之布隆过滤器 2024-10-08 11:24

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(五)之布隆过滤器 1. 什么是布隆过滤器? * 基本概念 适用场景 2. 使用 Redis 实现布隆过滤器 * 项目依赖 Redis 配置

设计模式第16讲——迭代器模式(Iterator) 2024-10-08 11:24

一、什么是迭代器模式 迭代器模式是一种行为型设计模式,它提供了一种统一的方式来访问集合对象中的元素,而不是暴露集合内部的表示方式。简单地说,就是将遍历集合的责任封装到一个单独的对象中,我们可以按照特定的方式访问集合中的元素。 二、角色组成 抽象迭代器(Iterator):定义了遍历聚合对象所需的方法

vue2路由和vue3路由区别及原理 2024-10-08 11:24

一、Vue2 与 Vue3 路由的区别 1. 创建路由实例方式的不同 Vue 2 中,通过 Vue.use() 注册路由插件,并通过 new VueRouter() 来创建路由实例。 import Vue from 'vue';import VueRouter from 'vue-router';i

目录

IT 外包服务商

  • 意见投递
  • zyf6619

软件开发应用

主菜单

  • 首页
  • 软件开发
  • 计算机基础
  • Hello Halo
  • 新手必读
  • 关于本知识库
Copyright © 2024 your company All Rights Reserved. Powered by Halo.