springboot 和 js (vue) 实现SM3加密 防篡改

springboot 和 js (vue) 实现SM3加密 防篡改

一、以下是对引入SM3进行说明

1.首先导入jar

<dependency>
    <groupId>org.bouncycastlegroupId>
    <artifactId>bcprov-jdk15onartifactId>
    <version>1.68version>
dependency>

2.写一个java的SM3工具类

import java.io.UnsupportedEncodingException;
import java.security.Security;
import java.util.Arrays;

import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;

public class SM3 {

	private static final String ENCODING = "UTF-8";
	
	static {
		Security.addProvider(new BouncyCastleProvider());
	}
	/**
	 * SM3加密
	 * @param paramStr	待加密字符串
	 * @return	返回加密后,固定长度=32的16进制字符串
	 */
	public static String encrypt(String paramStr) {
		//将返回的hash值转换为16进制字符串
		String resultHexString = "";
		try {
			//将字符串转换成byte数组
			byte[]  srcData = paramStr.getBytes(ENCODING);
			byte[] resultHash = hash(srcData);
			//将返回的hash值转换成16进制字符串
			resultHexString = ByteUtils.toHexString(resultHash);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return resultHexString;
	}
	/**
	 * 返回长度为32的byte数组
	 * 生成对应的hash值
	 * @param srcData
	 * @return
	 */
	public 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;
	}
	/**
	 * 通过指定密钥进行加密
	 * @param key 密钥
	 * @param srcData 被加密的byte数组
	 * @return
	 */
	public static byte[] hmac(byte[] key,byte[] srcData){
		KeyParameter keyParameter = new KeyParameter(key);
		SM3Digest digest = new SM3Digest();
		HMac mac = new HMac(digest);
		mac.init(keyParameter);
		mac.update(srcData,0,srcData.length);
		byte[] result = new byte[mac.getMacSize()];
		mac.doFinal(result, 0);
		return result;
	}
	/**
	 * 判断数据源与加密数据是否一致,通过验证原数组和生成是hash数组是否为同一数组,验证二者是否为同一数据
	 * @param srcStr
	 * @param sm3HexString
	 * @return
	 */
	public static boolean vertify(String srcStr,String sm3HexString){
		boolean flag = false;
		try {
			byte[] srcData = srcStr.getBytes(ENCODING);
			byte[] sm3Hash = ByteUtils.fromHexString(sm3HexString);
			byte[] newHash = hash(srcData);
			if(Arrays.equals(newHash, sm3Hash));
				flag = true;
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return flag;
	}
	
	public static void main(String[] args){
		//测试
		String str = "412345195604153562";
		String hex = SM3.encrypt(str);
		System.out.println(hex);
		
		String str1 = "1234";
		String hex1 = SM3.encrypt(str1);
		System.out.println(hex1);
		
		String str2 = "qwer12345";
		String hex2 = SM3.encrypt(str2);
		System.out.println(hex2);
		
		String strpre = "412727";
		String hexpre = SM3.encrypt(strpre);
		System.out.println(hexpre);
		//验证加密后的16进制字符串与加密前的字符串是否相同
		boolean flag = SM3.vertify(str, hex);
		System.out.println(flag);
	}
}

3.前端需要四个js

cipher-core.js
core.js
jsbn.js
jsbn2.js
sm3.js

4.1 编写JavaScript 的页面


<html>
<head>
<meta http-equiv="X-UA-Compatible" content="chrome=1" />
<meta name="description" content="SM3摘要加密测试" />
<title>SM3摘要加密测试title>

<script src="js/core.js">script>
<script src="js/cipher-core.js">script>
<script language="JavaScript" type="text/javascript" src="js/jsbn.js">script>
<script language="JavaScript" type="text/javascript" src="js/jsbn2.js">script>
<script language="JavaScript" type="text/javascript" src="js/sm3.js">script>
<script language="JavaScript" type="text/javascript">
	function doSM3() {
		/*var f1 = document.form1;
		var msg = f1.sm3input.value;
		var msgData = CryptoJS.enc.Utf8.parse(msg);

		var md;
		var sm3keycur = new SM3Digest();
		msgData = sm3keycur.GetWords(msgData.toString());
		console.log(msgData);
		sm3keycur.BlockUpdate(msgData, 0, msgData.length);
		console.log(msgData);
		var c3 = new Array(32);
		sm3keycur.DoFinal(c3, 0);
		f1.sm3key.value = sm3keycur.GetHex(c3).toString();
		
		*/
		var f1 = document.form1;
		var msg = f1.sm3input.value;
		var msgData = CryptoJS.enc.Utf8.parse(msg);
		var sm3keycur = new SM3Digest();
		msgData = sm3keycur.GetWords(msgData.toString());
		sm3keycur.BlockUpdate(msgData, 0, msgData.length);
		var c3 = new Array(32);
		sm3keycur.DoFinal(c3, 0);
		//解决sm3相同参数前后台加密不一致的问题
		for(var i = 0,len = c3.length; i < len; i++){
			if(256 == c3[i]){
				c3[i]=0;
			}
		}
		var hashHex = sm3keycur.GetHex(c3).toString();
		f1.sm3key.value = hashHex;
		return hashHex;
	}
script>
head>
<body>
	<script type="text/javascript">
		if (/msie/.test(navigator.userAgent.toLowerCase())) {
			document
					.write("

若IE浏览器下提示停止运行此脚本,请选择否(N)继续运行。

"
); }
script> <form name="form1"> <p> 加密字符串: <input type="text" name="sm3input" value="" size="100" /><br /> <input type="button" value="加密" onClick="doSM3();" /><br /> 加密结果: <input type="text" name="sm3key" value="" size="100" /><br /> p> form> body> html>

4.2 如果使用的是vue 需要自己改一下

import './core'
import './cipher-core'
import './jsbn'
import './jsbn2'
import './sm3'
import { SM3Digest } from './sm3'
import { CryptoJS } from './core'
export function doSM3(plaintext) {
  let d = CryptoJS.enc.Utf8.parse(plaintext);
  let msg = d;
  let md;
  const sm3keycur = new SM3Digest();
  msg = sm3keycur.GetWords(msg.toString());
  msg.forEach(element => {
    console.log('asd', element)
  });
  console.log('msg', msg);
  sm3keycur.BlockUpdate(msg, 0, msg.length);
  console.log('msg1', msg);
  const c3 = new Array(32);
  sm3keycur.DoFinal(c3, 0);
  md = sm3keycur.GetHex(c3).toString()
  console.log('加密后', md);
}

springboot 和 js (vue) 实现SM3加密 防篡改_第1张图片
springboot 和 js (vue) 实现SM3加密 防篡改_第2张图片
在这里插入图片描述

编写防篡改的功能

需要一个拦截器

@Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws Exception {

        //这里需要判断下是主站还是子站
        if (isMaster == 1) {
            long beginTimeThreadLocal = System.currentTimeMillis();
            //拦截器处理代码
            log.info("进入防 重放攻击 和 防篡改 拦截器!");

            //首先判断是不是可以不验证的 地址
            Set<String> uriSet = new HashSet<>(ignoredUrlsProperties.getSignUrls());
            boolean isMatch = false;
            for (String uri : uriSet) {
                if (uri.contains("/**")) {
                    uri = uri.split("/\\*\\*")[0];
                }
                isMatch = request.getRequestURI().contains(uri);
                if (isMatch) {
                    break;
                }
            }
            if (isMatch) {
                return true;
            } else {
                // 获取时间戳
                String timestamp = request.getHeader("timestamp");
                // 获取随机字符串
                String nonceStr = request.getHeader("nonceStr");
                // 获取签名
                String signature = request.getHeader("signature");
                //如果请求头里没有,去参数获取
                if (StrUtil.isEmpty(timestamp)) {
                    timestamp = request.getParameter("timestamp");
                }
                if (StrUtil.isEmpty(nonceStr)) {
                    nonceStr = request.getParameter("nonceStr");
                }
                if (StrUtil.isEmpty(signature)) {
                    signature = request.getParameter("signature");
                }

                if (StrUtil.isEmpty(timestamp)) {
                    throw new Exception("invalid  timestamp:验证失败,无效的时间戳");
                }

                long receiveTime = Long.parseLong(timestamp);
                // 判断时间是否大于 1 分钟 (防止重放攻击)
                long NONCE_STR_TIMEOUT_SECONDS = 1L;
                if ((System.currentTimeMillis() - receiveTime) / (1000 * 60) >= NONCE_STR_TIMEOUT_SECONDS) {
                    //addLog(beginTimeThreadLocal,request,"验证失败,数据被重放","REPLAY");
                    throw new Exception("invalid  timestamp:验证失败,时间戳过期");
                }

                // 判断该用户的nonceStr参数是否已经在redis中(防止短时间内的重放攻击)
                Boolean haveNonceStr = redisTemplate.hasKey(SecurityConstant.NONCE + nonceStr);
                if (haveNonceStr) {
                    addLog(beginTimeThreadLocal, request, "验证失败,会话被重放", "REPLAY");
                    throw new Exception("invalid nonceStr:验证失败,会话被重放");
                }

                // 对请求头参数进行签名
                if (StrUtil.isEmpty(signature) || !Objects.equals(signature, this.signature(timestamp, nonceStr, request))) {
                    addLog(beginTimeThreadLocal, request, "验证失败,数据被篡改", "TAMPERING");
                    throw new Exception("invalid signature:验证失败,数据被篡改");
                }

                // 将本次用户请求的nonceStr参数存到redis中设置xx秒后自动删除
                redisTemplate.opsForValue().set(SecurityConstant.NONCE + nonceStr, nonceStr, NONCE_STR_TIMEOUT_SECONDS, TimeUnit.MINUTES);
                log.info("验证通过");
                return true;
            }
        }
        return true;
    }

/**
     * 这里是后端生成的 sm3加密编码
     * (通过 参数+时间戳+随机数   生成的编码)
     * @param timestamp
     * @param nonceStr
     * @param request
     * @return
     * @throws UnsupportedEncodingException
     */
    private String signature(String timestamp, String nonceStr, HttpServletRequest request) {
        Map<String, Object> params = new HashMap<>(16);
        Enumeration<String> enumeration = request.getParameterNames();
        while (enumeration.hasMoreElements()){
            String name = enumeration.nextElement();
            String value = request.getParameter(name);
            params.put(name, value);
        }
        //获取参数,因为有的接口是没有参数的,所以要单独处理下
        String param=this.sortQueryParamString(params);
        String qs ;
        if(StringUtils.isNotEmpty(param)){
            qs=String.format("%s×tamp=%s&nonceStr=%s", param, timestamp, nonceStr);
        }else{
            qs=String.format("timestamp=%s&nonceStr=%s", timestamp, nonceStr);
        }
        log.info("qs:{}", qs);
        //从前端获取的nonce和后端的 进行对比,如果不一致则表示数据被篡改
        String newNonce= SM3Util.hashCodeStr(qs);
        log.info("newNonce:{}", newNonce);
        return newNonce;
    }

