国密算法—SM2介绍及基于BC的实现_bc库设置sm2的加密模式c1c3c2-程序员宅基地

技术标签: 工具  SM2  

国密算法—SM2介绍及基于BC的实现

简介

SM2密码算法是一种椭圆(非对称)密码算法

  • 加密强度:256位(私钥长度);
  • 公私钥长度:公钥长度为64字节(512位),私钥32字节(256位);
  • 支持签名最大数据量及签名结果长度:最大签名数据量长度无限制;签名结果为64字节(但由于签名后会做ASN.1编码,实际输出长度为70-72字节);
  • 支持加密最大数据量及加密后结果长度:支持最大近128G字节数据长度;加密结果(C=C1C3C2)增加96字节【C1(64字节) + C3(32字节)】(如果首个字节为0x04则增加97字节,实际有效96字节)
私钥

SM2私钥是一个大于1且小于n-1的整数(n为SM2算法的阶,其值参见GM/T 0003),简记为k,长度为256位(32字节)。

公钥

SM2公钥是SM2曲线上的一个点,由横坐标和纵坐标两个分量来表示,记为(x,y),简记为Q,每个分量的长度为256位,总长度为512位(64字节,不包含公钥标识)。

数据格式
密钥数据格式
私钥数据格式

SM2算法私钥数据格式的ASN.1定义为:

SM2PrivateKey::=INTEGER
公钥数据格式

SM2算法公钥数据格式的ASN.1定义为:

SM2PublicKey::=BIT STRING

SM2PublicKey为BIT STRING类型,内容为04||X|||Y,其中X和Y分别标示公钥的x分量和y的分量,其长度各位256位。04用来标示公钥为非压缩格式(压缩格式用02标识)。

加密数据格式

SM2算法加密后的数据格式的ASN.1定义为:

SM2Cipher::= SEQENCE{
  XCoordinate		INTEGER,					--x 分量 32字节(256位)
  YCoordinate		INTEGER,					--y 分量 32字节(256位)
  HASH				OCTET STRING SIZE(32),	 	--杂凑值 32字节(256位)
  CipherText		OCTET STRING 				--密文   等于明文长度	
}

其中,HASH为使用SM3算法对明文数据运算得到的杂凑值,其长度固定为256位。CipherText是与明文等长的密文。因此SM2加密后的密文长度比明文长度增加了97字节(1字节04标识 + 32字节x分量 + 32字节y分量 + 32字节Hash

签名数据格式

SM2算法签名数据格式的ASN.1定义为:

SM2Signature::={
	R		INTEGER,	--签名值的第一部分 32字节(256位)
	S		INTEGER		--签名值的第二部分  32字节(256位)
}

R和S的长度各位32字节。因此签名后的数据长度为固定的64字节

计算过程
生成密钥

SM2密钥生成是指生成SM2算法的密钥对的过程,该密钥对包括私钥与之对应的公钥。其中,私钥长度为256位,公钥长度为512位。

  • 输入

  • 输出

    k SM2PrivateKey SM2私钥 256位

    Q SM2PublicKey SM2公钥 512位

详细计算过程参见GM/T 0003

加密

SM2加密是指使用指定的公开密钥对明文进行特定的加密计算,生成相应密文的过程。该密文只能由该指定公开密钥对应的私钥解密。

  • 输入

    Q SM2PublicKey SM2公钥

    m 字节串 待加密的明文数据

  • 输出

    c SM2Cipher 密文

其中:

  1. 输出参数c的格式由本规范7.2中定义;

  2. 输出参数c中的XCoordinateYCoordinate俗称C1)为随机产生的公钥的x分量和y分量256位;

  3. 输出参数c中的HASH俗称C3)的计算公式为HASH = SM3(x||m||y),其中,x,y为公钥Q的x分量和y分量;

  4. 输出参数c中的CipherText俗称C2)为加密密文,其长度等于明文长度。

详细的计算过程参见GM/T 0003GM/T 0004

解密

