Java SM2 国密算法(最权威)!

发布时间:2023年12月18日

国密SM2算法简介

国密SM2算法是一种椭圆曲线公钥密码算法,其安全性基于椭圆曲线离散对数难题。该算法由国家密码管理局设计并公开,用于国家关键信息系统的数据加密、解密和数字签名等操作,是我国自主创新的一种密码算法。
在这里插入图片描述

一、SM2算法概述

SM2算法是一种基于椭圆曲线密码的公钥密码算法,其安全性主要基于椭圆曲线离散对数难题。该算法由国家密码管理局设计并公开,是我国自主创新的一种密码算法,可应用于数据加密、解密、数字签名等操作。SM2算法包括密钥生成算法、加密算法、解密算法和数字签名算法等部分。

二、SM2算法的应用场景

SM2算法作为一种公钥密码算法,可广泛应用于各种场景,如:

  1. 数据加密:SM2算法可用于加密和解密数据,保障数据传输的安全性。
  2. 数字签名:SM2算法可用于生成数字签名,验证文档或数据的完整性和真实性。
  3. 身份认证:SM2算法可用于身份认证,验证用户的身份信息。
  4. 网络安全:SM2算法可用于网络安全,保护网络传输的数据,防止被黑客攻击。

三、SM2算法的优势

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

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

## 四、SM2算法的未来发展

总之,SM2算法是一种安全、高效的公钥密码算法,具有广泛的应用前景和未来发展潜力。在我国自主创新的道路上,SM2算法将继续发挥重要作用,为保障国家关键信息系统的信息安全做出更大的贡献。

国密算法Java版本

pom.xml 文件加入引用

       <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15to18</artifactId>
            <version>1.69</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.10</version>
        </dependency>
        <!--以下包 可以适当引用-->
         <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.9.3</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
        </dependency>

SM2扩展类

这个类主要的的作用是对SM2 的扩展。

  • 扩展SM2 加密排列方式C1C2C3 还是C1C3C2
  • 扩展SM2 加签和验签算法SM3Digest
package com.yunce.ycsm;/*
 * @Auther:徐志强
 * @Date:2023/9/11
 * @Description:
 * @VERSON:1.0
 */

import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.math.ec.ECConstants;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.BigIntegers;
import java.math.BigInteger;
import java.security.SecureRandom;

/**
 * 对org.bouncycastle:bcprov-jdk15on:1.57扩展
 * <br/>BC库加密结果是按C1C2C3,国密标准是C1C3C2(加密芯片也是这个排列),
 * <br/>本扩展主要实现加密结果排列方式可选
 *
 */
public class SM2EngineExtend {
    private final Digest digest;

    /**是否为加密模式*/
    private boolean forEncryption;
    private ECKeyParameters ecKey;
    private ECDomainParameters ecParams;
    private int curveLength;
    private SecureRandom random;
    /**密文排序方式*/
    private int cipherMode;

    /**BC库默认排序方式-C1C2C3*/
    public static int CIPHERMODE_BC = 0;
    /**国密标准排序方式-C1C3C2*/
    public static int CIPHERMODE_NORM = 1;

    public SM2EngineExtend() {
        this(new SM3Digest());
    }

    public SM2EngineExtend(Digest digest) {
        this.digest = digest;
    }

    /**
     * 设置密文排序方式
     * @param cipherMode
     */
    public void setCipherMode(int cipherMode){
        this.cipherMode = cipherMode;
    }

    /**
     * 默认初始化方法,使用国密排序标准
     * @param forEncryption - 是否以加密模式初始化
     * @param param - 曲线参数
     */
    public void init(boolean forEncryption, CipherParameters param) {
        init(forEncryption, CIPHERMODE_NORM, param);
    }

    /**
     * 默认初始化方法,使用国密排序标准
     * @param forEncryption 是否以加密模式初始化
     * @param cipherMode 加密数据排列模式:1-标准排序;0-BC默认排序
     * @param param 曲线参数
     */
    public void init(boolean forEncryption, int cipherMode, CipherParameters param) {
        this.forEncryption = forEncryption;
        this.cipherMode = cipherMode;
        if (forEncryption) {
            ParametersWithRandom rParam = (ParametersWithRandom) param;

            ecKey = (ECKeyParameters) rParam.getParameters();
            ecParams = ecKey.getParameters();

            ECPoint s = ((ECPublicKeyParameters) ecKey).getQ().multiply(ecParams.getH());
            if (s.isInfinity()) {
                throw new IllegalArgumentException("invalid key: [h]Q at infinity");
            }

            random = rParam.getRandom();
        } else {
            ecKey = (ECKeyParameters) param;
            ecParams = ecKey.getParameters();
        }

        curveLength = (ecParams.getCurve().getFieldSize() + 7) / 8;
    }

