6.开源非对称加密算法SM2实现

news/2024/7/10 21:24:57 标签: java, SM2, RSA, 加密, 国密, 开源, 签名

SM2_0">6.开源非对称加密算法SM2实现

前期内容导读:

  1. 开源加解密RSA/AES/SHA1/PGP/SM2/SM3/SM4介绍
  2. 开源AES/SM4/3DES对称加密算法介绍及其实现
  3. 开源AES/SM4/3DES对称加密算法的验证实现
  4. 开源非对称加密算法RSA/SM2实现及其应用
  5. 开源非对称加密算法RSA实现

1. 开源组件 非对称秘钥加密介绍

  • 加密组件引入方法:
    <dependency>
        <groupId>com.biuqu</groupId>
        <artifactId>bq-encryptor</artifactId>
        <version>1.0.1</version>
    </dependency>
    

SM2_17">1.1 SM2的加解密实现

  • 加解密核心逻辑
    java">public byte[] doCipher(byte[] data, byte[] key, int cipherMode)
    {
        SM2Engine.Mode mode = SM2Engine.Mode.C1C2C3;
        if (!this.getPaddingMode().equalsIgnoreCase(String.valueOf(DEFAULT_MODE)))
        {
            mode = SM2Engine.Mode.C1C3C2;
        }
    
        SM2Engine sm2Engine = new SM2Engine(mode);
    
        this.initSm2Engine(sm2Engine, key, cipherMode);
    
        try
        {
            return sm2Engine.processBlock(data, 0, data.length);
        }
        catch (Exception e)
        {
            throw new EncryptionException("failed to do sm2 cipher.", e);
        }
    }
    
    private void initSm2Engine(SM2Engine sm2Engine, byte[] key, int cipherMode)
    {
        if (Cipher.ENCRYPT_MODE == cipherMode)
        {
            ECPublicKey keyObj = (ECPublicKey)this.toPubKey(key);
            ECDomainParameters domainParam = this.getDomainParam(keyObj);
            ECKeyParameters keyParam = new ECPublicKeyParameters(keyObj.getQ(), domainParam);
            byte[] initKey = UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8);
            sm2Engine.init(true, new ParametersWithRandom(keyParam, this.createRandom(initKey)));
        }
        else
        {
            ECPrivateKey keyObj = (ECPrivateKey)this.toPriKey(key);
            ECDomainParameters domainParam = this.getDomainParam(keyObj);
            ECKeyParameters keyParam = new ECPrivateKeyParameters(keyObj.getD(), domainParam);
            sm2Engine.init(false, keyParam);
        }
    }  
    

    说明:

    1. 上面的代码阐述了加解密的核心流程:根据二进制生成秘钥,再基于单独的API计算得到加解密结果,该计算逻辑完全不同于以往的加解密API;
    2. 通过上述核心代码逻辑,再对比上篇5.非对称加密算法RSA实现,可知SM2本身是支持分段的;
    3. 通过秘钥二进制反向生成秘钥对象是一个有意思且有点复杂的事情,后面再单独说明;

