Java 算法SM2加密解密_java sm2加密-程序员宅基地

技术标签: Java 算法  算法  加密解密  java  

简介

什么是SM2

SM2是国家密码管理局于2010年12月17日发布的椭圆曲线公钥密码算法。

SM2 算法和 RSA 算法都是公钥密码算法,SM2 算法是一种更先进安全的算法,在我们国家商用密码体系中被用来替换 RSA 算法。

随着密码技术和计算机技术的发展,目前常用的 1024RSA 算法面临严重的安全威胁,我们国家密码管理部门经过研究,决定采用 SM2 椭圆曲线算法替换RSA算法。

SM2算法和RSA算法比较

SM2性能更优更安全:密码复杂度高、处理速度快、机器性能消耗更小

- SM2 RSA
算法结构 基本椭圆曲线(ECC) 基于特殊的可逆模幂运算
计算复杂度 完全指数级 亚指数级
存储空间 192-256bit 2048-4096bit
秘钥生成速度 较RSA算法快百倍以上
解密加密速度 较快 一般
POM依赖
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-ext-jdk15to18</artifactId>
    <version>1.68</version>
</dependency>
<dependency>
	<groupId>org.bouncycastle</groupId>
	<artifactId>bcprov-jdk15to18</artifactId>
	<version>1.68</version>
</dependency>
Java工具类
package com.file.system.tools.sm2;

import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
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 org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.util.encoders.Hex;

import java.math.BigInteger;
import java.security.*;
import java.security.spec.ECGenParameterSpec;

/**
 * @ClassName SM2Utils
 * @Description SM2算法工具类
 * @Author msx
 * @Date 2021/9/24 16:50
 * @Version 1.0
 */
public class SM2Utils {
    

    /**
     * @Description 生成秘钥对
     * @Author msx
     * @return KeyPair
     */
    public static KeyPair createECKeyPair() {
    
        //使用标准名称创建EC参数生成的参数规范
        final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");

        // 获取一个椭圆曲线类型的密钥对生成器
        final KeyPairGenerator kpg;
        try {
    
            kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
            // 使用SM2算法域参数集初始化密钥生成器(默认使用以最高优先级安装的提供者的 SecureRandom 的实现作为随机源)
            // kpg.initialize(sm2Spec);

            // 使用SM2的算法域参数集和指定的随机源初始化密钥生成器
            kpg.initialize(sm2Spec, new SecureRandom());

            // 通过密钥生成器生成密钥对
            return kpg.generateKeyPair();
        } catch (Exception e) {
    
            e.printStackTrace();
            return null;
        }
    }

    /**
     * @Description 公钥加密
     * @Author msx
     * @param publicKeyHex SM2十六进制公钥
     * @param data         明文数据
     * @return String
     */
    public static String encrypt(String publicKeyHex, String data) {
    
        return encrypt(getECPublicKeyByPublicKeyHex(publicKeyHex), data, 1);
    }