    /**
     * 按照字母顺序进行升序排序
     *
     * @param params 请求参数
     * @return 排序后结果
     */
    private String sortQueryParamString(Map<String, Object> params) {
        List<String> listKeys = Lists.newArrayList(params.keySet());
        Collections.sort(listKeys);
        StrBuilder content = StrBuilder.create();
        for (String param : listKeys) {
            //如果是全部作为参数传过来,也会接收到加密的签,所以需要过滤掉(下载和导出功能会出现)
            if(!param.equals("signature")){
                content.append(param).append("=").append(params.get(param)).append("&");
            }
        }
        if (content.length() > 0) {
            return content.subString(0, content.length() - 1);
        }
        return content.toString();
    }

然后需要配置这个过滤器生效


@Configuration
public class InterceptorConfiguration implements WebMvcConfigurer {
	@Autowired
    private IgnoredUrlsProperties ignoredUrlsProperties;

    @Autowired
    private LimitRaterInterceptor limitRaterInterceptor;
    @Autowired
    private CorsInterceptor corsInterceptor;
    @Autowired
    private SignAuthInterceptor replayInterceptor;
 	
 	@Override
    public void addInterceptors(InterceptorRegistry registry) {

        // 注册拦截器
        InterceptorRegistration ir = registry.addInterceptor(limitRaterInterceptor);
        // 配置拦截的路径
        ir.addPathPatterns("/**");
        // 配置不拦截的路径 避免加载css也拦截
        ir.excludePathPatterns(ignoredUrlsProperties.getUrls());

        registry.addInterceptor(getSysLogAspect())
                .addPathPatterns("/**")
                .excludePathPatterns("/css/**", "/webjars/**", "/images/**", "/js/**", "/login.html");

        registry.addInterceptor(corsInterceptor).addPathPatterns("/**");
        registry.addInterceptor(replayInterceptor).addPathPatterns("/**");
    }

在yml文件中配置过滤的类

# 忽略url
ignored:
# 无需验证重放和篡改认证的请求
  signUrls:
    - /druid/**
    - /static/**
    - /store/**
    - /resources/**
    - /doc.html
    - /swagger-ui.html
    - /swagger-resources/**
    - /swagger/**
    - /xboot/test/**
    - /webjars/**

前端也需要对应的加密

这里就不写前端的了,前端传过来需要有header 头的时间戳、随机数、和校验码
那么这个校验码就是 = 所有的参数(key=value形式逐个拼接)+时间戳 +随机数

校验

前端给过来的和后端获取到的参数在进行加密,然后对比两个码是否一致即可。

你可能感兴趣的:(JAVA,SpringBoot,工具使用,加密解密,java,javascript,vue.js,安全)