SM2解密是指使用指定的私钥对密文进行解密计算,还原对应明文的过程。

  • 输入

    d SM2PrivateKey SM2私钥

    c SM2Cipher 密文

  • 输出

    m 字节串 与密文对应的明文

m为SM2Cipher经过解密运算得到明文,该明文的长度与输入参数c中的CipherText(俗称C2)的长度相同。

详细的计算过程参见GM/T 0003

预处理
预处理1

预处理1是指使用签名方的用户身份标识和签名方公钥,通过运算得到Z指的过程。Z值用于预处理2,也用于SM2密钥协商协议。

  • 输入

    ID 字节串 用户身份标识

    Q SM2PublicKey 用户的公钥

  • 输出

    Z 字节串 预处理1的输出

计算公式为:

Z = SM3(ENTL||ID||a||b||XG||yG||XA||yA)

其中:

  • ENTL 为由2个字节标示的ID的比特长度;
  • ID 为用户身份标识;
  • a、b 为系统曲线参考;
  • XG、yG 为基点;
  • XA、yA 为用户公钥;

详细计算过程参见GM/T 0003GM/T 0004

预处理2

预处理2是指使用Z值和待签名消息,通过SM3运算得到的杂凑值H的过程。杂凑值H用于SM2数字签名。

  • 输入

    Z 字节串 预处理2的输入

    M 字节串 待签名消息

  • 输出

    H 字节串 杂凑值

计算公式为:

H = SM3(Z||M)

详细计算过程参见GM/T 0003GM/T 0004

数字签名

SM2签名是指使用预处理的结果和签名者的私钥,通过签名计算得到签名结果的过程。

  • 输入

    d SM2Privatekey 签名者私钥

    H 字节串 预处理2的结果

  • 输出

    sign SM2Signature 签名值

详细计算过程参见GM/T 0003

签名验证

SM2签名验证是指使用预处理2的结果、签名值和签名者的公钥。通过验签计算确定签名是否通过验证的过程。

  • 输入

    H 字节串 预处理2的结果

    sign SM2Signature 签名值

    Q PublicKey 签名者的公钥

  • 输出

    :表示验证通过;为:表示验证不通过

详细计算过程参见GM/T 0003

Java基于BC实现SM2加解密

BouncyCastle在1.57版本已经提供对SM椭圆曲线密码算法的支持,不过对SM2仅提供C1C2C3模式的加解密。在1.62+版本中已经增加了对C1C3C2模式加解密的支持。

 public enum Mode
    {
    
        C1C2C3, C1C3C2;
    }

这里使用BouncyCastle 1.68版本进行测试

@SpringBootTest
class DemoApplicationTests {
    
  
   @Test
    void contextLoads() {
    
        testSM2();
    }
  
