<dependency>
<groupId>org.bouncycastlegroupId>
<artifactId>bcprov-jdk15onartifactId>
<version>1.68version>
dependency>
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);
}
}
cipher-core.js
core.js
jsbn.js
jsbn2.js
sm3.js
<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>
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);
}
@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("/**");
}
# 忽略url
ignored:
# 无需验证重放和篡改认证的请求
signUrls:
- /druid/**
- /static/**
- /store/**
- /resources/**
- /doc.html
- /swagger-ui.html
- /swagger-resources/**
- /swagger/**
- /xboot/test/**
- /webjars/**
这里就不写前端的了,前端传过来需要有header 头的时间戳、随机数、和校验码
那么这个校验码就是 = 所有的参数(key=value形式逐个拼接)+时间戳 +随机数
前端给过来的和后端获取到的参数在进行加密,然后对比两个码是否一致即可。