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

一、后端(SpringBoot)

1.后端导入国密支持

<dependency>
	<groupId>org.bouncycastlegroupId>
	<artifactId>bcprov-jdk15onartifactId>
	<version>1.68version>
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字节字符串做密钥)
     * 

建议使用org.bouncycastle.util.encoders.Hex将二进制转成HEX字符串

* * @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)

你可能感兴趣的:(Vue,springboot,小记,java,spring,boot,vue.js)