结合Hutool实现SM2算法文件加解密 & 签名验签

欢迎浏览本文章

该文属于个人经验分享类,结合csdn多篇文章整合重制而成,可能存在不足和缺点,欢迎提出指导&意见。

0 . 背景

业务中涉及SM2算法,特抽象出为工具类。

0.1 SM2 算法背景

SM2密码算法是一种椭圆(非对称)密码算法,具有以下重要特性:

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

0.2 本文背景

本文并非100%原创,只是做了必要的封装,提供给同样需求的朋友。参考了以下文章,在此衷心感谢他们的研究和分享:

  1. 【SM2 SM3 SM4简介】https://blog.csdn.net/cqwei1987/article/details/107329600;
  2. 【国密算法—SM2介绍及基于BC的实现】https://blog.csdn.net/zcmain/article/details/114099664;
  3. 【hutool国密sm2算法使用, 正确的秘钥生成签名及验签,签名为64字节】https://blog.csdn.net/qq_33140565/article/details/113818235;
  4. 【国密SM2算法加解密文件】https://blog.csdn.net/A_Lonely_Smile/article/details/118366945

1 . 依赖引入

		<!--开始-国密SM2 算法依赖:bouncycastle & hutool --> 
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15to18</artifactId>
            <version>1.66</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.10</version>
        </dependency>
        <!--结束-国密SM2 算法依赖:bouncycastle & hutool -->

2 .创建秘钥对象

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;

/**
 *  SM2 国密算法生成秘钥对 - 对象
 */
@Data
@Accessors(chain = true)
@ApiModel(value = "SM2KeyPairs", description = "SM2用户秘钥对对象")
public class SM2KeyPairs {
   @ApiModelProperty(value = "公钥")
    private String publicKeyBase64;

    @ApiModelProperty(value = "私钥")
    private String privateKeyBase64;
}

3 .核心:工具类

import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.springframework.web.multipart.MultipartFile;

/**
 * SM2 国密算法 工具类
 */
@Slf4j
public class SM2FileUtil {

    /**
     * 生成私钥 & 公钥
     *
     *
     * @return SM2KeyPairs对象
     */
    public static SM2KeyPairs getKeyParis() {
        SM2KeyPairs sm2KeyPairs = new SM2KeyPairs();
        //随机生成秘钥
        SM2 sm2 = new SM2();
        //获取秘钥对象
        PrivateKey privateKeyObject = sm2.getPrivateKey();
        PublicKey publicKeyObject = sm2.getPublicKey();
        //生成私钥
        String privateKeyBase64 = Base64.encode(privateKeyObject.getEncoded());
        //生成公钥
        String publicKeyBase64 = Base64.encode(publicKeyObject.getEncoded());
        sm2KeyPairs.setPrivateKeyBase64(privateKeyBase64);
        sm2KeyPairs.setPublicKeyBase64(publicKeyBase64);
        return sm2KeyPairs;
    }

    /**
     * SM2 文件加密
     *
     * @param publicKey     公钥
     * @param dataBytes     提交的原始文件以流的形式
     * @param outputPath    输出的加密文件路径
     * @param fileName      输出的加密文件名称
     */
    public static Boolean lockFile(String publicKey, byte[] dataBytes, String outputPath, String fileName) throws Exception {
        Boolean flag = false;
        if (StringUtils.isEmpty(publicKey) || null == dataBytes ||
                StringUtils.isEmpty(outputPath) || StringUtils.isEmpty(fileName)) {
            throw new BusinessException("缺少必要参数!");
        } else {
            long startTime = System.currentTimeMillis();
            // 初始化SM2对象
            try {
                SM2 SM_2 = new SM2(null, publicKey);
                byte[] data;
                data = SM_2.encrypt(dataBytes, KeyType.PublicKey);
                FileUtils.byteToFile(data, outputPath, fileName);
                flag = true;
            } catch (Exception e) {
                flag = false;
                log.error("Exception | " + e);
            }
            long endTime = System.currentTimeMillis();
            log.error("本次加密操作,所耗时间为:" + (endTime - startTime));
            return flag;
        }
    }

