前端Vue使用国密sm2、sm4与后端使用工具类

10 篇文章 1 订阅
4 篇文章 0 订阅

一、后端(SpringBoot)

1.后端导入国密支持

<dependency>
	<groupId>org.bouncycastle</groupId>
	<artifactId>bcprov-jdk15on</artifactId>
	<version>1.68</version>
</dependency>

2.国密工具类(sm1算法不公开,这里不涉及、sm2是非对称加密、sm3是信息摘要,类似MD5加密、sm4是对称加密)

package com.doctortech.tmc.support.util;

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 org.bouncycastle.crypto.digests.SM3Digest;
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.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.security.Security;

public class SMUtil {
    private static final Logger logger = LoggerFactory.getLogger(NotificationUtil.class);
    /**
    sm4的密钥需要16字节的字符串
    **/
    public static final String SM4_KEY = "1234567887654321";
    private static final String ALGORITHM_NAME = "SM4";
    private static final String ALGORITHM_ECB_PKCS5PADDING = "SM4/ECB/PKCS5Padding";
    /**
    sm2的公钥和私钥生成之后存起来使用即可,一般会把公钥提供给前端
    **/
    public static final String SM2_PRIVATE_KEY = "sm2私钥";
    public static final String SM2_PUBLIC_KEY = "sm2公钥";
    /**
     * sm2加密模式有两种:C1C2C3和C1C3C2(开始国密标准使用C1C2C3,新标准使用C1C3C2)
     */
    public static final SM2Engine.Mode SM2ENGINE_MODE = SM2Engine.Mode.C1C3C2;
    private static final String SM2BC_STR = "04";
    private static SM2 sm2 = new SM2();