SM2_66">1.2 SM2生成秘钥及转换实现

  • 秘钥生成逻辑

    java">public KeyPair createKey(byte[] initKey)
    {
        try
        {
            ECGenParameterSpec paramSpec = new ECGenParameterSpec(SM2_VERSION);
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM, this.getProvider());
            if (null == initKey)
            {
                initKey = UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8);
            }
            SecureRandom random = this.createRandom(initKey);
            keyGen.initialize(paramSpec, random);
            return keyGen.generateKeyPair();
        }
        catch (Exception e)
        {
            throw new EncryptionException("failed to get sm2 key.", e);
        }
    }
    
  • 公钥、私钥反向生成逻辑

    java">public PublicKey toPubKey(byte[] pubKey)
    {
        try
        {
            String hexKey = Hex.toHexString(pubKey);
            KeyFactory kf = KeyFactory.getInstance(ALGORITHM, this.getProvider());
            if (hexKey.startsWith(STANDARD_HEX_KEY_PREFIX))
            {
                return kf.generatePublic(new X509EncodedKeySpec(pubKey));
            }
            else
            {
                // 获取SM2相关参数
                X9ECParameters ecParam = GMNamedCurves.getByName(SM2_VERSION);
                // 将公钥HEX字符串转换为椭圆曲线对应的点
                ECCurve ecCurve = ecParam.getCurve();
                ECPoint ecPoint = ecCurve.decodePoint(pubKey);
                // 椭圆曲线参数规格
                ECParameterSpec ecSpec = new ECParameterSpec(ecCurve, ecParam.getG(), ecParam.getN(), ecParam.getH());
                // 将椭圆曲线点转为公钥KEY对象
                return kf.generatePublic(new ECPublicKeySpec(ecPoint, ecSpec));
            }
        }
        catch (Exception e)
        {
            throw new EncryptionException("failed to get sm2 pub key.", e);
        }
    }
    
    public PrivateKey toPriKey(byte[] priKey)
    {
        try
        {
            String hexKey = Hex.toHexString(priKey);
            KeyFactory kf = KeyFactory.getInstance(ALGORITHM, this.getProvider());
            if (hexKey.startsWith(STANDARD_HEX_KEY_PREFIX))
            {
                PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(priKey);
                return kf.generatePrivate(keySpec);
            }
            else
            {
                // 获取SM2相关参数
                X9ECParameters ecParam = GMNamedCurves.getByName(SM2_VERSION);
                ECCurve ecCurve = ecParam.getCurve();
                // 椭圆曲线参数规格
                ECParameterSpec ecSpec = new ECParameterSpec(ecCurve, ecParam.getG(), ecParam.getN(), ecParam.getH());
                // 将私钥HEX字符串转换为16进制的数字值
                BigInteger bigInteger = new BigInteger(Hex.toHexString(priKey), EncryptionConst.HEX_UNIT);
                // 将X值转为私钥KEY对象
                return kf.generatePrivate(new ECPrivateKeySpec(bigInteger, ecSpec));
            }
        }
        catch (Exception e)
        {
            throw new EncryptionException("failed to get sm2 pri key.", e);
        }
    }
    

    说明:SM2基于椭圆的原理来加解密,其秘钥生成和解析方式也与其它方式不同。

    1. SM2支持标准的秘钥生成方式:
    java">BaseSingleSignature sm2 = new Sm2Encryption();
    KeyPair keyPair = sm2.createKey(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));
    byte[] priKey0 = keyPair.getPrivate().getEncoded();
    byte[] pubKey0 = keyPair.getPublic().getEncoded();
    
    1. SM2支持非标准的秘钥生成方式:
    java">BaseSingleSignature sm2 = new Sm2Encryption();
    KeyPair keyPair = sm2.createKey(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));
    byte[] priKey1 = ((BCECPrivateKey)keyPair.getPrivate()).getD().toByteArray();
    byte[] pubKey1 = ((BCECPublicKey)keyPair.getPublic()).getQ().getEncoded(false);
    byte[] pubKey2 = ((BCECPublicKey)keyPair.getPublic()).getQ().getEncoded(true);
    
    1. 上述正文部分的秘钥转换逻辑可以无感兼容上述各种秘钥场景。有兴趣可以看看此算法的单元测试类。PS:网上资料通常只描述了其中一种秘钥生成场景,但是相互间是不兼容的。
  • 签名和验签判定逻辑:

    java">public byte[] sign(byte[] data, byte[] key)
    {
        try
        {
            PrivateKey priKey = this.toPriKey(key);
            Signature signature = Signature.getInstance(this.getSignatureAlg(), this.getProvider());
            signature.initSign(priKey);
            signature.update(data);
            return signature.sign();
        }
        catch (Exception e)
        {
            throw new EncryptionException("failed to signature.", e);
        }
    }
    
    public boolean verify(byte[] data, byte[] key, byte[] sign)
    {
        try
        {
            PublicKey pubKey = this.toPubKey(key);
            Signature signature = Signature.getInstance(this.getSignatureAlg(), this.getProvider());
            signature.initVerify(pubKey);
            signature.update(data);
            return signature.verify(sign);
        }
        catch (Exception e)
        {
            throw new EncryptionException("failed to verify signature.", e);
        }
    }
    
  • SM2加密批量验证逻辑

    java">@Test
    public void encrypt()
    {
        int[] encLengths = {256};
        super.encrypt(encLengths);
    }
    
    @Test
    public void testEncryptAndSign()
    {
        String initKey = UUID.randomUUID() + new String(RandomUtils.nextBytes(5000), StandardCharsets.UTF_8);
        int[] encLengths = {256};
        BaseSingleSignature encryption = new Sm2Encryption();
        for (int encLen : encLengths)
        {
            encryption.setEncryptLen(encLen);
            KeyPair keyPair = encryption.createKey(initKey.getBytes(StandardCharsets.UTF_8));
            super.testEncryptAndSign(encryption, keyPair.getPrivate().getEncoded(), keyPair.getPublic().getEncoded());
        }
    }  
    

    说明:

    1. SM2可以不用设置加密长度,因为默认只有一个,同理我们也无需关心其填充算法;
    2. 通过单元测试对比RSA算法可知,SM2由于秘钥非常短,其秘钥生成和加解密效率明显高于RSA