    /**
     * @Description 公钥加密
     * @Author msx
     * @param publicKey SM2公钥
     * @param data      明文数据
     * @param modeType  加密模式
     * @return String
     */
    public static String encrypt(BCECPublicKey publicKey, String data, int modeType) {
    
        //加密模式
        SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;
        if (modeType != 1) {
    
            mode = SM2Engine.Mode.C1C2C3;
        }
        //通过公钥对象获取公钥的基本域参数。
        ECParameterSpec ecParameterSpec = publicKey.getParameters();
        ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
                ecParameterSpec.getG(), ecParameterSpec.getN());
        //通过公钥值和公钥基本参数创建公钥参数对象
        ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(publicKey.getQ(), ecDomainParameters);
        //根据加密模式实例化SM2公钥加密引擎
        SM2Engine sm2Engine = new SM2Engine(mode);
        //初始化加密引擎
        sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));
        byte[] arrayOfBytes = null;
        try {
    
            //将明文字符串转换为指定编码的字节串
            byte[] in = data.getBytes("utf-8");
            //通过加密引擎对字节数串行加密
            arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
        } catch (Exception e) {
    
            System.out.println("SM2加密时出现异常:" + e.getMessage());
            e.printStackTrace();
        }
        //将加密后的字节串转换为十六进制字符串
        return Hex.toHexString(arrayOfBytes);
    }

    /**
     * @Description 私钥解密
     * @Author msx
     * @param privateKeyHex SM2十六进制私钥
     * @param cipherData    密文数据
     * @return String
     */
    public static String decrypt(String privateKeyHex, String cipherData) {
    
        return decrypt(getBCECPrivateKeyByPrivateKeyHex(privateKeyHex), cipherData, 1);
    }

    /**
     * @Description 私钥解密
     * @Author msx
     * @param privateKey SM私钥
     * @param cipherData 密文数据
     * @param modeType   解密模式
     * @return
     */
    public static String decrypt(BCECPrivateKey privateKey, String cipherData, int modeType) {
    
        //解密模式
        SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;
        if (modeType != 1)
            mode = SM2Engine.Mode.C1C2C3;
        //将十六进制字符串密文转换为字节数组(需要与加密一致,加密是:加密后的字节数组转换为了十六进制字符串)
        byte[] cipherDataByte = Hex.decode(cipherData);
        //通过私钥对象获取私钥的基本域参数。
        ECParameterSpec ecParameterSpec = privateKey.getParameters();
        ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
                ecParameterSpec.getG(), ecParameterSpec.getN());
        //通过私钥值和私钥钥基本参数创建私钥参数对象
        ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(privateKey.getD(),
                ecDomainParameters);
        //通过解密模式创建解密引擎并初始化
        SM2Engine sm2Engine = new SM2Engine(mode);
        sm2Engine.init(false, ecPrivateKeyParameters);
        String result = null;
        try {
    
            //通过解密引擎对密文字节串进行解密
            byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
            //将解密后的字节串转换为utf8字符编码的字符串(需要与明文加密时字符串转换成字节串所指定的字符编码保持一致)
            result = new String(arrayOfBytes, "utf-8");
        } catch (Exception e) {
    
            System.out.println("SM2解密时出现异常" + e.getMessage());
        }
        return result;
    }
    //椭圆曲线ECParameters ASN.1 结构
    private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
    //椭圆曲线公钥或私钥的基本域参数。
    private static ECParameterSpec ecDomainParameters = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());

    /**
     * @Description 公钥字符串转换为 BCECPublicKey 公钥对象
     * @Author msx
     * @param pubKeyHex 64字节十六进制公钥字符串(如果公钥字符串为65字节首个字节为0x04:表示该公钥为非压缩格式,操作时需要删除)
     * @return BCECPublicKey SM2公钥对象
     */
    public static BCECPublicKey getECPublicKeyByPublicKeyHex(String pubKeyHex) {
    
        //截取64字节有效的SM2公钥(如果公钥首个字节为0x04)
        if (pubKeyHex.length() > 128) {
    
            pubKeyHex = pubKeyHex.substring(pubKeyHex.length() - 128);
        }
        //将公钥拆分为x,y分量(各32字节)
        String stringX = pubKeyHex.substring(0, 64);
        String stringY = pubKeyHex.substring(stringX.length());
        //将公钥x、y分量转换为BigInteger类型
        BigInteger x = new BigInteger(stringX, 16);
        BigInteger y = new BigInteger(stringY, 16);
        //通过公钥x、y分量创建椭圆曲线公钥规范
        ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecDomainParameters);
        //通过椭圆曲线公钥规范,创建出椭圆曲线公钥对象(可用于SM2加密及验签)
        return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
    }

    /**
     * @Description 私钥字符串转换为 BCECPrivateKey 私钥对象
     * @Author msx
     * @param privateKeyHex 32字节十六进制私钥字符串
     * @return BCECPrivateKey SM2私钥对象
     */
    public static BCECPrivateKey getBCECPrivateKeyByPrivateKeyHex(String privateKeyHex) {
    
        //将十六进制私钥字符串转换为BigInteger对象
        BigInteger d = new BigInteger(privateKeyHex, 16);
        //通过私钥和私钥域参数集创建椭圆曲线私钥规范
        ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecDomainParameters);
        //通过椭圆曲线私钥规范,创建出椭圆曲线私钥对象(可用于SM2解密和签名)
        return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
    }

    public static void main(String[] args) {
    
        String publicKeyHex = null;
        String privateKeyHex = null;
        KeyPair keyPair = createECKeyPair();
        PublicKey publicKey = keyPair.getPublic();
        if (publicKey instanceof BCECPublicKey) {
    
            //获取65字节非压缩缩的十六进制公钥串(0x04)
            publicKeyHex = Hex.toHexString(((BCECPublicKey) publicKey).getQ().getEncoded(false));
            System.out.println("---->SM2公钥:" + publicKeyHex);
        }
        PrivateKey privateKey = keyPair.getPrivate();
        if (privateKey instanceof BCECPrivateKey) {
    
            //获取32字节十六进制私钥串
            privateKeyHex = ((BCECPrivateKey) privateKey).getD().toString(16);
            System.out.println("---->SM2私钥:" + privateKeyHex);
        }

        /**
         * 公钥加密
         */
        String data = "=========待加密数据=========";

        //将十六进制公钥串转换为 BCECPublicKey 公钥对象
        String encryptData = encrypt(publicKeyHex, data);
        System.out.println("---->加密结果:" + encryptData);

        /**
         * 私钥解密
         */
        //将十六进制私钥串转换为 BCECPrivateKey 私钥对象
        data = decrypt(privateKeyHex, encryptData);
        System.out.println("---->解密结果:" + data);
    }
}