   static void testSM2() {
    
        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);
        }

        //TODO...可对以上公钥进行分发传输

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

        /**
         * 私钥解密
         */
        //将十六进制私钥串转换为 BCECPrivateKey 私钥对象
        BCECPrivateKey bcecPrivateKey = getBCECPrivateKeyByPrivateKeyHex(privateKeyHex);
        data = decrypt(bcecPrivateKey, encryptData, 1);
        System.out.println("---->解密结果:" + data);
    } 
  
   /**
     * 生成密钥对
     */
    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;
        }
    }  
  
     /**
     * 公钥加密
     *
     * @param publicKey SM2公钥
     * @param data      明文数据
     * @param modeType  加密模式
     * @return
     */
    public static String encrypt(BCECPublicKey publicKey, String data, int modeType) {
    
        //加密模式
        SM2Engine.Mode mode;
        if (modeType == 1) {
    
            mode = SM2Engine.Mode.C1C3C2;
        } else {
    
            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);
    }
  
     /**
     * 私钥解密
     *
     * @param privateKey SM私钥
     * @param cipherData 密文数据
     * @return
     */
    public static String decrypt(BCECPrivateKey privateKey, String cipherData, int modeType) {
    
        //解密模式
        SM2Engine.Mode mode;
        if (modeType == 1) {
    
            mode = SM2Engine.Mode.C1C3C2;
        } else {
    
            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());
  
   /**
     * 公钥字符串转换为 BCECPublicKey 公钥对象
     *
     * @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);
    } 
  
  
    /**
     * 私钥字符串转换为 BCECPrivateKey 私钥对象
     *
     * @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);
    } 
}

测试结果

---->SM2公钥:045c798b7bdedff4ace6b935269b8cc79ddeac67000f4d4f94adbfc982398ce7b72343542b035a7b9242e4f470ce495d858d5129975d7ca207ddcfae59fc1eb66d
---->SM2私钥:466a7234630258356942a47fc32b848966c557aacbe7439d4a2bb397d0cf9144
---->加密结果:04bff9cab1e38d743f87b524dae8be97d3d0fce55d9f3771a5bcf02d9a6c162e96bdefb8e91ccffddacaecfb0281bbc22ae908484c5c1ecf57a7d654efc06f9a89b0ba182264eab6e0ba1a76b21edab10f4ee221b774bba979101a9e3c57affeeffcef48ead5039f4da57d8ea4b1541ec2a290530419b61cc99938f1f885fe10bb57
---->解密结果:=========待加密数据=========

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

智能推荐

分布式光纤传感器的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告_预计2026年中国分布式传感器市场规模有多大-程序员宅基地

文章浏览阅读3.2k次。本文研究全球与中国市场分布式光纤传感器的发展现状及未来发展趋势,分别从生产和消费的角度分析分布式光纤传感器的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的市场份额。主要生产商包括:FISO TechnologiesBrugg KabelSensor HighwayOmnisensAFL GlobalQinetiQ GroupLockheed MartinOSENSA Innovati_预计2026年中国分布式传感器市场规模有多大

07_08 常用组合逻辑电路结构——为IC设计的延时估计铺垫_基4布斯算法代码-程序员宅基地

文章浏览阅读1.1k次,点赞2次,收藏12次。常用组合逻辑电路结构——为IC设计的延时估计铺垫学习目的:估计模块间的delay,确保写的代码的timing 综合能给到多少HZ,以满足需求!_基4布斯算法代码

OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版-程序员宅基地

文章浏览阅读3.3k次,点赞3次,收藏5次。OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版

关于美国计算机奥赛USACO,你想知道的都在这_usaco可以多次提交吗-程序员宅基地

文章浏览阅读2.2k次。USACO自1992年举办,到目前为止已经举办了27届,目的是为了帮助美国信息学国家队选拔IOI的队员,目前逐渐发展为全球热门的线上赛事,成为美国大学申请条件下,含金量相当高的官方竞赛。USACO的比赛成绩可以助力计算机专业留学,越来越多的学生进入了康奈尔,麻省理工,普林斯顿,哈佛和耶鲁等大学,这些同学的共同点是他们都参加了美国计算机科学竞赛(USACO),并且取得过非常好的成绩。适合参赛人群USACO适合国内在读学生有意向申请美国大学的或者想锻炼自己编程能力的同学,高三学生也可以参加12月的第_usaco可以多次提交吗

MySQL存储过程和自定义函数_mysql自定义函数和存储过程-程序员宅基地

文章浏览阅读394次。1.1 存储程序1.2 创建存储过程1.3 创建自定义函数1.3.1 示例1.4 自定义函数和存储过程的区别1.5 变量的使用1.6 定义条件和处理程序1.6.1 定义条件1.6.1.1 示例1.6.2 定义处理程序1.6.2.1 示例1.7 光标的使用1.7.1 声明光标1.7.2 打开光标1.7.3 使用光标1.7.4 关闭光标1.8 流程控制的使用1.8.1 IF语句1.8.2 CASE语句1.8.3 LOOP语句1.8.4 LEAVE语句1.8.5 ITERATE语句1.8.6 REPEAT语句。_mysql自定义函数和存储过程

半导体基础知识与PN结_本征半导体电流为0-程序员宅基地

文章浏览阅读188次。半导体二极管——集成电路最小组成单元。_本征半导体电流为0

随便推点

【Unity3d Shader】水面和岩浆效果_unity 岩浆shader-程序员宅基地

文章浏览阅读2.8k次,点赞3次,收藏18次。游戏水面特效实现方式太多。咱们这边介绍的是一最简单的UV动画(无顶点位移),整个mesh由4个顶点构成。实现了水面效果(左图),不动代码稍微修改下参数和贴图可以实现岩浆效果(右图)。有要思路是1,uv按时间去做正弦波移动2,在1的基础上加个凹凸图混合uv3,在1、2的基础上加个水流方向4,加上对雾效的支持,如没必要请自行删除雾效代码(把包含fog的几行代码删除)S..._unity 岩浆shader

广义线性模型——Logistic回归模型(1)_广义线性回归模型-程序员宅基地

文章浏览阅读5k次。广义线性模型是线性模型的扩展,它通过连接函数建立响应变量的数学期望值与线性组合的预测变量之间的关系。广义线性模型拟合的形式为:其中g(μY)是条件均值的函数(称为连接函数)。另外,你可放松Y为正态分布的假设,改为Y 服从指数分布族中的一种分布即可。设定好连接函数和概率分布后,便可以通过最大似然估计的多次迭代推导出各参数值。在大部分情况下,线性模型就可以通过一系列连续型或类别型预测变量来预测正态分布的响应变量的工作。但是,有时候我们要进行非正态因变量的分析,例如:(1)类别型.._广义线性回归模型

HTML+CSS大作业 环境网页设计与实现(垃圾分类) web前端开发技术 web课程设计 网页规划与设计_垃圾分类网页设计目标怎么写-程序员宅基地

文章浏览阅读69次。环境保护、 保护地球、 校园环保、垃圾分类、绿色家园、等网站的设计与制作。 总结了一些学生网页制作的经验:一般的网页需要融入以下知识点:div+css布局、浮动、定位、高级css、表格、表单及验证、js轮播图、音频 视频 Flash的应用、ul li、下拉导航栏、鼠标划过效果等知识点,网页的风格主题也很全面:如爱好、风景、校园、美食、动漫、游戏、咖啡、音乐、家乡、电影、名人、商城以及个人主页等主题,学生、新手可参考下方页面的布局和设计和HTML源码(有用点赞△) 一套A+的网_垃圾分类网页设计目标怎么写

C# .Net 发布后,把dll全部放在一个文件夹中,让软件目录更整洁_.net dll 全局目录-程序员宅基地

文章浏览阅读614次,点赞7次,收藏11次。之前找到一个修改 exe 中 DLL地址 的方法, 不太好使,虽然能正确启动, 但无法改变 exe 的工作目录,这就影响了.Net 中很多获取 exe 执行目录来拼接的地址 ( 相对路径 ),比如 wwwroot 和 代码中相对目录还有一些复制到目录的普通文件 等等,它们的地址都会指向原来 exe 的目录, 而不是自定义的 “lib” 目录,根本原因就是没有修改 exe 的工作目录这次来搞一个启动程序,把 .net 的所有东西都放在一个文件夹,在文件夹同级的目录制作一个 exe._.net dll 全局目录

BRIEF特征点描述算法_breif description calculation 特征点-程序员宅基地

文章浏览阅读1.5k次。本文为转载,原博客地址:http://blog.csdn.net/hujingshuang/article/details/46910259简介 BRIEF是2010年的一篇名为《BRIEF:Binary Robust Independent Elementary Features》的文章中提出,BRIEF是对已检测到的特征点进行描述,它是一种二进制编码的描述子,摈弃了利用区域灰度..._breif description calculation 特征点

房屋租赁管理系统的设计和实现,SpringBoot计算机毕业设计论文_基于spring boot的房屋租赁系统论文-程序员宅基地

文章浏览阅读4.1k次,点赞21次,收藏79次。本文是《基于SpringBoot的房屋租赁管理系统》的配套原创说明文档,可以给应届毕业生提供格式撰写参考,也可以给开发类似系统的朋友们提供功能业务设计思路。_基于spring boot的房屋租赁系统论文