    /**
     * SM2 文件解密
     *
     * @param privateKey   私钥
     * @param lockFilePath 加密文件路径
     * @param outputPath   输出的解密文件路径
     * @param fileName     输出的解密文件名称
     */
    public static Boolean unlockFile(String privateKey, String lockFilePath, String outputPath, String fileName) {
        Boolean flag = false;
        if (StringUtils.isEmpty(privateKey) || StringUtils.isEmpty(lockFilePath) || StringUtils.isEmpty(fileName)) {
            throw new BusinessException("缺少必要参数!");
        } else {
            long startTime = System.currentTimeMillis();
            try {
                // 初始化SM2对象
                SM2 SM_2 = new SM2(privateKey, null);
                byte[] bytes = FileUtils.fileToByte(lockFilePath);
                byte[] data;
                data = SM_2.decrypt(bytes, KeyType.PrivateKey);
                FileUtils.byteToFile(data, outputPath, fileName);
                flag = true;
            } catch (Exception e) {
                log.error("Exception | " + e);
            }
            long endTime = System.currentTimeMillis();
            log.error("本次解密操作,所耗时间为:" + (endTime - startTime));
            return flag;
        }
    }

    /**
     * 通过私钥进行文件签名
     *
     * @param privateKey    私钥
     * @param dataBytes 需要签名的文件以流的形式
     * @throws Exception
     */
    public static String generateFileSignByPrivateKey(String privateKey, byte[] dataBytes) throws Exception {
        String signature = "";
        if (StringUtils.isEmpty(privateKey) || null == dataBytes) {
            throw new BusinessException("缺少必要参数!");
        } else {
            long startTime = System.currentTimeMillis();
            String signs = "";
            try {
                //----------------------20210830优化:私钥HEX处理---------------------------------
                byte[] decode = Base64.decode(privateKey);
                SM2 sm3 = new SM2(decode,null);
                byte[] bytes = BCUtil.encodeECPrivateKey(sm3.getPrivateKey());
                String privateKeyHex = HexUtil.encodeHexStr(bytes);
                //------@End----------------20210830优化:私钥HEX处理------------------------------
                //需要加密的明文,得到明文对应的字节数组
                ECPrivateKeyParameters privateKeyParameters = BCUtil.toSm2Params(privateKeyHex);
                //创建sm2 对象
                SM2 sm2 = new SM2(privateKeyParameters, null);
                //这里需要手动设置,sm2 对象的默认值与我们期望的不一致 , 使用明文编码
                sm2.usePlainEncoding();
                sm2.setMode(SM2Engine.Mode.C1C2C3);
                byte[] sign = sm2.sign(dataBytes, null);
                //change encoding : hex to base64
                signs = Base64.encode(sign);
                signature = signs;
            } catch (Exception e) {
                log.error("Exception | " + e);
            }
            long endTime = System.currentTimeMillis();
            log.error("本次签名操作,所得签名为:" + signs + ",所耗时间为:" + (endTime - startTime));
            return signature;
        }
    }

    /**
     * 通过公钥进行文件验签
     *
     * @param publicKey 公钥
     * @param sign 签名(原先为hex处理后的16位,现在改为base处理后的64位)
     * @param dataBytes 需要验签的文件数据以流的形式
     * @return
     * @throws Exception
     */
    public static Boolean verifyFileSignByPublicKey(String publicKey, String sign, byte[] dataBytes) throws Exception {
        if (StringUtils.isEmpty(publicKey) || StringUtils.isEmpty(sign) || null == dataBytes) {
            throw new BusinessException("缺少必要参数!");
        } else {
            long startTime = System.currentTimeMillis();
            Boolean verify = false;
            try {
                //-----------------------------20210830修改公钥HEX处理----------------------------
                byte[] decode = Base64.decode(publicKey);
                SM2 sm3 = new SM2(null,decode);
                byte[] bytes = ((BCECPublicKey) sm3.getPublicKey()).getQ().getEncoded(false);
                String publicKeyHex = HexUtil.encodeHexStr(bytes);
                //--------@End---------------------公钥HEX处理------------------------------------
                //需要加密的明文,得到明文对应的字节数组
                //这里需要根据公钥的长度进行加工
                if (publicKeyHex.length() == 130) {
                    //这里需要去掉开始第一个字节 第一个字节表示标记
                    publicKeyHex = publicKeyHex.substring(2);
                }
                String xhex = publicKeyHex.substring(0, 64);
                String yhex = publicKeyHex.substring(64, 128);
                ECPublicKeyParameters ecPublicKeyParameters = BCUtil.toSm2Params(xhex, yhex);
                //创建sm2 对象
                SM2 sm2 = new SM2(null, ecPublicKeyParameters);
                //这里需要手动设置,sm2 对象的默认值与我们期望的不一致 , 使用明文编码
                sm2.usePlainEncoding();
                sm2.setMode(SM2Engine.Mode.C1C2C3);
                verify = sm2.verify(dataBytes, Base64.decode(sign));
            } catch (Exception e) {
                log.error("Exception | " + e);
            }
            long endTime = System.currentTimeMillis();
            log.error("本次验签操作,所得结果为:" + verify + ",所耗时间为:" + (endTime - startTime));
            return verify;
        }
    }

4 .其他辅助类

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;

/**
 * 文件处理工具类
 *
 * @author wujie
 */
@Slf4j
public class FileUtils {
  