2. 总结:

  1. 基于BouncyCastleSM2由于其算法独特性,与其它的算法实现差异加大,但是在实际使用时,由于其秘钥非常短,在核心的加解密执行效率上是有一定优势的;
  2. SM2支持多种方式的秘钥生成,本开源组件较好地解决了秘钥不同方式生成的兼容问题;
  3. SM2算法相对来说还比较新,在有些秘改(国密改造)场景时,还无法做到。因为大部分开源组件只支持标准协议,一般支持p256v1非对称加密,但是不支持sm2p256v1,国人仍需努力。

http://www.niftyadmin.cn/n/371169.html

相关文章

JavaEE进阶5/24(Spring入门)

1.IOC 控制反转 把对象的生命周期的控制权由程序员反转给其他人。 控制反转减少了代码的耦合性。 哪里发生了反转&#xff1f;f 1.对象生命周期的控制权由程序员转交给Spring 2.对象创建的顺序反转了&#xff0c;原本程序员通过new来创建的是从外层到内层的&#xff0c;控制反转…

九耶丨阁瑞钛伦特-springmvc(三)

SpringMVC作为一种流行的Java Web框架&#xff0c;是基于Spring之上的。它提供了强大的MVC&#xff08;Model-View-Controller&#xff09;架构&#xff0c;能够快速地实现Java Web开发&#xff0c;高效地与数据交互。如何使用SpringMVC成为开发人员的首要问题。要了解SpringMV…

抖音seo源码搭建,抖音矩阵系统源码分发,抖音矩阵同步分发

前言&#xff1a;抖音seo源码&#xff0c;抖音矩阵系统源码搭建&#xff0c;抖音矩阵同步分发。抖音seo源码部署是需要对接到这些正规接口再来做开发的&#xff0c;目前账号矩阵程序开发的功能&#xff0c;围绕一键管理多个账号&#xff0c;做到定时投放&#xff0c;关键词自动…

华为OD机试之羊、狼、农夫过河(Java源码)

羊、狼、农夫过河 题目描述 羊、狼、农夫都在岸边&#xff0c;当羊的数量小于狼的数量时&#xff0c;狼会攻击羊&#xff0c;农夫则会损失羊。农夫有一艘容量固定的船&#xff0c;能够承载固定数量的动物。 要求求出不损失羊情况下将全部羊和狼运到对岸需要的最小次数。只计算…

几个提高工作效率的 Python 自动化脚本,收藏!

在这个自动化时代&#xff0c;我们有很多重复无聊的工作要做。 想想这些你不再需要一次又一次地做的无聊的事情&#xff0c;让它自动化&#xff0c;让你的生活更轻松。 那么在本文中&#xff0c;我将向您介绍 10 个 Python 自动化脚本&#xff0c;以使你的工作更加自动化&#…

浅谈安科瑞霍尔传感器在转速测量中的选型与应用

安科瑞 徐浩竣 江苏安科瑞电器制造有限公司 zx acrelxhj 摘 要&#xff1a;在现代工业生产中存在许多需要转速测量的方面&#xff0c;针对转速测量方法落后、只能进行接触式测量等问题&#xff0c;提出把霍尔传感器应用于工业生产中 , 利用霍尔效应测量转速&#xff0c;具有动…

KD7742交直流耐压绝缘分析仪

一、产品简介 KD7742交直流耐压绝缘分析仪具有交/直流耐压、绝缘电阻等项目的测试分析功能&#xff0c;能显示电压、电流和电阻的波形图以及趋势图&#xff0c;以便更直观的监测分析绝缘性能和绝缘崩溃时的各项指标&#xff0c;适用于高要求的测试分析场合。 产品具有测试参数范…

vue如何给页面切换增加动画效果?

Vue.js 提供了 <transition> 组件以在插入、更新或移除 DOM 时应用过渡效果。它允许在元素或组件进入/离开时应用 CSS 过渡或动画&#xff0c;并允许在同一时间触发 JavaScript 动画和钩子函数。 你可以为路由更改添加过渡效果。在你的 Vue 路由组件中使用 <transiti…