    /**
     * 加密或解密输入数据
     * @param in
     * @param inOff
     * @param inLen
     * @return
     * @throws InvalidCipherTextException
     */
    public byte[] processBlock( byte[] in, int inOff, int inLen) throws InvalidCipherTextException {
        if (forEncryption) {
            // 加密
            return encrypt(in, inOff, inLen);
        } else {
            return decrypt(in, inOff, inLen);
        }
    }

    /**
     * 加密实现,根据cipherMode输出指定排列的结果,默认按标准方式排列
     * @param in
     * @param inOff
     * @param inLen
     * @return
     * @throws InvalidCipherTextException
     */
    private byte[] encrypt(byte[] in, int inOff, int inLen)
            throws InvalidCipherTextException {
        byte[] c2 = new byte[inLen];

        System.arraycopy(in, inOff, c2, 0, c2.length);

        byte[] c1;
        ECPoint kPB;
        do {
            BigInteger k = nextK();

            ECPoint c1P = ecParams.getG().multiply(k).normalize();

            c1 = c1P.getEncoded(false);

            kPB = ((ECPublicKeyParameters) ecKey).getQ().multiply(k).normalize();

            kdf(digest, kPB, c2);
        }
        while (notEncrypted(c2, in, inOff));

        byte[] c3 = new byte[digest.getDigestSize()];

        addFieldElement(digest, kPB.getAffineXCoord());
        digest.update(in, inOff, inLen);
        addFieldElement(digest, kPB.getAffineYCoord());

        digest.doFinal(c3, 0);
        if (cipherMode == CIPHERMODE_NORM){
            return Arrays.concatenate(c1, c3, c2);
        }
        return Arrays.concatenate(c1, c2, c3);
    }

    /**
     * 解密实现,默认按标准排列方式解密,解密时解出c2部分原文并校验c3部分
     * @param in
     * @param inOff
     * @param inLen
     * @return
     * @throws InvalidCipherTextException
     */
    private byte[] decrypt(byte[] in, int inOff, int inLen)
            throws InvalidCipherTextException {
        byte[] c1 = new byte[curveLength * 2 + 1];

        System.arraycopy(in, inOff, c1, 0, c1.length);

        ECPoint c1P = ecParams.getCurve().decodePoint(c1);

        ECPoint s = c1P.multiply(ecParams.getH());
        if (s.isInfinity()) {
            throw new InvalidCipherTextException("[h]C1 at infinity");
        }

        c1P = c1P.multiply(((ECPrivateKeyParameters) ecKey).getD()).normalize();

        byte[] c2 = new byte[inLen - c1.length - digest.getDigestSize()];
        if (cipherMode == CIPHERMODE_BC) {
            System.arraycopy(in, inOff + c1.length, c2, 0, c2.length);
        }else{
            // C1 C3 C2
            System.arraycopy(in, inOff + c1.length + digest.getDigestSize(), c2, 0, c2.length);
        }

        kdf(digest, c1P, c2);

        byte[] c3 = new byte[digest.getDigestSize()];

        addFieldElement(digest, c1P.getAffineXCoord());
        digest.update(c2, 0, c2.length);
        addFieldElement(digest, c1P.getAffineYCoord());

        digest.doFinal(c3, 0);

        int check = 0;
        // 检查密文输入值C3部分和由摘要生成的C3是否一致
        if (cipherMode == CIPHERMODE_BC) {
            for (int i = 0; i != c3.length; i++) {
                check |= c3[i] ^ in[c1.length + c2.length + i];
            }
        }else{
            for (int i = 0; i != c3.length; i++) {
                check |= c3[i] ^ in[c1.length + i];
            }
        }

        clearBlock(c1);
        clearBlock(c3);

        if (check != 0) {
            clearBlock(c2);
            throw new InvalidCipherTextException("invalid cipher text");
        }

        return c2;
    }

    private boolean notEncrypted(byte[] encData, byte[] in, int inOff) {
        for (int i = 0; i != encData.length; i++) {
            if (encData[i] != in[inOff]) {
                return false;
            }
        }

        return true;
    }