扩展资料

国密即国家密码局认定的国产密码算法。主要有SM1,SM2,SM3,SM4。密钥长度和分组长度均为128位。
(1)SM1 为对称加密。其加密强度与AES相当。该算法不公开,调用该算法时,需要通过加密芯片的接口进行调用。
(2)SM2为非对称加密,基于ECC。该算法已公开。由于该算法基于ECC,故其签名速度与秘钥生成速度都快于RSA。ECC 256位(SM2采用的就是ECC 256位的一种)安全强度比RSA 2048位高,但运算速度快于RSA。
(3)SM3 消息摘要。可以用MD5作为对比理解。该算法已公开。校验结果为256位。
(4)SM4 无线局域网标准的分组数据算法。对称加密,密钥长度和分组长度均为128位。

这里有一篇关于SM1、SM2 、SM3、 SM4算法详解

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_36949163/article/details/120511612

智能推荐

低成本挖出电商API接口-程序员要注意那些事项-技术分享_pvwd-程序员宅基地

文章浏览阅读914次。通过了解API接口的相关信息后,您可以根据需求进行开发,从而获取天猫的购物车API接口数据,实现自己的电商平台的功能需求。在注册阿里云后,了解天猫购物车API接口的相关信息是必须的。参数校验:接口参数是影响接口调用结果的重要因素,我们需要在接口调用前对参数进行校验,确保参数合法、规范、完整。接口性能:电商业务接口需要承受巨大的访问量,所以我们需要保证接口的性能。总之,无论选择哪种方式,都需要您投入精力和时间,但是从长远考虑,低成本获取天猫购物车API接口可以为您的电商平台节约不少资金,提升您的竞争优势。_pvwd

rust被禁播还能玩吗_我steam上有个游戏被开发者禁封,移除那个游戏能消除被禁封的红字吗?...-程序员宅基地

文章浏览阅读2.9k次。展开全部不能,steam上有个游e69da5e887aa3231313335323631343130323136353331333431363537戏被开发者禁封,移除那个游戏不能消除被禁封的红字。导致Steam帐户被禁用的原因很多,比如非法或者开挂,大多数为你的游戏来源非法,较普遍的情况是黑卡,盗用他人信用卡进行消费。通常在1~3个月后持卡人收到账单后拒绝付款,导致帐号被禁,或者是伪造的假信用卡..._rust游戏开发者实施的封禁

【Grafana】【十二】基于Grafana的报警_grafana报警-程序员宅基地

文章浏览阅读688次。橘色线在这里开始判断(每一分钟做一次检测),红色线表示已经持续了5分钟了。_grafana报警

希尔伯特变换Hilbert Transform_希尔伯特变换公式-程序员宅基地