    /**
     * 获得指定文件的byte数组
     *
     * @param filePath 文件路径
     * @return 字节数组
     */
    public static byte[] fileToByte(String filePath) {
        byte[] buffer = null;
        try {
            File file = new File(filePath);
            FileInputStream fis = new FileInputStream(file);
            ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
            byte[] b = new byte[1024];
            int n;
            while ((n = fis.read(b)) != -1) {
                bos.write(b, 0, n);
            }
            fis.close();
            bos.close();
            buffer = bos.toByteArray();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return buffer;
    }

    /**
     *
     * @param multipartFile 真实文件
     * @return byte[] 数组
     */
    public static byte[] multipartFileToByte(MultipartFile multipartFile) throws Exception {
        byte[] buffer = null;
        File file = multipartFileToFile(multipartFile);
        try {
            FileInputStream fis = new FileInputStream(file);
            ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
            byte[] b = new byte[1024];
            int n;
            while ((n = fis.read(b)) != -1) {
                bos.write(b, 0, n);
            }
            fis.close();
            bos.close();
            buffer = bos.toByteArray();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //清除缓存文件
            delteTempFile(file);
        }
        return buffer;
    }



    /**
     * 根据byte数组,生成文件
     * @param bfile 字节数组
     * @param filePath 文件路径
     * @param fileName 文件名
     */
    public static void byteToFile(byte[] bfile, String filePath, String fileName) {
        BufferedOutputStream bos = null;
        FileOutputStream fos = null;
        File file = null;
        try {
            File dir = new File(filePath);
            if (!dir.exists() && dir.isDirectory()) {
                dir.mkdirs();
            }
            file = new File(filePath + "\\" + fileName);
            fos = new FileOutputStream(file);
            bos = new BufferedOutputStream(fos);
            bos.write(bfile);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(bos);
            IOUtils.closeQuietly(fos);
        }
    }

    public static File multipartFileToFile(MultipartFile file) throws Exception {

        File toFile = null;
        if (file.equals("") || file.getSize() <= 0) {
            file = null;
        } else {
            InputStream ins = null;
            ins = file.getInputStream();
            toFile = new File(file.getOriginalFilename());
            inputStreamToFile(ins, toFile);
            ins.close();
        }
        return toFile;
    }

    private static void inputStreamToFile(InputStream ins, File file) {
        try {
            OutputStream os = new FileOutputStream(file);
            int bytesRead = 0;
            byte[] buffer = new byte[8192];
            while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            os.close();
            ins.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void delteTempFile(File file) {
        if (file != null) {
            File del = new File(file.toURI());
            del.delete();
        }
    }
}

  • 4
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
PowerBuilder是一个以Rapid Application Development(RAD)为核心的集成开发环境(IDE),它支持开发Windows应用程序和Web应用程序。在PowerBuilder中使用国密SM4加解密SM2签名验签,可以通过调用相应的DLL文件实现。 以下是在PowerBuilder中使用国密SM4加解密SM2签名验签的一般步骤: 1. 首先,你需要下载和安装支持国密算法的DLL文件。这些DLL文件通常由加密算法提供商提供,例如FeiTian、鹏博士等。确保你已经获得了正确的DLL文件,并且已经将它们放置在适当的文件夹中。 2. 在PowerBuilder中创建一个新的应用程序或打开现有的应用程序。你需要使用PowerBuilder的“DLL函数”功能来调用国密SM4和SM2算法的DLL文件。 3. 在PowerBuilder的“DLL函数”设置对话框中,输入DLL文件的名称、函数名称和参数。确保你已经按照DLL文件提供商提供的说明正确地设置了参数。 4. 在PowerBuilder的脚本中,调用DLL函数来执行国密SM4加解密SM2签名验签操作。你需要使用PowerBuilder的变量来存储输入和输出数据。 5. 测试你的PowerBuilder应用程序,确保国密SM4加解密SM2签名验签功能正常工作。 需要注意的是,国密算法涉及到国家安全等重要问题,因此,在使用国密算法进行加密和解密时,需要遵守相关的法律法规和政策。同时,在使用国密算法时,需要保护好密钥和数据,防止泄露和被攻击。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值