    private void kdf(Digest digest, ECPoint c1, byte[] encData) {
        int ct = 1;
        int v = digest.getDigestSize();

        byte[] buf = new byte[digest.getDigestSize()];
        int off = 0;

        for (int i = 1; i <= ((encData.length + v - 1) / v); i++) {
            addFieldElement(digest, c1.getAffineXCoord());
            addFieldElement(digest, c1.getAffineYCoord());
            digest.update((byte) (ct >> 24));
            digest.update((byte) (ct >> 16));
            digest.update((byte) (ct >> 8));
            digest.update((byte) ct);

            digest.doFinal(buf, 0);

            if (off + buf.length < encData.length) {
                xor(encData, buf, off, buf.length);
            } else {
                xor(encData, buf, off, encData.length - off);
            }

            off += buf.length;
            ct++;
        }
    }

    private void xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining) {
        for (int i = 0; i != dRemaining; i++) {
            data[dOff + i] ^= kdfOut[i];
        }
    }

    private BigInteger nextK() {
        int qBitLength = ecParams.getN().bitLength();

        BigInteger k;
        do {
            k = new BigInteger(qBitLength, random);
        }
        while (k.equals(ECConstants.ZERO) || k.compareTo(ecParams.getN()) >= 0);

        return k;
    }

    private void addFieldElement(Digest digest, ECFieldElement v) {
        byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger());

        digest.update(p, 0, p.length);
    }

    /**
     * clear possible sensitive data
     */
    private void clearBlock(
            byte[] block) {
        for (int i = 0; i != block.length; i++) {
            block[i] = 0;
        }
    }

}

SM2 工具类

工具类实现以下功能

  • 加密(排列算法设定)
  • 解密
  • 加签
  • 验签
package yc.util.sm;
/*
 * @Auther:徐志强
 * @Date:2023/9/11
 * @Description:
 * @VERSON:1.0
 */

import cn.hutool.crypto.asymmetric.SM2;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
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.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.SecureRandom;

public class SM2Utils {
    /**sm2曲线参数名称*/
    public static final String CRYPTO_NAME_SM2 = "sm2p256v1";

    /**
     * SM2加密算法
     * @param publicKey 公钥
     * @param data 待加密的数据
     * @return 密文,BC库产生的密文带由04标识符,与非BC库对接时需要去掉开头的04
     */
    public static String encrypt(String publicKey, String data) throws InvalidCipherTextException {
        // 按国密排序标准加密
        return encrypt(publicKey, data, SM2EngineExtend.CIPHERMODE_BC);
    }

    /**
     * SM2加密算法 (设置密文排列方式)
     * @param publicKey 公钥
     * @param data 待加密的数据
     * @param cipherMode 密文排列方式0-C1C2C3;1-C1C3C2;
     * @return 密文,BC库产生的密文带由04标识符,与非BC库对接时需要去掉开头的04
     */
    public static String encrypt(String publicKey, String data, int cipherMode) throws InvalidCipherTextException {
        // 获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName(CRYPTO_NAME_SM2);
        // 构造ECC算法参数,曲线方程、椭圆曲线G点、大整数N
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
        //提取公钥点
        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));
        // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);

        SM2EngineExtend sm2Engine = new SM2EngineExtend();
        // 设置sm2为加密模式
        sm2Engine.init(true, cipherMode, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));

        byte[] arrayOfBytes = null;
        try {
            byte[] in = data.getBytes();
            arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
        } catch (Exception e) {
             throw  e;
        }
        return Hex.toHexString(arrayOfBytes);
    }
    /**
     * SM2解密算法
     * @param privateKey    私钥
     * @param cipherData    密文数据
     * @description  密文数据以04开头,传入的密文前面没有04则补上
     * @return
     */
    public static String decrypt(String privateKey, String cipherData) throws InvalidCipherTextException {
        // // 按国密排序标准解密
        return decrypt(privateKey, cipherData, SM2EngineExtend.CIPHERMODE_BC);
    }

    /**
     * SM2解密算法
     * @param privateKey    私钥
     * @param cipherData    密文数据
     * @param cipherMode 密文排列方式0-C1C2C3;1-C1C3C2;
     * @return
     */
    public static String decrypt(String privateKey, String cipherData, int cipherMode) throws InvalidCipherTextException {
        // 使用BC库加解密时密文以04开头,传入的密文前面没有04则补上
        if (!cipherData.startsWith("04")){
            cipherData = "04" + cipherData;
        }
        byte[] cipherDataByte = Hex.decode(cipherData);

        //获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName(CRYPTO_NAME_SM2);
        //构造domain参数
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());

        BigInteger privateKeyD = new BigInteger(privateKey, 16);
        ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);

        SM2EngineExtend sm2Engine = new SM2EngineExtend();
        // 设置sm2为解密模式
        sm2Engine.init(false, cipherMode, privateKeyParameters);

        String result = "";
        try {
            byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
            return new String(arrayOfBytes);
        } catch (Exception e) {
            throw  e;
        }


    }

    /**
     * SM2 加签
     * @param privateKey 承建商私钥
     * @param id 授权码
     * @param source 16进制加密原文
     * @return 16进制签名
     */

    public static String sign(String privateKey,String id,  String source) throws UnsupportedEncodingException {
        SM2 sm2=  new SM2(privateKey,null);
         String id16=Hex.toHexString(id.getBytes("utf-8"));
        String source16=Hex.toHexString(source.getBytes("utf-8"));

        String sign= sm2.signHex(source16 ,id16);
        return sign;
    }

    /**
     * 校验签名
     * @param publicKey 民科提供公钥
     * @param data 16进制加密原文
     * @param signHex 16进制签名
     * @param id 授权码
     * @return
     */
    public static  boolean verifysign(String publicKey,String data, String signHex, String id) throws UnsupportedEncodingException {
        SM2 sm = new SM2(null,publicKey);
        String id16=Hex.toHexString(id.getBytes("utf-8"));
        String source16=Hex.toHexString(data.getBytes("utf-8"));
        boolean isSign= sm.verifyHex(data,signHex,id);
        return isSign;
    }
}