    /**
     * SM4算法目前只支持128位(即密钥16字节)
     */
    private static final int DEFAULT_KEY_SIZE = 128;
    private static  final String DEFAULT_ENCODING="utf-8";
    static {
        if(null == Security.getProvider(BouncyCastleProvider.PROVIDER_NAME)){
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    /**
     * sm3加密,文摘算法,杂凑算法
     * @param content 加密内容
     * @return
     */
    public static String sm3(String content){
        String res = "";
        try{
            byte[] srcData = content.getBytes(DEFAULT_ENCODING);
            byte[] resultHash = hash(srcData);
            res = ByteUtils.toHexString(resultHash);
        }catch (Exception e){
            e.printStackTrace();
        }
        return res;
    }
    
    /**
     * sm3加密
     * @param srcData 加密内容的byte数组
    **/
    private static byte[] hash(byte[] srcData){
        SM3Digest digest = new SM3Digest();
        digest.update(srcData,0,srcData.length);
        byte[] hash = new byte[digest.getDigestSize()];
        digest.doFinal(hash,0);
        return  hash;
    }




    /**
     * 生成sm4密钥,生成一次后将byte数组存起来使用即可(推荐:也可以自定义16字节字符串,本工具类使用的是自定义16字节字符串做密钥)
     * <p>建议使用org.bouncycastle.util.encoders.Hex将二进制转成HEX字符串</p>
     *
     * @return 密钥16位
     * @throws Exception 生成密钥异常
     */
    private static byte[] generateKey() throws Exception {
        KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
        kg.init(DEFAULT_KEY_SIZE, new SecureRandom());
        return kg.generateKey().getEncoded();
    }

    /***
     * SM4对称加密,
     * @param content 加密内容
     * @param key 密钥 需要128bit,即16个字符组成
     * @return
     */
    public  static String sm4Encrypt(String content,String key){
        String res = "";
        try{
            byte[] resByte = encryptEcbPkcs5Padding(content.getBytes(DEFAULT_ENCODING),key.getBytes(DEFAULT_ENCODING));
            res = Hex.toHexString(resByte);
        }catch (Exception e){
            e.printStackTrace();
        }
        return res;
    }

    /***
     * SM4对称加密,
     * @param content 加密内容
     * @return
     */
    public  static String sm4Encrypt(String content){
        String res = "";
        try{
            byte[] resByte = encryptEcbPkcs5Padding(content.getBytes(DEFAULT_ENCODING),SM4_KEY.getBytes(DEFAULT_ENCODING));
            res = Hex.toHexString(resByte);
        }catch (Exception e){
            e.printStackTrace();
        }
        return res;
    }

    /***
     * SM4对称解密,
     * @param data 解密前数据
     * @param key 密钥 需要128bit,即16个字符组成
     * @return
     */
    public static String sm4Decrypt(String data,String key){
        String res = "";
        try{
            byte[] resByte = decryptEcbPkcs5Padding(Hex.decode(data),key.getBytes(DEFAULT_ENCODING));
             res = new String(resByte, DEFAULT_ENCODING);
        }catch (Exception e){
            e.printStackTrace();
        }
        return res;
    }

    /***
     * SM4对称解密,
     * @param data 解密前数据
     * @return
     */
    public static String sm4Decrypt(String data){
        String res = "";
        try{
            byte[] resByte = decryptEcbPkcs5Padding(Hex.decode(data),SM4_KEY.getBytes(DEFAULT_ENCODING));
            res = new String(resByte, DEFAULT_ENCODING);
        }catch (Exception e){
            e.printStackTrace();
        }
        return res;
    }

    /**
     * 加密,SM4-ECB-PKCS5Padding
     *
     * @param data 要加密的明文
     * @param key  密钥16字节,使用Sm4Util.generateKey()生成
     * @return 加密后的密文
     * @throws Exception 加密异常
     */
    private static byte[] encryptEcbPkcs5Padding(byte[] data, byte[] key) throws Exception {
        return sm4work(data, key, ALGORITHM_ECB_PKCS5PADDING, null, Cipher.ENCRYPT_MODE);
    }

    /**
     * 解密,SM4-ECB-PKCS5Padding
     *
     * @param data 要解密的密文
     * @param key  密钥16字节,使用Sm4Util.generateKey()生成
     * @return 解密后的明文
     * @throws Exception 解密异常
     */
    private static byte[] decryptEcbPkcs5Padding(byte[] data, byte[] key) throws Exception {
        return sm4work(data, key, ALGORITHM_ECB_PKCS5PADDING, null, Cipher.DECRYPT_MODE);
    }

    /**
     * SM4对称加解密
     *
     * @param input   明文(加密模式)或密文(解密模式)
     * @param key     密钥
     * @param sm4mode sm4加密模式
     * @param iv      初始向量(ECB模式下传NULL)
     * @param mode    Cipher.ENCRYPT_MODE - 加密;Cipher.DECRYPT_MODE - 解密
     * @return 密文(加密模式)或明文(解密模式)
     * @throws Exception 加解密异常
     */
    private static byte[] sm4work(byte[] input, byte[] key, String sm4mode, byte[] iv, int mode)
            throws Exception {
        IvParameterSpec ivParameterSpec = null;
        if (null != iv) {
            ivParameterSpec = new IvParameterSpec(iv);
        }
        SecretKeySpec sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
        Cipher cipher = Cipher.getInstance(sm4mode, BouncyCastleProvider.PROVIDER_NAME);
        if (null == ivParameterSpec) {
            cipher.init(mode, sm4Key);
        } else {
            cipher.init(mode, sm4Key, ivParameterSpec);
        }
        return cipher.doFinal(input);
    }

    /**
     * sm2解密
     * @param data 密文
     * @return
     */
    public static String sm2Decrypt(String data) {
        String decryptData = "";
        try {
            long start = System.currentTimeMillis();
            logger.info("初始化sm2实例");
            initSm2Instance();
            //使用BC库解密需要在密文前面拼上”04“字符串(前端加密是不会加上04的,后端加密则会加上04,所以需要做多一层判断)
            //注意:请使用使用StringBuffer不要使用StringBuilder,前者是线程安全,后者非线程安全
            if (!data.startsWith(SM2BC_STR)) {
                data = new StringBuffer(SM2BC_STR).append(data).toString();
            }
            decryptData = sm2.decryptStr(data, KeyType.PrivateKey);
            long end = System.currentTimeMillis();
            logger.info("sm2解密一次所花时间:{}ms",(end - start));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return decryptData;
    }

    /**
     * sm2加密
     * @param data 待加密文本
     * @return
     */
    public static String sm2Encrypt(String data) {
        String encryptData = "";
        try {
            long start = System.currentTimeMillis();
            logger.info("初始化sm2实例");
            initSm2Instance();
            encryptData = sm2.encryptBcd(data, KeyType.PublicKey);
            long end = System.currentTimeMillis();
            logger.info("sm2加密一次所花时间:{}ms",(end - start));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encryptData;
    }

    /**
     * 初始化sm2实例
     * @return
     */
    private static void initSm2Instance() {
        logger.info("初始化sm2实例");
        ECPrivateKeyParameters privateKeyParameters = BCUtil.toSm2Params(SM2_PRIVATE_KEY);
        //产生的公钥为130字节,实际公钥为128字节,第一个字节表示是否压缩,这里用不上,所以从第二个字节开始截取横坐标和纵坐标
        String xhex = SM2_PUBLIC_KEY.substring(2, 66);
        String yhex = SM2_PUBLIC_KEY.substring(66, 130);
        ECPublicKeyParameters publicKeyParameters = BCUtil.toSm2Params(xhex, yhex);
        //设置公钥和私钥
        sm2.setPrivateKeyParams(privateKeyParameters);
        sm2.setPublicKeyParams(publicKeyParameters);
        //设置加密模式
        sm2.setMode(SM2ENGINE_MODE);
    }

    /**
     * 生成sm2公钥和私钥,一般生成之后将公钥和私钥存起来使用即可
     */
    public static void createSm2Key() {
        //创建sm2 对象
        SM2 createSm2Key = SmUtil.sm2();
        //这里会自动生成对应的随机秘钥对 , 注意! 这里一定要强转,才能得到对应有效的秘钥信息
        byte[] privateKey = BCUtil.encodeECPrivateKey(createSm2Key.getPrivateKey());
        //这里公钥不压缩  公钥的第一个字节用于表示是否压缩  可以不要
        byte[] publicKey = ((BCECPublicKey) createSm2Key.getPublicKey()).getQ().getEncoded(false);
        //这里得到的 压缩后的公钥   ((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(true);
        byte[] publicKeyEc = BCUtil.encodeECPublicKey(createSm2Key.getPublicKey());
        //打印当前的公私秘钥
        System.out.println("私钥: " + HexUtil.encodeHexStr(privateKey));
        System.out.println("公钥: " + HexUtil.encodeHexStr(publicKey));
        System.out.println("压缩后的公钥: " + HexUtil.encodeHexStr(publicKeyEc));
    }
}

3.国密测试

package com.doctortech.tmc;

import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.SM2;
import com.doctortech.tmc.entity.User;
import com.doctortech.tmc.mapper.UserMapper;
import com.doctortech.tmc.service.ServiceConst;
import com.doctortech.tmc.support.util.SMUtil;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @author zxb
 * @version 1.0
 * @date 2021/05/21 9:56
 * @description 测试加密工具类
 */
@SpringBootTest
public class SMTest {
    
    /**
     * 生成sm2公钥和私钥测试(生成密钥对后将密钥存到SMUtil对应的常量中即可)
     */
    @Test
    public void createSm2KeyTest() {
        SMUtil.createSm2Key();
    }
    
    /**
     * sm2非对称加密和解密测试
     */
    @Test
    public void sm2EncryptAndDecryptTest() {
        long start = System.currentTimeMillis();
        String text = "我是一段测试aaaa";
        String sm2Encrypt = SMUtil.sm2Encrypt(text);
        System.out.println("加密结果:" + sm2Encrypt);
        String sm2Decrypt = SMUtil.sm2Decrypt(sm2Encrypt);
        System.out.println("解密结果:" + sm2Decrypt);
        System.out.println(text.equals(sm2Decrypt));
        long end = System.currentTimeMillis();
        System.out.println("加密解密一次所花时间:" + (end - start) + "ms");
    }

    /**
    sm3对称加密和解密测试
    **/
    @Test
    public void sm3EncryptAndDecryptTest() {
        String encryptPassword = SMUtil.sm3(ServiceConst.RESET_PASSWORD);
        System.out.println(encryptPassword);
        List<User> userList = userMapper.selectList(null);
        for (User user : userList) {
            user.setPassword(encryptPassword);
            userMapper.updateById(user);
        }
    }

    /**
     * sm2非对称加密和解密测试
     */
    @Test
    public void sm2EncryptAndDecryptTest() {
        long start = System.currentTimeMillis();
        String text = "我是一段测试aaaa";
        String sm2Encrypt = SMUtil.sm2Encrypt(text);
        System.out.println("加密结果:" + sm2Encrypt);
        String sm2Decrypt = SMUtil.sm2Decrypt(sm2Encrypt);
        System.out.println("解密结果:" + sm2Decrypt);
        System.out.println(text.equals(sm2Decrypt));
        long end = System.currentTimeMillis();
        System.out.println("加密解密一次所花时间:" + (end - start) + "ms");
    }
    
    /**
     * sm3加密测试(无法解密,一般用于密码加密,验证就是将用户输入的密码也进行一次sm3加密再比较是否相同)
     */
    @Test
    public void sm3EncryptAndDecryptTest() {
        String encryptPassword = SMUtil.sm3("123456");
        System.out.println(encryptPassword);
    }
    
    /**
     * 生成sm4密钥测试,这里得到的密钥是一个byte数组
     * @throws Exception
     */
    @Test
    public void createSm4KeyTest() throws Exception {
        byte[] bytes = SMUtil.generateKey();
        for (int i = 0; i < bytes.length; i++) {
            System.out.println(bytes[i]);
        }
    }

    /**
     * 将sm4的key转成16进制字符串输出,前端使用的是16进制的key进行加解密
     */
    @Test
    public void sm4KeyToByteTest() {
        System.out.println(HexUtil.encodeHexStr(SMUtil.SM4_KEY));
    }
    
    /**
     * sm4加密和解密测试
     */
    @Test
    public void sm4EncryptAndDecryptTest() {
        String content = "123456789你好";
        String encryptContent = SMUtil.sm4Encrypt(content);
        String decryptContent = SMUtil.sm4Decrypt(encryptContent);
        System.out.println("加密后内容:" + encryptContent);
        System.out.println("解密后内容:" + decryptContent);
        System.out.println(content.equals(decryptContent));
    }
}

二、前端(Vue)

1.安装依赖

npm install --save sm-crypto

2.sm2非对称加密(对登录、注册、修改密码进行加密)

const sm2 = require('sm-crypto').sm2
//sm2的加解密时有两种方式即0——C1C2C3、1——C1C3C2,前后端需要统一
const cipherMode = 1
const publicKey = '公钥'
let encryptPwd = sm2.doEncrypt('password', publicKey, cipherMode) // 加密
console.log("encryptPwd:" + encryptPwd)

3.sm4对称加解密(对身份证、邮箱等敏感信息进行加密和解密)

const sm4 = require('sm-crypto').sm4
const sm4Key = 'sm4密钥,要求16进制'
let sm4Encrypt = sm4.encrypt("1245877961584你好", sm4Key) //加密
console.log(sm4Encrypt)
let sm4Decrypt = sm4.decrypt(sm4Encrypt, sm4Key) //解密
console.log(sm4Decrypt)
  • 4
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值