文章浏览阅读5.8k次,点赞4次,收藏20次。x^(t)=H[x(t)]=x(t)∗1πt\hat x(t)=\mathcal{H}[x(t)]=x(t)*\frac{1}{\pi t}x^(t)=H[x(t)]=x(t)∗πt1​也就是说,信号的希尔伯特变换就是把它经过一个冲激响应h(t)=1πth(t)=\frac{1}{\pi t}h(t)=πt1​的LTI线性时不变系统得到的输出,此系统的频率响应:H(jw)=−jsgn(w)={..._希尔伯特变换公式

10 ,正态分布,期望,方差 :_正态分布的期望和方差-程序员宅基地

文章浏览阅读2.1w次,点赞3次,收藏19次。1 , 正态分布 :公式 :图 :意义 :μ : 平均数derta : 标准差2 ,期望 :射手打中几环的概率 :期望 : 假设打 100 次,他大概能射中的平均分数是多少 ?_正态分布的期望和方差

正点原子STM32H7阿波罗 PWM输出实验_正点原子stm32h7代码-程序员宅基地

文章浏览阅读1.4k次。##PWM_正点原子stm32h7代码

随便推点

Android教程-01 Android Studio创建第一个项目_android studio创建第一个项目,并写布局-程序员宅基地

文章浏览阅读2k次,点赞2次,收藏3次。最近一直使用Android Studio 简单把Android Studio介绍下1. 首先介绍下 Android Studio的快捷键映射到Eclipse_android studio创建第一个项目,并写布局

大学 经验_大学经验-程序员宅基地

文章浏览阅读118次。自己的学校玩旅游玩社团玩 谈一场恋爱_大学经验

王坚博士专访 | 揭开国家 AI 创新平台“城市大脑”的神秘面纱-程序员宅基地

文章浏览阅读691次。阿里妹导读:王坚博士,一手打造了阿里云。在过去两年,他几乎将所有的时间和精力都投入到“城市大脑”的打造上。近日,首批国家人工智能开放创新平台名单公布,阿里云ET城市大脑成功入选。王坚博士对城市大脑有着清楚的定位:它会是未来城市,乃至于整体人类社会的关键基础建设。互联网作为基础设施的城市大脑一向善于以深入浅出比喻说明新生概念的王坚,用“电”来说明城市大脑未来所扮演的角色。_城市大脑领域的应用和服务城市管理20年经验研发创新能力强加入我国信创体系与阿里

Apache NiFi FlowFile属性_nifi如何查看每个flowfile的属性-程序员宅基地

文章浏览阅读154次。每个FlowFile都拥有多个属性,这些属性将在FlowFile的生命周期中发生变化。5、表达式语言/在Property值中使用attribute。6、表达式语言中的自定义属性。3、添加用户自定义的属性。_nifi如何查看每个flowfile的属性

习题8-3 数组循环右移 (20分)_本题要求实现一个对数组进行循环右移的简单函数:一个数组a中存有n(>0)个整数,将每-程序员宅基地

文章浏览阅读2.7k次,点赞3次,收藏8次。本题要求实现一个对数组进行循环右移的简单函数:一个数组a中存有n(>0)个整数,将每个整数循环向右移m(≥0)个位置,即把a中的数据最后m个数循环移至最前面的m个位置。函数接口定义:int ArrayShift( int a[], int n, int m );其中a[]是用户传入的数组;n是数组的大小;m是右移的位数。函数ArrayShift须将循环右移后的数组仍然存在a[]中。裁判测试程序样例:#include <stdio.h>#define MAXN 10int_本题要求实现一个对数组进行循环右移的简单函数:一个数组a中存有n(>0)个整数,将每

ACM暑假集训总结1-程序员宅基地

文章浏览阅读308次。百度之星第三场Discount题目描述学皇来到了一个餐馆吃饭。他觉得这家餐馆很好吃,于是就想办个会员。一共有 nn 种会员充值卡套餐,假设学皇这餐饭的消费为 aa 元,选择第 ii 种套餐,需要充值 b[i] * ab[i]∗a 的钱,这次吃饭可以打 c[i]\times 10c[i]×10 折,由充值的钱支付(即这次吃饭只需要从充值金额中扣除 a\times c[i]a×c[i] 元)。以后用剩余的充值的钱吃饭不再打折。请问学皇应该选择哪个套餐(必须选择恰好一个套餐),使得优惠的比例最大?优惠比