加密示例(单元测试)

1.对“Hello Word”进行一系列的SM2操作。

加密-单元测试

package yc.util.sm;/*
 * @Auther:徐志强
 * @Date:2023/12/18
 * @Description:
 * @VERSON:1.0
 */
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;


public class SM2UtilsTest {
    @Test
    public void testEncrypt() {
        try {
            String publicKey = "04979c0b359aba90a846e7724ab66621c79af8095a459e5ca981845952d865403f8644c5e79eb7d8ea0bcb9c2fb53f82e8e1108a85d96a3e1d4e687bb91a622ea4";
            String data     = "hello word";
            /*
            密文排列方式
            0-C1C2C3;
            1-C1C3C2;
             */
            String result = SM2Utils.encrypt(publicKey, data, 1);
            System.out.println("加密后:");
            System.out.println(result);
        } catch (InvalidCipherTextException e) {
            // Handle exception if necessary
        }
    }
}

加密后:
044e6f524065ebd5a758eaa9a8278a4bf51a49bc043ddfcf7b7ed5fe4475fd36b66077618af044acff157b065ba02045baaa75ff23efa7d366228ed5006f3b8b52f72db1811064e8485337708044b2d30d13691513ba0a939c19d1e8dc3faa4089862cc33f01d4b3e6b818

解密-单元测试

 String privateKey = "00e5f6e0d04985ba1ced8c6fef49c5dd3dd84f6542d64cafb6ffde94a433392e3c";
            String cipherData = "04972df81d2685884f0dd3a20cf9dc743203bf66e00c98b9761b7a569b27c1231ce271329c6121704e8e59df9a080f982390b8901829f3e6fc1ac3a2d0b7bfac7c0634d8b95dfdbf316922762a874b3708dc2ce7e7dd5a49a667d15f878ef85437f68f10d8bc05d5b5aede";
            int cipherMode = 1;


            String result = SM2Utils.decrypt(privateKey, cipherData, cipherMode);
            System.out.println("解密后:");
            System.out.println(result);

加签-单元测试

   @Test
    public void testSign() throws UnsupportedEncodingException, UnsupportedEncodingException {
        String privateKey = "00e5f6e0d04985ba1ced8c6fef49c5dd3dd84f6542d64cafb6ffde94a433392e3c";
        String id = "admin";
        String source = "Hello Word";


        String actualSign = SM2Utils.sign(privateKey, id, source);
        System.out.println("加签:");
        System.out.println(actualSign );

    }

验签-单元测试

  @Test
    public void testVerifysign() throws UnsupportedEncodingException {
        String publicKey = "04979c0b359aba90a846e7724ab66621c79af8095a459e5ca981845952d865403f8644c5e79eb7d8ea0bcb9c2fb53f82e8e1108a85d96a3e1d4e687bb91a622ea4";
        String data = "Hello Word";
        String signHex = "3046022100d8edc9c5f73d7845cec3e29b7a3196b4b21c9a1f2b9edf93d33fe4cb4f8c45bd022100b6c382a4bb72e3290fea140787a09df4e6ad5174debe03b8026bcc7e43369c79";
        String id = "admin";


        boolean result = SM2Utils.verifysign(publicKey, data, signHex, id);
        System.out.println(result);
        Assert.isTrue(result);
    }

在这里插入图片描述

文章来源:https://blog.csdn.net/bj_xuzhiqiang/article/details/135056192
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。