Переглянути джерело

完成前后端加解密、签名、验签

xusl 2 роки тому
батько
коміт
015e3690c8

+ 1 - 1
backend/src/main/java/com/jiayue/ssi/config/WebSecurityConfig.java

@@ -56,8 +56,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
     @Override
     protected void configure(HttpSecurity httpSecurity) throws Exception {
-        httpSecurity.addFilterBefore(new VerifySmFilter(), UsernamePasswordAuthenticationFilter.class);
         httpSecurity.addFilterBefore(new InterfaceLimitFilter(), UsernamePasswordAuthenticationFilter.class);
+        httpSecurity.addFilterBefore(new VerifySmFilter(), UsernamePasswordAuthenticationFilter.class);
         httpSecurity.addFilterBefore(new VerifyCodeFilter(), UsernamePasswordAuthenticationFilter.class);
         httpSecurity.addFilterBefore(new MailCodeFilter(), UsernamePasswordAuthenticationFilter.class);
         httpSecurity.addFilterBefore(new JwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);

+ 4 - 4
backend/src/main/java/com/jiayue/ssi/constant/SecretKeyConstants.java

@@ -8,17 +8,17 @@ public class SecretKeyConstants {
     /**
      * 客户端密钥对(公钥)
      */
-    public static final String CLIENT_PUBLIC_KEY = "042b5cd2658c0b670f9c83aa57ac2df7b3c72ad96b66fc467d716c9be1ba8a107a10857f9b3739d323867a09c27c72a6be024be1cdc3bceb7e1eda16c7f168898d";
+    public static final String CLIENT_PUBLIC_KEY = "0460ff8c8c306fe62f6f9d11c5c82c30d10bbbc703da094e423072cac7dc663c97fad52eccb34f311f47a07f280de157ba4f2aa659cabe749121384b9376ea2ed2";
     /**
      * 客户端密钥对(私钥)
      */
-    public static final String CLIENT_PRIVATE_KEY = "79507190f53fb88717551c5a784f3f54627cbcb92d35a531ce4630f12fe62a40";
+    public static final String CLIENT_PRIVATE_KEY = "27ce6eec39dbf3b564a77c4da1e129fe1ba01a92f6d61055a33ed14ffcbc949e";
     /**
      * 服务端密钥对(公钥)
      */
-    public static final String SERVER_PUBLIC_KEY = "04298364ec840088475eae92a591e01284d1abefcda348b47eb324bb521bb03b0b2a5bc393f6b71dabb8f15c99a0050818b56b23f31743b93df9cf8948f15ddb54";
+    public static final String SERVER_PUBLIC_KEY = "041967638ca43d4577d8dba166bff4437fde944270101f398a95b846ec2f8177d09f8abc5d62b6cd2c7216274d7abe0c8e04b0bb691207a32dd2e12d6bd2798672";
     /**
      * 服务端密钥对(私钥)
      */
-    public static final String SERVER_PRIVATE_KEY = "3037723d47292171677ec8bd7dc9af696c7472bc5f251b2cec07e65fdef22e25";
+    public static final String SERVER_PRIVATE_KEY = "6155d63ee27cbeca07f3e40c4f8856f1be8119fcbda1aadc7e0e595e52bad7bd";
 }

+ 2 - 0
backend/src/main/java/com/jiayue/ssi/filter/MailCodeFilter.java

@@ -61,6 +61,8 @@ public class MailCodeFilter extends OncePerRequestFilter {
                 return;
             }
             if (!String.valueOf(mailCode).toLowerCase().equals(mailbox.toLowerCase())) {
+                // 删除缓存邮箱口令
+                LocalCache.remove(CacheConstants.MAIL_CODE_KEY + username);
                 response.addHeader("Access-Control-Allow-Origin", "*");
                 response.setContentType("text/html;charset=UTF-8");
                 response.setStatus(401);

+ 2 - 0
backend/src/main/java/com/jiayue/ssi/filter/VerifyCodeFilter.java

@@ -58,6 +58,8 @@ public class VerifyCodeFilter extends OncePerRequestFilter {
                 return;
             }
             if (!String.valueOf(uuidObj).toLowerCase().equals(requestCaptcha.toLowerCase())) {
+                // 删除缓存验证码
+                LocalCache.remove(CacheConstants.CAPTCHA_CODE_KEY + verifyuuid);
                 response.addHeader("Access-Control-Allow-Origin", "*");
                 response.setContentType("text/html;charset=UTF-8");
                 response.setStatus(401);

+ 90 - 0
backend/src/main/java/com/jiayue/ssi/filter/VerifySmFilter.java

@@ -0,0 +1,90 @@
+package com.jiayue.ssi.filter;
+
+import com.jiayue.ssi.constant.SecretKeyConstants;
+import com.jiayue.ssi.servlet.ParameterRequestWrapper;
+import com.jiayue.ssi.util.SM2CryptUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.core.annotation.Order;
+import org.springframework.web.filter.OncePerRequestFilter;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+* 验证sm过滤器
+*
+* @author xsl
+* @since 2023/02/27
+*/
+@Order(4)
+public class VerifySmFilter extends OncePerRequestFilter {
+    private String defaultFilterProcessUrl = "/user/login";
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+        // 不是登录操作
+        if (!("POST".equalsIgnoreCase(request.getMethod()) && defaultFilterProcessUrl.equals(request.getServletPath()))) {
+            // 验证token
+            String tokenStr = request.getHeader("Authorization");
+            if (StringUtils.isNotEmpty(tokenStr)){
+                // 解密token
+                String decryptTokenStr = SM2CryptUtils.decrypt(tokenStr,SecretKeyConstants.SERVER_PRIVATE_KEY);
+                System.out.println("接收token后解密:"+decryptTokenStr);
+                String tokenSign = request.getHeader("TokenSign");
+                // 验证签名
+                boolean verifySign = SM2CryptUtils.verifySign(SecretKeyConstants.CLIENT_PUBLIC_KEY,decryptTokenStr,tokenSign);
+                if (!verifySign){
+                    // 验签失败
+                    response.addHeader("Access-Control-Allow-Origin", "*");
+                    response.setContentType("text/html;charset=UTF-8");
+                    response.setStatus(401);
+                    response.getWriter().write("token验签失败,不能访问系统!");
+                    return;
+                }
+            }
+            else{
+                response.addHeader("Access-Control-Allow-Origin", "*");
+                response.setContentType("text/html;charset=UTF-8");
+                response.setStatus(401);
+                response.getWriter().write("没有令牌权限,不能访问系统!");
+                return;
+            }
+        }
+
+        // 验证加密的参数文本
+        String data_sm2 = request.getParameter("secretData");
+        String decryptStr = "";
+        if (StringUtils.isNotEmpty(data_sm2)){
+            System.out.println("接收前端加密:"+data_sm2);
+            decryptStr = SM2CryptUtils.decrypt(data_sm2,SecretKeyConstants.SERVER_PRIVATE_KEY);
+            System.out.println("解密后:" + decryptStr);
+            // 验签前端参数
+            String paramSign = request.getParameter("paramSign");
+            System.out.println(paramSign);
+            // 验证签名
+            boolean verifySign = SM2CryptUtils.verifySign(SecretKeyConstants.CLIENT_PUBLIC_KEY,decryptStr,paramSign);
+            if (!verifySign){
+                // 验签失败
+                response.addHeader("Access-Control-Allow-Origin", "*");
+                response.setContentType("text/html;charset=UTF-8");
+                response.setStatus(401);
+                response.getWriter().write("参数验签失败,不能访问系统!");
+                return;
+            }
+        }
+        Map<String,Object> stringToMap = new HashMap(16);
+        if (!"".equals(decryptStr)){
+            String[] tempInterval = decryptStr.split("&");
+            for (int i=0;i<tempInterval.length;i++){
+                String[] fieldKeyValue = tempInterval[i].split("=");
+                stringToMap.put(fieldKeyValue[0],fieldKeyValue[1]);
+            }
+        }
+        ParameterRequestWrapper pr = new ParameterRequestWrapper(request, stringToMap);
+        filterChain.doFilter(pr, response);
+    }
+}

+ 92 - 0
backend/src/main/java/com/jiayue/ssi/util/SM2CryptUtils.java

@@ -0,0 +1,92 @@
+package com.jiayue.ssi.util;
+
+import cn.hutool.core.util.HexUtil;
+import cn.hutool.crypto.BCUtil;
+import cn.hutool.crypto.ECKeyUtil;
+import cn.hutool.crypto.asymmetric.KeyType;
+import cn.hutool.crypto.asymmetric.SM2;
+import cn.hutool.crypto.SmUtil;
+import com.jiayue.ssi.constant.SecretKeyConstants;
+import org.bouncycastle.crypto.engines.SM2Engine;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.signers.PlainDSAEncoding;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
+
+import java.security.PublicKey;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * sm2工具类
+ *
+ * @author xsl
+ * @since 2023/02/28
+ */
+public class SM2CryptUtils {
+
+    // 生成秘钥对
+    public static Map<String, String> createSM2Key() {
+        SM2 sm2 = SmUtil.sm2();
+        sm2.setMode(SM2Engine.Mode.C1C3C2);
+        // 这里会自动生成对应的随机秘钥对
+        byte[] privateKey = BCUtil.encodeECPrivateKey(sm2.getPrivateKey());
+        // 这里得到未压缩的公钥
+        byte[] publicKey = ((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false);
+        // 私钥
+        String priKey = HexUtil.encodeHexStr(privateKey);
+        // 公钥
+        String pubKey = HexUtil.encodeHexStr(publicKey);
+        Map<String, String> keys = new HashMap<>();
+        keys.put(priKey, pubKey);
+        return keys;
+    }
+
+    // 加密
+    public static String encrypt(String data,String publicKey) {
+        String publicKeyTmp = publicKey;
+        if (publicKey.length() == 130) {
+            // 这里需要去掉开始第一个字节 第一个字节表示标记
+            publicKeyTmp = publicKey.substring(2);
+        }
+        String xhex = publicKeyTmp.substring(0, 64);
+        String yhex = publicKeyTmp.substring(64, 128);
+        ECPublicKeyParameters ecPublicKeyParameters = BCUtil.toSm2Params(xhex, yhex);
+        // 创建sm2 对象
+        SM2 sm2 = new SM2(null, ecPublicKeyParameters);
+        sm2.usePlainEncoding();
+        sm2.setMode(SM2Engine.Mode.C1C3C2);
+        String hex = sm2.encryptHex(data, KeyType.PublicKey).substring(2);
+        return hex;
+
+    }
+
+    // 解密
+    public static String decrypt(String data,String privateKey) {
+        // ECKeyUtil.toSm2PrivateParams()
+        SM2 sm2 = new SM2(ECKeyUtil.toSm2PrivateParams(privateKey), null);
+        sm2.setMode(SM2Engine.Mode.C1C3C2);
+        sm2.setEncoding(new PlainDSAEncoding());
+        String encryptStr = sm2.decryptStr("04" + data, KeyType.PrivateKey);
+        return encryptStr;
+    }
+
+    /**
+     * 验证签名
+     * @param publicKey     公钥
+     * @param content       签名原内容
+     * @param sign          签名
+     * @return
+     */
+    public static boolean verifySign(String publicKey, String content, String sign) {
+        SM2 sm2 = new SM2(null, publicKey);
+        boolean verify = sm2.verifyHex(HexUtil.encodeHexStr(content), sign);
+        return verify;
+    }
+
+    public static void main(String[] args) {
+        Map<String,String> map = createSM2Key();
+        for(Map.Entry<String, String> entry:map.entrySet()){
+            System.out.println(entry.getKey()+"--->"+entry.getValue());
+        }
+    }
+}

+ 39 - 11
backend/src/test/java/com/jiayue/ssi/service/Test.java

@@ -1,6 +1,8 @@
 package com.jiayue.ssi.service;
 
+import cn.hutool.core.convert.Convert;
 import cn.hutool.core.date.DateUnit;
+import cn.hutool.core.util.CharsetUtil;
 import cn.hutool.core.util.HexUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.crypto.BCUtil;
@@ -23,16 +25,42 @@ import java.util.Map;
 */
 public class Test {
     public static void main(String[] args) throws Exception{
-        SM2 sm2 = SmUtil.sm2();
-        //这里会自动生成对应的随机秘钥对
-        byte[] privateKey = BCUtil.encodeECPrivateKey(sm2.getPrivateKey());
-        //这里默认公钥压缩  公钥的第一个字节用于表示是否压缩 02或者03表示是压缩公钥,04表示未压缩公钥
-//        byte[] publicKey = BCUtil.encodeECPublicKey(sm2.getPublicKey());
-        //这里得到未压缩的公钥
-         byte[] publicKey = ((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false);
-        String priKey = HexUtil.encodeHexStr(privateKey);
-        String pubKey = HexUtil.encodeHexStr(publicKey);
-        System.out.println(priKey);
-        System.out.println(pubKey);
+//        SM2 sm2 = SmUtil.sm2();
+//        //这里会自动生成对应的随机秘钥对
+//        byte[] privateKey = BCUtil.encodeECPrivateKey(sm2.getPrivateKey());
+//        //这里默认公钥压缩  公钥的第一个字节用于表示是否压缩 02或者03表示是压缩公钥,04表示未压缩公钥
+////        byte[] publicKey = BCUtil.encodeECPublicKey(sm2.getPublicKey());
+//        //这里得到未压缩的公钥
+//         byte[] publicKey = ((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false);
+//        String priKey = HexUtil.encodeHexStr(privateKey);
+//        String pubKey = HexUtil.encodeHexStr(publicKey);
+//        System.out.println("私钥: " + priKey);
+//        System.out.println("公钥: " + pubKey);
+//
+
+
+
+//        String hex = "757365726E616D653D61646D696E2670617373776F72643D61646D696E26636F64653D3963356226766572696679757569643D6534336331633731626266643464313861306439353236633636663131363630266D61696C626F783D313131313131";
+//        int bsLength = hex.length();
+//        if (bsLength % 2 != 0) {
+//            hex = "0"+hex;
+//            bsLength = hex.length();
+//        }
+//        byte[] cs = new byte[bsLength / 2];
+//        String st;
+//        for (int i = 0; i < bsLength; i = i + 2) {
+//            st = hex.substring(i, i + 2);
+//            cs[i / 2] = (byte) Integer.parseInt(st, 16);
+//        }
+//        System.out.println(new String(cs,"utf-8"));
+        String content = "SM2签名和验签";
+        final SM2 sm2 = SmUtil.sm2("27ce6eec39dbf3b564a77c4da1e129fe1ba01a92f6d61055a33ed14ffcbc949e","0460ff8c8c306fe62f6f9d11c5c82c30d10bbbc703da094e423072cac7dc663c97fad52eccb34f311f47a07f280de157ba4f2aa659cabe749121384b9376ea2ed2");
+        String sign = sm2.signHex(HexUtil.encodeHexStr(content));
+        System.out.println("sign:" + sign);
+
+        boolean verify = sm2.verifyHex(HexUtil.encodeHexStr(content), sign);
+        System.out.println("校验结果为:" + verify);
+
+
     }
 }

+ 29 - 14
ui/src/main.js

@@ -21,7 +21,8 @@ import VXETable from 'vxe-table'
 import 'vxe-table/lib/index.css'
 
 import {removeToken} from './utils/auth'
-import {getBrowserToken} from './utils/commonFuc' // get token from cookie
+import {getBrowserToken} from './utils/commonFuc'
+import {sm2 as sm222, sm2} from "sm-crypto"; // get token from cookie
 Vue.prototype.$moment = moment
 Vue.prototype.$echarts = echarts
 Vue.use(VXETable)
@@ -53,6 +54,11 @@ VXETable.setup({
   validArgs: 'obsolete' // 将自定义校验参数还原为 Function(rule, cellValue, callback)
 })
 
+// 公钥Q
+let publicKey2 = '041967638ca43d4577d8dba166bff4437fde944270101f398a95b846ec2f8177d09f8abc5d62b6cd2c7216274d7abe0c8e04b0bb691207a32dd2e12d6bd2798672'
+// 私钥D
+let privateKey1 = '27ce6eec39dbf3b564a77c4da1e129fe1ba01a92f6d61055a33ed14ffcbc949e'
+
 Vue.prototype.$axios.interceptors.request.use(
     config => {
       // get请求映射params参数
@@ -64,19 +70,21 @@ Vue.prototype.$axios.interceptors.request.use(
       }
       if (config.method === 'post' || config.method === 'put') {
         if (config.url!='/getMailCode'){
+          // 参数加密
           let encryptParam = doEncrypt(config.data.toString())
-          let result = 'secretData='+encryptParam
+          // 参数签名
+          let paramSign = doSign(config.data.toString())
+          let result = 'secretData='+encryptParam+'&paramSign='+paramSign
           config.data = result
         }
       }
-      return config
 
-// console.log(config)
-//         if (getBrowserToken()) { // 判断是否存在token,如果存在的话,则每个http header都加上token
-//             config.headers['Authorization'] = getBrowserToken()
-//             sessionStorage.setItem('user', getBrowserUser())
-//         }
-//         return config
+      if (getBrowserToken()) { // 判断是否存在token,如果存在的话,则每个http header都加上token
+        let tokenStr = doEncrypt(getBrowserToken())
+        config.headers['Authorization']= tokenStr
+        config.headers['TokenSign']= doSign(getBrowserToken())
+      }
+      return config
     },
     error => {
         // do something with request error
@@ -85,7 +93,6 @@ Vue.prototype.$axios.interceptors.request.use(
     }
 )
 
-
 function getBrowserUser() {
     var user = "";
     var ca = document.cookie.split(';');
@@ -232,8 +239,7 @@ export function doEncrypt(msgString) {
   let sm2 = require('sm-crypto').sm2;
   // 1 - C1C3C2;	0 - C1C2C3;	默认为1
   let cipherMode = 1
-  // 公钥Q
-  let publicKey2 = '04298364ec840088475eae92a591e01284d1abefcda348b47eb324bb521bb03b0b2a5bc393f6b71dabb8f15c99a0050818b56b23f31743b93df9cf8948f15ddb54'
+
   // 加密结果
   let encryptData = sm2.doEncrypt(msgString, publicKey2, cipherMode);
   // 加密后的密文前需要添加04,后端才能正常解密
@@ -251,8 +257,7 @@ export function doDecryptStr(enStr) {
   let sm2 = require('sm-crypto').sm2;
   // 1 - C1C3C2;	0 - C1C2C3;	默认为1
   let cipherMode = 1
-  // 私钥
-  let privateKey1 = '79507190f53fb88717551c5a784f3f54627cbcb92d35a531ce4630f12fe62a40'
+
   // 加密后的密文,需要前去掉04。因为doDecrypt中自行添加了04,后端加密代码也自行添加了04
   let en = enStr.data.substr(2)
   // 解密结果
@@ -261,3 +266,13 @@ export function doDecryptStr(enStr) {
   let objData = JSON.parse(doDecrypt)
   return objData;
 }
+
+// 签名:
+export function doSign(msgString) {
+  let sm2 = require('sm-crypto').sm2;
+  // 1 - C1C3C2;	0 - C1C2C3;	默认为1
+  let cipherMode = 1
+  // 签名
+  let sign = sm2.doSignature(msgString,privateKey1, { hash:true, der:true })
+  return sign;
+}