整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          C#爬蟲與反爬蟲-字體加密篇

          C#爬蟲與反爬蟲-字體加密篇

          爬蟲和反爬蟲是一條很長的路,遇到過js加密,flash加密、重點信息生成圖片、css圖片定位、請求頭.....等手段;今天我們來聊一聊字體;

          那是一個偶然我遇到了這個網站,把價格信息全加密了;瀏覽器展示:

          查看源碼后是這樣:

          當時突然恍然大悟,以為不就是把價格換成 xxxx: .. 字符實體了嘛 我轉下就行了;(注:大家可能對轉義字符(實體名稱)比較熟悉 比如雙引號對應" ,字符實體呢就 開頭 ; 結尾 中間那些是ASCII碼 ;我這個示例中又有點不一樣的是 以x開頭 表明是16進制的 ASCII碼;詳解:http://www.w3school.com.cn/html/html_entities.asp 和

          http://www.w3school.com.cn/tags/html_ref_ascii.asp) ;于是開始了轉碼。。。。

          這是什么鬼,難道我的判斷有誤。當我打開瀏覽器調試界面發現

          也是亂碼,我在界面上copy金額黏貼到記事本看看

          也是亂碼說明上面的推斷是沒有錯的,他應該就是在顯示上做了什么處理;

          在找找發現一段鬼鬼祟祟的代碼;給price設置了一個叫zhongshugui的字體,但是好好的字體為啥要用base64 不用 http加載,http加載貌似可以用瀏覽器緩存的不是更好,base64 的話我每次瀏覽一個網頁不都是要重新加載一下;莫非他不想緩存就是每次加載都不一樣,經各種刷新后發現確實每次都生成不同文件;

          既然感覺字體有問題 那我們就解析出來看看是啥,

          這些0~9正好和界面上的html字符實體編碼一一對應;所以這個加密方式就是 html寫對應的HTML實體并與字體里的ASCII對應,自定義字體文件 以base64的方式內嵌到html里 ;每個頁面隨機生成不同的字體(就是字體里的ASCII);上面那個是為了寫帖子自己仿造原網站做的例子;

          C#解析字體的類在System.Windows.Media命名空間下Fonts類:

          https://docs.microsoft.com/zh-cn/dotnet/api/system.windows.media.fonts?view=netframework-4.8

          作者:~小菜鳥

          出處:https://www.cnblogs.com/qqljcn/p/11089286.html,

           前言

            前段時間看到一篇文章講如何保證API調用時數據的安全性(傳送門:https://blog.csdn.net/ityouknow/article/details/80603617),文中講到利用RSA來加密傳輸AES的秘鑰,用AES來加密數據,并提供如下思路:

            說人話就是前、后端各自生成自己的RSA秘鑰對(公鑰、私鑰),然后交換公鑰(后端給前端的是正常的明文公鑰,前端給后端的是用后端公鑰加密后的密文公鑰;PS:其實我覺得直接交換兩個明文公鑰就行了),后端生成AES的明文key,用明文key進行AES加密得到密文數據,用前端的公鑰進行RSA加密得到密文key,API交互時并將密文數據與密文key進行傳輸,前端用自己的私鑰進行RAS解密的到明文key,用明文key進行AES解密得到明文數據;前端給后端發送數據時同理,這樣一來,傳輸的數據都是密文,且只有秘鑰才能解密

            可惜這篇博客只提供了思路,但并沒有具體的代碼,我們在網上查找一下資料,開始生擼代碼,實現一個前后端API交互數據加密——AES與RSA混合加密,并應用到項目中


            后端加、解密

            從網上查找工具類,再進行改造

            先引入Base64工具類

                  <!-- Base64編碼需要  -->
                  <dependency>
                      <groupId>org.apache.directory.studio</groupId>
                      <artifactId>org.apache.commons.codec</artifactId>
                      <version>1.8</version>
                  </dependency>


            AES

          AesUtil

           package cn.huanzi.ims.util;
          
          import org.apache.tomcat.util.codec.binary.Base64;
          import org.bouncycastle.jce.provider.BouncyCastleProvider;
          
          import javax.crypto.Cipher;
          import javax.crypto.spec.SecretKeySpec;
          import java.nio.charset.StandardCharsets;
          import java.security.SecureRandom;
          import java.util.Random;
          
          /**
           * AES加、解密算法工具類
           */
          public class AesUtil {
              /**
               * 加密算法AES
               */
              private static final String KEY_ALGORITHM="AES";
          
              /**
               * key的長度,Wrong key size: must be equal to 128, 192 or 256
               * 傳入時需要16、24、36
               */
              private static final Integer KEY_LENGTH=16 * 8;
          
              /**
               * 算法名稱/加密模式/數據填充方式
               * 默認:AES/ECB/PKCS5Padding
               */
              private static final String ALGORITHMS="AES/ECB/PKCS5Padding";
          
              /**
               * 后端AES的key,由靜態代碼塊賦值
               */
              public static String key;
          
              static {
                  key=getKey();
              }
          
              /**
               * 獲取key
               */
              public static String getKey() {
                  StringBuilder uid=new StringBuilder();
                  //產生16位的強隨機數
                  Random rd=new SecureRandom();
                  for (int i=0; i < KEY_LENGTH / 8; i++) {
                      //產生0-2的3位隨機數
                      int type=rd.nextInt(3);
                      switch (type) {
                          case 0:
                              //0-9的隨機數
                              uid.append(rd.nextInt(10));
                              break;
                          case 1:
                              //ASCII在65-90之間為大寫,獲取大寫隨機
                              uid.append((char) (rd.nextInt(25) + 65));
                              break;
                          case 2:
                              //ASCII在97-122之間為小寫,獲取小寫隨機
                              uid.append((char) (rd.nextInt(25) + 97));
                              break;
                          default:
                              break;
                      }
                  }
                  return uid.toString();
              }
          
              /**
               * 加密
               *
               * @param content    加密的字符串
               * @param encryptKey key值
               */
              public static String encrypt(String content, String encryptKey) throws Exception {
                  //設置Cipher對象
                  Cipher cipher=Cipher.getInstance(ALGORITHMS,new BouncyCastleProvider());
                  cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), KEY_ALGORITHM));
          
                  //調用doFinal
                  byte[] b=cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
          
                  // 轉base64
                  return Base64.encodeBase64String(b);
          
              }
          
              /**
               * 解密
               *
               * @param encryptStr 解密的字符串
               * @param decryptKey 解密的key值
               */
              public static String decrypt(String encryptStr, String decryptKey) throws Exception {
                  //base64格式的key字符串轉byte
                  byte[] decodeBase64=Base64.decodeBase64(encryptStr);
          
                  //設置Cipher對象
                  Cipher cipher=Cipher.getInstance(ALGORITHMS,new BouncyCastleProvider());
                  cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), KEY_ALGORITHM));
          
                  //調用doFinal解密
                  byte[] decryptBytes=cipher.doFinal(decodeBase64);
                  return new String(decryptBytes);
              }
          
          }

            RSA

          RsaUtil

           package cn.huanzi.ims.util;
          
          
          import org.apache.commons.codec.binary.Base64;
          
          import javax.crypto.Cipher;
          import java.io.ByteArrayOutputStream;
          import java.security.Key;
          import java.security.KeyFactory;
          import java.security.KeyPair;
          import java.security.KeyPairGenerator;
          import java.security.interfaces.RSAPrivateKey;
          import java.security.interfaces.RSAPublicKey;
          import java.security.spec.PKCS8EncodedKeySpec;
          import java.security.spec.X509EncodedKeySpec;
          import java.util.HashMap;
          import java.util.Map;
          
          /**
           * RSA加、解密算法工具類
           */
          public class RsaUtil {
          
              /**
               * 加密算法AES
               */
              private static final String KEY_ALGORITHM="RSA";
          
              /**
               * 算法名稱/加密模式/數據填充方式
               * 默認:RSA/ECB/PKCS1Padding
               */
              private static final String ALGORITHMS="RSA/ECB/PKCS1Padding";
          
              /**
               * Map獲取公鑰的key
               */
              private static final String PUBLIC_KEY="publicKey";
          
              /**
               * Map獲取私鑰的key
               */
              private static final String PRIVATE_KEY="privateKey";
          
              /**
               * RSA最大加密明文大小
               */
              private static final int MAX_ENCRYPT_BLOCK=117;
          
              /**
               * RSA最大解密密文大小
               */
              private static final int MAX_DECRYPT_BLOCK=128;
          
              /**
               * RSA 位數 如果采用2048 上面最大加密和最大解密則須填寫:  245 256
               */
              private static final int INITIALIZE_LENGTH=1024;
          
              /**
               * 后端RSA的密鑰對(公鑰和私鑰)Map,由靜態代碼塊賦值
               */
              private static Map<String, Object> genKeyPair=new HashMap<>();
          
              static {
                  try {
                      genKeyPair.putAll(genKeyPair());
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          
              /**
               * 生成密鑰對(公鑰和私鑰)
               */
              private static Map<String, Object> genKeyPair() throws Exception {
                  KeyPairGenerator keyPairGen=KeyPairGenerator.getInstance(KEY_ALGORITHM);
                  keyPairGen.initialize(INITIALIZE_LENGTH);
                  KeyPair keyPair=keyPairGen.generateKeyPair();
                  RSAPublicKey publicKey=(RSAPublicKey) keyPair.getPublic();
                  RSAPrivateKey privateKey=(RSAPrivateKey) keyPair.getPrivate();
                  Map<String, Object> keyMap=new HashMap<String, Object>(2);
                  //公鑰
                  keyMap.put(PUBLIC_KEY, publicKey);
                  //私鑰
                  keyMap.put(PRIVATE_KEY, privateKey);
                  return keyMap;
              }
          
              /**
               * 私鑰解密
               *
               * @param encryptedData 已加密數據
               * @param privateKey    私鑰(BASE64編碼)
               */
              public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws Exception {
                  //base64格式的key字符串轉Key對象
                  byte[] keyBytes=Base64.decodeBase64(privateKey);
                  PKCS8EncodedKeySpec pkcs8KeySpec=new PKCS8EncodedKeySpec(keyBytes);
                  KeyFactory keyFactory=KeyFactory.getInstance(KEY_ALGORITHM);
                  Key privateK=keyFactory.generatePrivate(pkcs8KeySpec);
          
                  //設置加密、填充方式
                  /*
                      如需使用更多加密、填充方式,引入
                      <dependency>
                          <groupId>org.bouncycastle</groupId>
                          <artifactId>bcprov-jdk16</artifactId>
                          <version>1.46</version>
                      </dependency>
                      并改成
                      Cipher cipher=Cipher.getInstance(ALGORITHMS ,new BouncyCastleProvider());
                   */
                  Cipher cipher=Cipher.getInstance(ALGORITHMS);
                  cipher.init(Cipher.DECRYPT_MODE, privateK);
          
                  //分段進行解密操作
                  return encryptAndDecryptOfSubsection(encryptedData, cipher, MAX_DECRYPT_BLOCK);
              }
          
              /**
               * 公鑰加密
               *
               * @param data      源數據
               * @param publicKey 公鑰(BASE64編碼)
               */
              public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {
                  //base64格式的key字符串轉Key對象
                  byte[] keyBytes=Base64.decodeBase64(publicKey);
                  X509EncodedKeySpec x509KeySpec=new X509EncodedKeySpec(keyBytes);
                  KeyFactory keyFactory=KeyFactory.getInstance(KEY_ALGORITHM);
                  Key publicK=keyFactory.generatePublic(x509KeySpec);
          
                  //設置加密、填充方式
                  /*
                      如需使用更多加密、填充方式,引入
                      <dependency>
                          <groupId>org.bouncycastle</groupId>
                          <artifactId>bcprov-jdk16</artifactId>
                          <version>1.46</version>
                      </dependency>
                      并改成
                      Cipher cipher=Cipher.getInstance(ALGORITHMS ,new BouncyCastleProvider());
                   */
                  Cipher cipher=Cipher.getInstance(ALGORITHMS);
                  cipher.init(Cipher.ENCRYPT_MODE, publicK);
          
                  //分段進行加密操作
                  return encryptAndDecryptOfSubsection(data, cipher, MAX_ENCRYPT_BLOCK);
              }
          
              /**
               * 獲取私鑰
               */
              public static String getPrivateKey() {
                  Key key=(Key) genKeyPair.get(PRIVATE_KEY);
                  return Base64.encodeBase64String(key.getEncoded());
              }
          
              /**
               * 獲取公鑰
               */
              public static String getPublicKey() {
                  Key key=(Key) genKeyPair.get(PUBLIC_KEY);
                  return Base64.encodeBase64String(key.getEncoded());
              }
          
              /**
               * 分段進行加密、解密操作
               */
              private static byte[] encryptAndDecryptOfSubsection(byte[] data, Cipher cipher, int encryptBlock) throws Exception {
                  int inputLen=data.length;
                  ByteArrayOutputStream out=new ByteArrayOutputStream();
                  int offSet=0;
                  byte[] cache;
                  int i=0;
                  // 對數據分段加密
                  while (inputLen - offSet > 0) {
                      if (inputLen - offSet > encryptBlock) {
                          cache=cipher.doFinal(data, offSet, encryptBlock);
                      } else {
                          cache=cipher.doFinal(data, offSet, inputLen - offSet);
                      }
                      out.write(cache, 0, cache.length);
                      i++;
                      offSet=i * encryptBlock;
                  }
                  byte[] toByteArray=out.toByteArray();
                  out.close();
                  return toByteArray;
              }
          }

            簡單測試

            AES對稱加密、解密簡單測試

            1、字符串

          public static void main(String[] args) {
                  //16位
                  String key="MIGfMA0GCSqGSIb3";
          
                  //字符串
                  String str="huanzi.qch@qq.com:歡子";
                  try {
                      //加密
                      String encrypt=AesUtil.encrypt(str, key);
                      //解密
                      String decrypt=AesUtil.decrypt(encrypt, key);
          
                      System.out.println("加密前:" + str);
                      System.out.println("加密后:" + encrypt);
                      System.out.println("解密后:" + decrypt);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          加密前:huanzi.qch@qq.com:歡子
          加密后:dXPRtcdHPQSTwxLnmixkaSvNfGHhg5Gz8sGTtiqCpPo=解密后:huanzi.qch@qq.com:歡子


            2、復雜對象

          public static void main(String[] args) {
                  //16位
                  String key="MIGfMA0GCSqGSIb3";
          
                  //復雜對象
                  ImsUserVo userVo=new ImsUserVo();
                  userVo.setUserName("123456");
                  userVo.setPassword("111111");
                  try {
                      //加密
                      String encrypt=AesUtil.encrypt(userVo.toString(), key);
                      //解密
                      String decrypt=AesUtil.decrypt(encrypt, key);
                      System.out.println("加密前:" + userVo.toString());
                      System.out.println("加密后:" + encrypt);
                      System.out.println("解密后:" + decrypt);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          加密前:ImsUserVo(id=null, userName=123456, password=111111, nickName=null, gender=null, avatar=null, email=null, phone=null, sign=null, createdTime=null, updataTime=null)
          加密后:AXv8ewfY+gbuZ/dCmGAxngLry+Idlp1NKZ8yyf9+bmrBggUBo3b+e4XRwMAE/DP+vFS2HpgeYQTrZM1ECjo01uvZ/T6lY7b2C6L8PTotYHQyJM3kOs+YNXL/uyvFZ2EICSQWhmM1XX+g0juHLCbgQDMNXc56S/7eH2p+su1+CTMygUBCF0U/gZaSzqylqujTb3sg7q4xMuxCQ6ne6xmL3ebjanOLeMJHypTDy1rlJTw=解密后:ImsUserVo(id=null, userName=123456, password=111111, nickName=null, gender=null, avatar=null, email=null, phone=null, sign=null, createdTime=null, updataTime=null)

          RAS非對稱加密、解密簡單測試

            1、字符串的RSA公鑰加密、私鑰解密

          public static void main(String[] args) {
                  //字符串
                  String str="huanzi.qch@qq.com:歡子";
                  try {
                      System.out.println("私鑰:" + RsaUtil.getPrivateKey());
                      System.out.println("公鑰:" + RsaUtil.getPublicKey());
          
                      //公鑰加密
                      byte[] ciphertext=RsaUtil.encryptByPublicKey(str.getBytes(), RsaUtil.getPublicKey());
                      //私鑰解密
                      byte[] plaintext=RsaUtil.decryptByPrivateKey(ciphertext, RsaUtil.getPrivateKey());
          
                      System.out.println("公鑰加密前:" + str);
                      System.out.println("公鑰加密后:" + Base64.encodeBase64String(ciphertext));
                      System.out.println("私鑰解密后:" + new String(plaintext));
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          私鑰:MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANG08b2L0Hk1QCJXyTUI6A4CqW+KENCedZyJCYMteZ/vx93KeYZbPShhI3IWJJtj9U+ibiAVRjzmikI9lkKdgnCaOgTmEZis2RWgLzhcOpSqdp/J6d+YtmCD6UDeO3E6QPyfVv9d3qPrqaYUCxi7CmouzVaa/cJqrfYB7qGYt3u5AgMBAAECgYBbovQX3ebFcG2MFExKLpAovyUHJo/eeb/vHTrY5aBGMWNnGbks6uW4pWn1ypNIi8+AcvwobON6bUtxUrQ8e9OpUlDYTAAqDE8JvJoRC3theHpJbkHCdDLeNnz1EizUwxfe3X3IVwEYd29C00WXt0rUW2D/Fsa7ECp08taeV+ukAQJBAOyfO8opGp8t40bbyMRVsIR2zK19rN6Kd/NGvjjW/7BPgzDJZsybcN6e0AuhFaWTyHNSonpDztEQ0VWhF0mHokECQQDi4W9xCmzQf0l8mgXUP2IDY5YtQN9g9vL51qEwpcxhHxCCcid62R0y6T2GnRTmkEpSwPYZ2EZQrKtpGiEk4wt5AkAhwwqd6sWApuSB7MQ1t2BLVkQYERGEY0+AJ7zmkU7EUmQOpv4C/b7aFODsd9yF1pNIWScTuO8eh37G8AhJlo/BAkEAuIHfME3rGlA5whQ8I1T8b4cgjWLRhrit9tI+OiLLqDwsH/mX88b3gPy/pWa/pZW4a74zJeeFn3wc1heC1s2x+QJAXHVf9fZaFwDlD6nD3x0Sgu8Mdp8tsfdz2wIkvjtANc+eojkfxwdZd6PKWgmiPTLKNNqbPaLgtU74WVAnlpSgsw==公鑰:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRtPG9i9B5NUAiV8k1COgOAqlvihDQnnWciQmDLXmf78fdynmGWz0oYSNyFiSbY/VPom4gFUY85opCPZZCnYJwmjoE5hGYrNkVoC84XDqUqnafyenfmLZgg+lA3jtxOkD8n1b/Xd6j66mmFAsYuwpqLs1Wmv3Caq32Ae6hmLd7uQIDAQAB
          公鑰加密前:huanzi.qch@qq.com:歡子
          公鑰加密后:MQa65DyVZg/L8SBilLX1yUiajtiTBqUFpQ/qlrSRyMGCubylbp9KisowRghPxk9BuI3+ea/4QpidIZKJaZAbQQ+ZKyslSTk3nm6H+0BF9pMA7BUeC33xHSy+3lJrNOr5S+Vup1Oir3Nu8i2vJYQV1pPkB5+zyUVEcNLD3xr/eNQ=私鑰解密后:huanzi.qch@qq.com:歡子


            2、復雜對象的RSA公鑰加密、私鑰解密

          public static void main(String[] args) {
                  //復雜對象
                  ImsUserVo userVo=new ImsUserVo();
                  userVo.setUserName("123456");
                  userVo.setPassword("111111");
                  try {
                      System.out.println("私鑰:" + RsaUtil.getPrivateKey());
                      System.out.println("公鑰:" + RsaUtil.getPublicKey());
          
                      //公鑰加密
                      byte[] ciphertext=RsaUtil.encryptByPublicKey(userVo.toString().getBytes(), RsaUtil.getPublicKey());
                      //私鑰解密
                      byte[] plaintext=RsaUtil.decryptByPrivateKey(ciphertext, RsaUtil.getPrivateKey());
          
                      System.out.println("公鑰加密前:" + userVo.toString());
                      System.out.println("公鑰加密后:" + Base64.encodeBase64String(ciphertext));
                      System.out.println("私鑰解密后:" + new String(plaintext));
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          私鑰:MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAL6gSKs2G4iFrhPo0aLELfGzsCAaB5hztvclD9J2hZT2KXfs6S5JwZ0RWRR28rqHm0e2RNW3fzYyOLvSoq93n/TRAkmXBbVia3BCTrSzLPrKFY8JvLyXqbrV0NrxywY+4ZlgR5R+scWaj3LtUR63sSXb5ddmOg9XctrWBGvsKrNJAgMBAAECgYEAoql9OPPDzNxdbcnGUQDcP5pYGRx9DL75Cq2KccoHNNRVEGuNkp0HZLLv84GIoFikzS2gUUnyeFmkhck4X0hRqYpCo9DwRsBgBpqn+4ebjSu4bd3lG5KCAtMaPC5sAbznY1uuuJnUdul3p9PuF7AmFTsoFFB4YvstvkRna5ZPFA0CQQDfpxPYVpZjOsgng7187vEpFa9vlQxmyamvJ2iAeFLRHCqJwlq4VYqJkgr08SE1XCBqSVhXkLyIPAtdeqxU0iFLAkEA2jJfKVSy4I/BHmk/rdpw7InQ3ERBc/a09t2ZiI3bqtnobTIf/sMZEWPeMkY83RrWL9ZQvMNDa843cans3bm1OwJAIdipGi5QaAf3TnOTc5q9iFgtypcl31BZi5ZNLFQJRHgcv+hXzlmzs4oUemkbe3XLugoLgoT24y8jESyFc/iw7QJBAJdR26EENlF6IIoAn8Ln/Oxt30UCqQnNDE8v+2wyRSdFm+Uun/XEQ7xFsDDZeRg1pljinndqS3WWO+k92SEjy0UCQQCr5UsIMBAjpGCYXeXrRWYoYdfI6+R20I+uWoOGzoly+KK4ixqMLFuimEwrmXhYnJMzvVHfbLsoogBv9NOP9ffH
          公鑰:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+oEirNhuIha4T6NGixC3xs7AgGgeYc7b3JQ/SdoWU9il37OkuScGdEVkUdvK6h5tHtkTVt382Mji70qKvd5/00QJJlwW1YmtwQk60syz6yhWPCby8l6m61dDa8csGPuGZYEeUfrHFmo9y7VEet7El2+XXZjoPV3La1gRr7CqzSQIDAQAB
          公鑰加密前:ImsUserVo(id=null, userName=123456, password=111111, nickName=null, gender=null, avatar=null, email=null, phone=null, sign=null, createdTime=null, updataTime=null)
          公鑰加密后:Un+1m/CbpzVkkxYrwNOWyEXqpsawxcdv4p3G+9b+SQRiC/THL8YG+IvqFCHnxizzYGB9LEvLbQxw72JB0Wlo1+/SvX7AJb2h0ddpvVUkPjmtXNo073SV1zMK+9NTCJUMMoHu/TIptxRbVxlBoGMHa+jq8h2y3RUOPtx/9zhBWlQmzZEifv0MjgAhKX5ucExYfXctcAVGHL959+TwKqKQmTENw5o0ElksaA0KIF+4L7RvpWVSqZT1Y4O2gMP9ALjamCx6ziRcmk4b4Q5Goph0nmw6nA387qVi3Vz6rQHrIpL0HT5OSiz1O7+2L3N0Him2IZeAgg3EZCi5xTGl54jGEw==私鑰解密后:ImsUserVo(id=null, userName=123456, password=111111, nickName=null, gender=null, avatar=null, email=null, phone=null, sign=null, createdTime=null, updataTime=null)


            如需使用更多加密、填充方式,引入

          <dependency>
              <groupId>org.bouncycastle</groupId>
              <artifactId>bcprov-jdk16</artifactId>
              <version>1.46</version>
          </dependency>


            加解密的時候改成

          Cipher cipher=Cipher.getInstance(ALGORITHMS ,new BouncyCastleProvider());


            重要更新

            2020-05-21更新

            重要Bug修復:后端加解密中,不能在代碼里new BouncyCastleProvider(),JceSecurity. getVerificationResult內部會進行判斷,如果是新值,則每次都會put到map中,導致內存緩便被耗盡,程序假死崩潰(參考博客:https://www.bbsmax.com/A/lk5aQo7451/ )


            應該改成,我已經在開源項目改了,博客上面之前貼出來的代碼我就不改了,具體代碼大家去開源項目查看看吧



            前端加、解密

            AES我們采用CryptoJS,是一個標準和安全加密算法的JavaScript庫,它的AES加密支持AES-128、AES-192和AES-256。下載或查看詳情介紹請戳官網地址

            GitHub地址:https://github.com/brix/crypto-js

            官網地址:https://code.google.com/archive/p/crypto-js/


            RSA我們采用JSEncrypt,它是一個很好用的RSA加密算法的JavaScript庫,使用PKCS#1進行填充,加解密使用方式很簡單,具體的介紹或者下載請移步官網

            GitHub地址:https://github.com/travist/jsencrypt

            官網地址:http://travistidwell.com/jsencrypt/

            下載下來后我們在項目頭部head.html引入,并新建兩個小工具類


            AES

          aesUtil

           /**
           * 簡單封裝一下
           */
          var aesUtil={
          
              //獲取key,
              genKey : function (length=16) {
                  let random="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
                  let str="";
                  for (let i=0; i < length; i++) {
                      str=str + random.charAt(Math.random() * random.length)
                  }
                  return str;
              },
          
              //加密
              encrypt : function (plaintext,key) {
                  if (plaintext instanceof Object) {
                      //JSON.stringify
                      plaintext=JSON.stringify(plaintext)
                  }
                  let encrypted=CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(plaintext), CryptoJS.enc.Utf8.parse(key), {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
                  return encrypted.toString();
              },
          
              //解密
              decrypt : function (ciphertext,key) {
                  let decrypt=CryptoJS.AES.decrypt(ciphertext, CryptoJS.enc.Utf8.parse(key), {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
                  let decString=CryptoJS.enc.Utf8.stringify(decrypt).toString();
                  if(decString.charAt(0)==="{" || decString.charAt(0)==="[" ){
                      //JSON.parse
                      decString=JSON.parse(decString);
                  }
                  return decString;
              }
          };

            RSA

          rsaUtil

           /**
           * 簡單封裝一下
           */
          var rsaUtil={
              //RSA 位數,這里要跟后端對應
              bits: 1024,
          
              //當前JSEncrypted對象
              thisKeyPair: {},
          
              //生成密鑰對(公鑰和私鑰)
              genKeyPair: function (bits=rsaUtil.bits) {
                  let genKeyPair={};
                  rsaUtil.thisKeyPair=new JSEncrypt({default_key_size: bits});
          
                  //獲取私鑰
                  genKeyPair.privateKey=rsaUtil.thisKeyPair.getPrivateKey();
          
                  //獲取公鑰
                  genKeyPair.publicKey=rsaUtil.thisKeyPair.getPublicKey();
          
                  return genKeyPair;
              },
          
              //公鑰加密
              encrypt: function (plaintext, publicKey) {
                  if (plaintext instanceof Object) {
                      //1、JSON.stringify
                      plaintext=JSON.stringify(plaintext)
                  }
                  publicKey && rsaUtil.thisKeyPair.setPublicKey(publicKey);
                  return rsaUtil.thisKeyPair.encrypt(plaintext);
              },
          
              //私鑰解密
              decrypt: function (ciphertext, privateKey) {
                  privateKey && rsaUtil.thisKeyPair.setPrivateKey(privateKey);
                  let decString=rsaUtil.thisKeyPair.decrypt(ciphertext);
                  if(decString.charAt(0)==="{" || decString.charAt(0)==="[" ){
                      //JSON.parse
                      decString=JSON.parse(decString);
                  }
                  return decString;
              }
          };

            簡單測試

            AES對稱加密、解密簡單測試

            1、字符串

          //字符串
          let text="huanzi.qch@qq.com:歡子";
          
          //key
          let genKey=aesUtil.genKey();
          //key加密
          let ciphertext=aesUtil.encrypt(text,genKey);
          //key解密
          let plaintext=aesUtil.decrypt(ciphertext,genKey);
          
          console.log("key:");console.log(genKey);
          console.log("加密前:");console.log(text);
          console.log("key加密后:" + ciphertext);
          console.log("key解密后:");console.log(plaintext);
          key:q99IsnEuryk1ZvgX
          加密前:huanzi.qch@qq.com:歡子
          key加密后:aZn58GtEj9Is0hNWbJoqpRD6RkiBVPCHOvva3Xq2PYo=key解密后:huanzi.qch@qq.com:歡子


            2、復雜對象

          //復雜對象
          let user={username: "歡子", password: 123456, remark: "abcd!@#$:"};
          
          //key
          let genKey=aesUtil.genKey();
          //key加密
          let ciphertext=aesUtil.encrypt(user,genKey);
          //key解密
          let plaintext=aesUtil.decrypt(ciphertext,genKey);
          
          console.log("key:");console.log(genKey);
          console.log("加密前:");console.log(user);
          console.log("key加密后:" + ciphertext);
          console.log("key解密后:");console.log(plaintext);
          key:e6gzizHIpDfc6hbg
          加密前:{username: "歡子", password: 123456, remark: "abcd!@#$:"}
          key加密后:YdNw5AwteEp8WZs5xMv0YiGcXvX81P9MCLOvroHjfLUyQV/GwJ6obRqi4DT2ucJy8DWrKueOzLGLSQXUVhAgIA==key解密后:{username: "歡子", password: 123456, remark: "abcd!@#$:"}

            RAS非對稱加密、解密簡單測試

            1、字符串的RSA公鑰加密、私鑰解密

          //普通字符串
          let text="huanzi.qch@qq.com:歡子";
          
          //秘鑰對
          let keyPair=rsaUtil.genKeyPair();
          //公鑰加密
          let ciphertext=rsaUtil.encrypt(text,keyPair.publicKey);
          //私鑰解密
          let plaintext=rsaUtil.decrypt(ciphertext,keyPair.privateKey);
          
          console.log("秘鑰:");console.log(keyPair.privateKey);
          console.log("公鑰:" + keyPair.publicKey);
          console.log("加密前:" + text);
          console.log("公鑰加密后:" + ciphertext);
          console.log("解密后:" + plaintext);
          秘鑰:MIICXQIBAAKBgQDtBKNg9NJ0+mMWq+99geoi32t+xkbJuvQ4Wr7x8I+zGT8xiG+jG+OAuSjvi5yA7IEMMAj8Y8vS7IPPo2mAr/PH0DsNiHMATJm8mNIEDzfP4WOFOdidzqP+6/9iOLMfe4cHtGq+kdX7QPx4uabnIXAREnR4nVl5Mtxf+vEHXGmEPwIDAQABAoGBANH92gJ85jld3YyoqHa6M4bSC5s2cGEqklWbkLEqQSacp7BrAP2yJ85UPkB9oRtYbr0tkciLYnptshq03TR2r7QT5+ovb5KJ2MQExTXk8GZTO/5sSqD0zwA9SESlAmWj8yc49p5Tk0h5UYFgsRATTer1n1llziryXa4QMiIfKrmBAkEA+za3DryZnMyNqre6Kx+FYsFVvIbHRU7tJ5LOiZ43Vl0DXq44zmeNQeh6MzfH6sc0Avu1c61/+KNDVf23yfVt7QJBAPGIr0GokOZ+L0sttiEoQSq/dBdYaSCBfTht+rA/9ie8RgcFJkYj4h/6RPzdYIRWDco5RzI+oPnZmFC4rfPjFVsCQHE2XFMw3c2TRfj86dKLVxKFbL0UxHNQuYIPIDNW8TtjmaQuwf0LH9bnDUNNzTPaaG87vq+OLlEAStVTDWPfzpUCQDVJvbjTstxXfKGufR9FnVMMGFXKOK9mQjU/9m4KPom3vQ9xcGdLJWl+stfDE7c+sR4rkuyf6q4U9sjgZeiH8j8CQQCfaxXfxiDJPztUDm1AKI6uDwz4P4eiYRbbbQ5x+iQSunHbq0Y7U9UUkWcLw0xDhReHEYkFuOeiBj2ViAPJz1r0
          公鑰:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDtBKNg9NJ0+mMWq+99geoi32t+xkbJuvQ4Wr7x8I+zGT8xiG+jG+OAuSjvi5yA7IEMMAj8Y8vS7IPPo2mAr/PH0DsNiHMATJm8mNIEDzfP4WOFOdidzqP+6/9iOLMfe4cHtGq+kdX7QPx4uabnIXAREnR4nVl5Mtxf+vEHXGmEPwIDAQAB
          加密前:huanzi.qch@qq.com:歡子
          公鑰加密后:aUkUMYC7lF8M1xzcx5ZEdc0DQt4FrqvWqEnD30raV++j7rwsfEcyXpPmeF1g2LR86FVG3oxgdTptorkwUDSXB3Tv4av7toGg7Zcf9l1vs5WQX7kCDTitwBVwyBNTZq22xed1J/LAkDujDav6tUJHdMmRKYVe2NeTswvWLOqWWW4=解密后:huanzi.qch@qq.com:歡子


            2、復雜對象的RSA公鑰加密、私鑰解密

          //復雜對象
          let user={username:"歡子",password:123456,remark:"abcd!@#$:"};
          
          //秘鑰對
          let keyPair=rsaUtil.genKeyPair();
          //公鑰加密
          let ciphertext=rsaUtil.encrypt(user,keyPair.publicKey);
          //私鑰解密
          let plaintext=rsaUtil.decrypt(ciphertext,keyPair.privateKey);
          
          console.log("秘鑰:");console.log(keyPair.privateKey);
          console.log("公鑰:" + keyPair.publicKey);
          console.log("加密前:" + user);
          console.log("公鑰加密后:" + ciphertext);
          console.log("解密后:" + plaintext);
          秘鑰:MIICXAIBAAKBgQCsAE5TN8kD7U4mFyxBzN1w23Rkf4K8MQ3B0bCZE5crjYp81eUtWrfUM+zLPmF9e1P/ws2yGHvL6mueU9PxtDJn5rSLsQBSxIkN0QB/nq76S4uh2Nrmmrjomejy5LqXnTVbEoIW2RTFBzyMWy4AjQY6P2pAJ8zCagvcdYcweUIqMQIDAQABAoGAbwrLhkIvjk938nNnaRufoqqrW+5OMrzgis6bWlghckawr6NPj5ZPs7nKF/Sv79jdA/N55I6V7bHrxI2N+S9Ckm2ygv8nNYimSjzspR48SqVRuH/xYmQQ9hi8Iy4dTlCMud34oXsV2sYI5tEn7f3bypOVfJa6kHSqxe1PIQTxirkCQQDjmHOp2JMcltpL+639nnNgZ2U06cRhPHX+tcGTIgoqu1Sqp0bKH7QuUF9WPNWxHrbYY5+s5jnnhTTwQZg74atTAkEAwXelo0JLYHpML7+sLs8aUzitRJXjkW3dY4JPf1wLTNLbawvi4KA/6NA3jx7kCD6KzM7vsWWsRgArrUWa1Dbn6wJARY5pAuZyh1E/I+umEBWl0zemQZaT8tekhBSONWY4zzhzNrhqtQkdau4bROLQuBHX9af0u8WcuroGJMsXOG3OiwJBALc91OPJ8cziaPC80Z/QRvXV877HXTCsZ4lNrnBJxOYxvOMp8eyhu4aOWGE1d/QbEKolwj86tq3ikXvfNmOT0ZsCQBkfviB7CKdHCrUCnpAK0upa9x8uFraomDNxFP/HwTFPSOPGhA5pgAzJgygSu2hpEwFFIwfC3E+pQ2EAhoeIcdw=公鑰:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsAE5TN8kD7U4mFyxBzN1w23Rkf4K8MQ3B0bCZE5crjYp81eUtWrfUM+zLPmF9e1P/ws2yGHvL6mueU9PxtDJn5rSLsQBSxIkN0QB/nq76S4uh2Nrmmrjomejy5LqXnTVbEoIW2RTFBzyMWy4AjQY6P2pAJ8zCagvcdYcweUIqMQIDAQAB
          加密前:[object Object]
          公鑰加密后:LwggD8SIeWoqzh4gHh/vJ9nEZsqeNZfoxgrRRPD7k0wpp9/uZmR5kfRJ8O59yW5IaOt1z50mJ26ylRBOKNoTTl7Rt4zVmBYX4EXr4Ajq3CINFcPI/j5l8yQRSIgLPUvOxhIAKmfrgNKCaLjSdjK/CnTbPrZoDArI8iAHq/ih4r8=解密后:{\"username\":\"歡子\",\"password\":123456,\"remark\":\"abcd!@#$:\"}


            聯調測試

            自己解密自己加密的的數據基本上沒有什么問題,重要的是解密對方加密的數據會不會成功,前后端相互加解密的工程中,最重要的就是保持兩邊的加密、填充方式一致、加密位數一致,還有就是后端Base64字符串轉成byte[]數組的時候要注意,Base64工具類轉跟直接字符串getByte()跟用輸入輸出流來轉,得到的數組結果有差異,在本次測試中我也是搞了好久才使得前后端一致,緊跟上面的簡單測試,接下來我們進行前后端聯調測試

            1、AES:前后端相互用對方的key解密對方加密的數據

            2、RSA:前后端相互用對方的公鑰進行加密數據,然后將數據交給對方解密

            這里要講一下步驟,不然大家看不懂下面這幾張圖,為了確保后端加密解密用的是同一個密鑰對,我們采用控制臺輸入前端秘鑰跟前端使用后端公鑰加密后的密文,然后再使用私鑰去解密從而得到前端的明文,而js前端部分,只有不刷新頁面,對象數據會存在瀏覽器內存中,確保了加密解密是用同一個對密鑰對

            總而言之,測試結果是正確的,接下來就可以在項目中進行加解密了

            項目應用

            理論思路

            前、后端的代碼都封裝好了,并且都通過了簡單測試,接下來就是應用到項目中,首先我們要解決的是生成公鑰秘鑰并交換的問題,思路如下:

            生成:

              1、后端:在項目啟動的時候生成RSA公鑰秘鑰并在整個項目運行中不發生改變(或者每隔一段時間更新一次也行),AES的key是每次響應之前隨機獲取

              2、前端:我們在訪問頁面開始生成RSA公鑰秘鑰并且希望頁面在刷新之前都不發生改變,因此將它們存在window對象中(如果需要更加健全,使用H5的本地存儲localStorage、sessionStorage)在head.html中生成,AES的key在每次發起請求之前隨機獲取

            交換:

              1、前端獲取后端RSA公鑰:前端訪問登錄頁面(網站入口),后臺返回modelAndView時注入RSA的公鑰,前端獲取用存到sessionStorage中,直到回話關閉

              2、后端獲取前端RSA公鑰:前端公鑰跟隨http請求發送到后端


            生成與交換公鑰的問題解決了,接下來就是如何傳輸AES加密后的數據RSA公鑰加密后的AES的key,思路如下:

            1、前端:重寫$.ajax方法(或者封裝一個ajax),發送數據前用AES加密數據(key隨機生成),用后端的RSA公鑰加密AES的key,將加密后的data數據、加密后的AES的key、前端RSA公鑰發送到后端;觸發回調后,先用前端RSA私鑰解密AES的key,再用明文key去解密

            2、后端:寫兩個自定義注解Encrypt、Decrypt,AOP攔截所有帶自定義注解的post請求進行加密解密,有@Encrypt需要對返回值進行加密,有@Decrypt需要對參數進行解密,加密解密過程與前端的操作同理


            前端代碼

            引入js

                   <!--CryptoJS jsencrypt -->
                  <script th:src="@{/js/cryptojs.js}"></script>
                  <script th:src="@{/js/jsencrypt.js}"></script>
                  <script th:src="@{/js/aesUtil.js}"></script>
                  <script th:src="@{/js/rsaUtil.js}"></script>


            下載CryptoJs跟jsencrypt下來發現CryptoJs需要引入很多js,因此在網上找了這個整合的js,引它就夠了

          cryptojs.js

           !function(t,n){"object"==typeof exports?module.exports=exports=n():"function"==typeof define&&define.amd?define([],n):t.CryptoJS=n()}(this,function(){var t=t||function(t,n){var i=Object.create||function(){function t(){}return function(n){var i;return t.prototype=n,i=new t,t.prototype=null,i}}(),e={},r=e.lib={},o=r.Base=function(){return{extend:function(t){var n=i(this);return t&&n.mixIn(t),n.hasOwnProperty("init")&&this.init!==n.init||(n.init=function(){n.$super.init.apply(this,arguments)}),n.init.prototype=n,n.$super=this,n},create:function(){var t=this.extend();return t.init.apply(t,arguments),t},init:function(){},mixIn:function(t){for(var n in t)t.hasOwnProperty(n)&&(this[n]=t[n]);t.hasOwnProperty("toString")&&(this.toString=t.toString)},clone:function(){return this.init.prototype.extend(this)}}}(),s=r.WordArray=o.extend({init:function(t,i){t=this.words=t||[],i!=n?this.sigBytes=i:this.sigBytes=4*t.length},toString:function(t){return(t||c).stringify(this)},concat:function(t){var n=this.words,i=t.words,e=this.sigBytes,r=t.sigBytes;if(this.clamp(),e%4)for(var o=0;o<r;o++){var s=i[o>>>2]>>>24-o%4*8&255;n[e+o>>>2]|=s<<24-(e+o)%4*8}else for(var o=0;o<r;o+=4)n[e+o>>>2]=i[o>>>2];return this.sigBytes+=r,this},clamp:function(){var n=this.words,i=this.sigBytes;n[i>>>2]&=4294967295<<32-i%4*8,n.length=t.ceil(i/4)},clone:function(){var t=o.clone.call(this);return t.words=this.words.slice(0),t},random:function(n){for(var i,e=[],r=function(n){var n=n,i=987654321,e=4294967295;return function(){i=36969*(65535&i)+(i>>16)&e,n=18e3*(65535&n)+(n>>16)&e;var r=(i<<16)+n&e;return r/=4294967296,r+=.5,r*(t.random()>.5?1:-1)}},o=0;o<n;o+=4){var a=r(4294967296*(i||t.random()));i=987654071*a(),e.push(4294967296*a()|0)}return new s.init(e,n)}}),a=e.enc={},c=a.Hex={stringify:function(t){for(var n=t.words,i=t.sigBytes,e=[],r=0;r<i;r++){var o=n[r>>>2]>>>24-r%4*8&255;e.push((o>>>4).toString(16)),e.push((15&o).toString(16))}return e.join("")},parse:function(t){for(var n=t.length,i=[],e=0;e<n;e+=2)i[e>>>3]|=parseInt(t.substr(e,2),16)<<24-e%8*4;return new s.init(i,n/2)}},u=a.Latin1={stringify:function(t){for(var n=t.words,i=t.sigBytes,e=[],r=0;r<i;r++){var o=n[r>>>2]>>>24-r%4*8&255;e.push(String.fromCharCode(o))}return e.join("")},parse:function(t){for(var n=t.length,i=[],e=0;e<n;e++)i[e>>>2]|=(255&t.charCodeAt(e))<<24-e%4*8;return new s.init(i,n)}},f=a.Utf8={stringify:function(t){try{return decodeURIComponent(escape(u.stringify(t)))}catch(t){throw new Error("Malformed UTF-8 data")}},parse:function(t){return u.parse(unescape(encodeURIComponent(t)))}},h=r.BufferedBlockAlgorithm=o.extend({reset:function(){this._data=new s.init,this._nDataBytes=0},_append:function(t){"string"==typeof t&&(t=f.parse(t)),this._data.concat(t),this._nDataBytes+=t.sigBytes},_process:function(n){var i=this._data,e=i.words,r=i.sigBytes,o=this.blockSize,a=4*o,c=r/a;c=n?t.ceil(c):t.max((0|c)-this._minBufferSize,0);var u=c*o,f=t.min(4*u,r);if(u){for(var h=0;h<u;h+=o)this._doProcessBlock(e,h);var p=e.splice(0,u);i.sigBytes-=f}return new s.init(p,f)},clone:function(){var t=o.clone.call(this);return t._data=this._data.clone(),t},_minBufferSize:0}),p=(r.Hasher=h.extend({cfg:o.extend(),init:function(t){this.cfg=this.cfg.extend(t),this.reset()},reset:function(){h.reset.call(this),this._doReset()},update:function(t){return this._append(t),this._process(),this},finalize:function(t){t&&this._append(t);var n=this._doFinalize();return n},blockSize:16,_createHelper:function(t){return function(n,i){return new t.init(i).finalize(n)}},_createHmacHelper:function(t){return function(n,i){return new p.HMAC.init(t,i).finalize(n)}}}),e.algo={});return e}(Math);return t});
          //# sourceMappingURL=core.min.js.map
          !function(e,t,i){"object"==typeof exports?module.exports=exports=t(require("./core.min"),require("./sha1.min"),require("./hmac.min")):"function"==typeof define&&define.amd?define(["./core.min","./sha1.min","./hmac.min"],t):t(e.CryptoJS)}(this,function(e){return function(){var t=e,i=t.lib,r=i.Base,n=i.WordArray,o=t.algo,a=o.MD5,c=o.EvpKDF=r.extend({cfg:r.extend({keySize:4,hasher:a,iterations:1}),init:function(e){this.cfg=this.cfg.extend(e)},compute:function(e,t){for(var i=this.cfg,r=i.hasher.create(),o=n.create(),a=o.words,c=i.keySize,f=i.iterations;a.length<c;){s&&r.update(s);var s=r.update(e).finalize(t);r.reset();for(var u=1;u<f;u++)s=r.finalize(s),r.reset();o.concat(s)}return o.sigBytes=4*c,o}});t.EvpKDF=function(e,t,i){return c.create(i).compute(e,t)}}(),e.EvpKDF});
          //# sourceMappingURL=evpkdf.min.js.map
          !function(r,e){"object"==typeof exports?module.exports=exports=e(require("./core.min")):"function"==typeof define&&define.amd?define(["./core.min"],e):e(r.CryptoJS)}(this,function(r){return function(){function e(r,e,t){for(var n=[],i=0,o=0;o<e;o++)if(o%4){var f=t[r.charCodeAt(o-1)]<<o%4*2,c=t[r.charCodeAt(o)]>>>6-o%4*2;n[i>>>2]|=(f|c)<<24-i%4*8,i++}return a.create(n,i)}var t=r,n=t.lib,a=n.WordArray,i=t.enc;i.Base64={stringify:function(r){var e=r.words,t=r.sigBytes,n=this._map;r.clamp();for(var a=[],i=0;i<t;i+=3)for(var o=e[i>>>2]>>>24-i%4*8&255,f=e[i+1>>>2]>>>24-(i+1)%4*8&255,c=e[i+2>>>2]>>>24-(i+2)%4*8&255,s=o<<16|f<<8|c,h=0;h<4&&i+.75*h<t;h++)a.push(n.charAt(s>>>6*(3-h)&63));var p=n.charAt(64);if(p)for(;a.length%4;)a.push(p);return a.join("")},parse:function(r){var t=r.length,n=this._map,a=this._reverseMap;if(!a){a=this._reverseMap=[];for(var i=0;i<n.length;i++)a[n.charCodeAt(i)]=i}var o=n.charAt(64);if(o){var f=r.indexOf(o);f!==-1&&(t=f)}return e(r,t,a)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}}(),r.enc.Base64});
          //# sourceMappingURL=enc-base64.min.js.map
          !function(e,t,r){"object"==typeof exports?module.exports=exports=t(require("./core.min"),require("./evpkdf.min")):"function"==typeof define&&define.amd?define(["./core.min","./evpkdf.min"],t):t(e.CryptoJS)}(this,function(e){e.lib.Cipher||function(t){var r=e,i=r.lib,n=i.Base,c=i.WordArray,o=i.BufferedBlockAlgorithm,s=r.enc,a=(s.Utf8,s.Base64),f=r.algo,p=f.EvpKDF,d=i.Cipher=o.extend({cfg:n.extend(),createEncryptor:function(e,t){return this.create(this._ENC_XFORM_MODE,e,t)},createDecryptor:function(e,t){return this.create(this._DEC_XFORM_MODE,e,t)},init:function(e,t,r){this.cfg=this.cfg.extend(r),this._xformMode=e,this._key=t,this.reset()},reset:function(){o.reset.call(this),this._doReset()},process:function(e){return this._append(e),this._process()},finalize:function(e){e&&this._append(e);var t=this._doFinalize();return t},keySize:4,ivSize:4,_ENC_XFORM_MODE:1,_DEC_XFORM_MODE:2,_createHelper:function(){function e(e){return"string"==typeof e?B:x}return function(t){return{encrypt:function(r,i,n){return e(i).encrypt(t,r,i,n)},decrypt:function(r,i,n){return e(i).decrypt(t,r,i,n)}}}}()}),h=(i.StreamCipher=d.extend({_doFinalize:function(){var e=this._process(!0);return e},blockSize:1}),r.mode={}),u=i.BlockCipherMode=n.extend({createEncryptor:function(e,t){return this.Encryptor.create(e,t)},createDecryptor:function(e,t){return this.Decryptor.create(e,t)},init:function(e,t){this._cipher=e,this._iv=t}}),l=h.CBC=function(){function e(e,r,i){var n=this._iv;if(n){var c=n;this._iv=t}else var c=this._prevBlock;for(var o=0;o<i;o++)e[r+o]^=c[o]}var r=u.extend();return r.Encryptor=r.extend({processBlock:function(t,r){var i=this._cipher,n=i.blockSize;e.call(this,t,r,n),i.encryptBlock(t,r),this._prevBlock=t.slice(r,r+n)}}),r.Decryptor=r.extend({processBlock:function(t,r){var i=this._cipher,n=i.blockSize,c=t.slice(r,r+n);i.decryptBlock(t,r),e.call(this,t,r,n),this._prevBlock=c}}),r}(),_=r.pad={},v=_.Pkcs7={pad:function(e,t){for(var r=4*t,i=r-e.sigBytes%r,n=i<<24|i<<16|i<<8|i,o=[],s=0;s<i;s+=4)o.push(n);var a=c.create(o,i);e.concat(a)},unpad:function(e){var t=255&e.words[e.sigBytes-1>>>2];e.sigBytes-=t}},y=(i.BlockCipher=d.extend({cfg:d.cfg.extend({mode:l,padding:v}),reset:function(){d.reset.call(this);var e=this.cfg,t=e.iv,r=e.mode;if(this._xformMode==this._ENC_XFORM_MODE)var i=r.createEncryptor;else{var i=r.createDecryptor;this._minBufferSize=1}this._mode&&this._mode.__creator==i?this._mode.init(this,t&&t.words):(this._mode=i.call(r,this,t&&t.words),this._mode.__creator=i)},_doProcessBlock:function(e,t){this._mode.processBlock(e,t)},_doFinalize:function(){var e=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){e.pad(this._data,this.blockSize);var t=this._process(!0)}else{var t=this._process(!0);e.unpad(t)}return t},blockSize:4}),i.CipherParams=n.extend({init:function(e){this.mixIn(e)},toString:function(e){return(e||this.formatter).stringify(this)}})),m=r.format={},k=m.OpenSSL={stringify:function(e){var t=e.ciphertext,r=e.salt;if(r)var i=c.create([1398893684,1701076831]).concat(r).concat(t);else var i=t;return i.toString(a)},parse:function(e){var t=a.parse(e),r=t.words;if(1398893684==r[0]&&1701076831==r[1]){var i=c.create(r.slice(2,4));r.splice(0,4),t.sigBytes-=16}return y.create({ciphertext:t,salt:i})}},x=i.SerializableCipher=n.extend({cfg:n.extend({format:k}),encrypt:function(e,t,r,i){i=this.cfg.extend(i);var n=e.createEncryptor(r,i),c=n.finalize(t),o=n.cfg;return y.create({ciphertext:c,key:r,iv:o.iv,algorithm:e,mode:o.mode,padding:o.padding,blockSize:e.blockSize,formatter:i.format})},decrypt:function(e,t,r,i){i=this.cfg.extend(i),t=this._parse(t,i.format);var n=e.createDecryptor(r,i).finalize(t.ciphertext);return n},_parse:function(e,t){return"string"==typeof e?t.parse(e,this):e}}),g=r.kdf={},S=g.OpenSSL={execute:function(e,t,r,i){i||(i=c.random(8));var n=p.create({keySize:t+r}).compute(e,i),o=c.create(n.words.slice(t),4*r);return n.sigBytes=4*t,y.create({key:n,iv:o,salt:i})}},B=i.PasswordBasedCipher=x.extend({cfg:x.cfg.extend({kdf:S}),encrypt:function(e,t,r,i){i=this.cfg.extend(i);var n=i.kdf.execute(r,e.keySize,e.ivSize);i.iv=n.iv;var c=x.encrypt.call(this,e,t,n.key,i);return c.mixIn(n),c},decrypt:function(e,t,r,i){i=this.cfg.extend(i),t=this._parse(t,i.format);var n=i.kdf.execute(r,e.keySize,e.ivSize,t.salt);i.iv=n.iv;var c=x.decrypt.call(this,e,t,n.key,i);return c}})}()});
          //# sourceMappingURL=cipher-core.min.js.map
          !function(e,i){"object"==typeof exports?module.exports=exports=i(require("./core.min")):"function"==typeof define&&define.amd?define(["./core.min"],i):i(e.CryptoJS)}(this,function(e){!function(){var i=e,t=i.lib,n=t.Base,s=i.enc,r=s.Utf8,o=i.algo;o.HMAC=n.extend({init:function(e,i){e=this._hasher=new e.init,"string"==typeof i&&(i=r.parse(i));var t=e.blockSize,n=4*t;i.sigBytes>n&&(i=e.finalize(i)),i.clamp();for(var s=this._oKey=i.clone(),o=this._iKey=i.clone(),a=s.words,f=o.words,c=0;c<t;c++)a[c]^=1549556828,f[c]^=909522486;s.sigBytes=o.sigBytes=n,this.reset()},reset:function(){var e=this._hasher;e.reset(),e.update(this._iKey)},update:function(e){return this._hasher.update(e),this},finalize:function(e){var i=this._hasher,t=i.finalize(e);i.reset();var n=i.finalize(this._oKey.clone().concat(t));return n}})}()});
          //# sourceMappingURL=hmac.min.js.map
          !function(e,o,r){"object"==typeof exports?module.exports=exports=o(require("./core.min"),require("./cipher-core.min")):"function"==typeof define&&define.amd?define(["./core.min","./cipher-core.min"],o):o(e.CryptoJS)}(this,function(e){return e.mode.ECB=function(){var o=e.lib.BlockCipherMode.extend();return o.Encryptor=o.extend({processBlock:function(e,o){this._cipher.encryptBlock(e,o)}}),o.Decryptor=o.extend({processBlock:function(e,o){this._cipher.decryptBlock(e,o)}}),o}(),e.mode.ECB});
          //# sourceMappingURL=mode-ecb.min.js.map
          !function(e,r,i){"object"==typeof exports?module.exports=exports=r(require("./core.min"),require("./cipher-core.min")):"function"==typeof define&&define.amd?define(["./core.min","./cipher-core.min"],r):r(e.CryptoJS)}(this,function(e){return e.pad.Pkcs7});
          //# sourceMappingURL=pad-pkcs7.min.js.map
          !function(e,r,i){"object"==typeof exports?module.exports=exports=r(require("./core.min"),require("./enc-base64.min"),require("./md5.min"),require("./evpkdf.min"),require("./cipher-core.min")):"function"==typeof define&&define.amd?define(["./core.min","./enc-base64.min","./md5.min","./evpkdf.min","./cipher-core.min"],r):r(e.CryptoJS)}(this,function(e){return function(){var r=e,i=r.lib,n=i.BlockCipher,o=r.algo,t=[],c=[],s=[],f=[],a=[],d=[],u=[],v=[],h=[],y=[];!function(){for(var e=[],r=0;r<256;r++)r<128?e[r]=r<<1:e[r]=r<<1^283;for(var i=0,n=0,r=0;r<256;r++){var o=n^n<<1^n<<2^n<<3^n<<4;o=o>>>8^255&o^99,t[i]=o,c[o]=i;var p=e[i],l=e[p],_=e[l],k=257*e[o]^16843008*o;s[i]=k<<24|k>>>8,f[i]=k<<16|k>>>16,a[i]=k<<8|k>>>24,d[i]=k;var k=16843009*_^65537*l^257*p^16843008*i;u[o]=k<<24|k>>>8,v[o]=k<<16|k>>>16,h[o]=k<<8|k>>>24,y[o]=k,i?(i=p^e[e[e[_^p]]],n^=e[e[n]]):i=n=1}}();var p=[0,1,2,4,8,16,32,64,128,27,54],l=o.AES=n.extend({_doReset:function(){if(!this._nRounds||this._keyPriorReset!==this._key){for(var e=this._keyPriorReset=this._key,r=e.words,i=e.sigBytes/4,n=this._nRounds=i+6,o=4*(n+1),c=this._keySchedule=[],s=0;s<o;s++)if(s<i)c[s]=r[s];else{var f=c[s-1];s%i?i>6&&s%i==4&&(f=t[f>>>24]<<24|t[f>>>16&255]<<16|t[f>>>8&255]<<8|t[255&f]):(f=f<<8|f>>>24,f=t[f>>>24]<<24|t[f>>>16&255]<<16|t[f>>>8&255]<<8|t[255&f],f^=p[s/i|0]<<24),c[s]=c[s-i]^f}for(var a=this._invKeySchedule=[],d=0;d<o;d++){var s=o-d;if(d%4)var f=c[s];else var f=c[s-4];d<4||s<=4?a[d]=f:a[d]=u[t[f>>>24]]^v[t[f>>>16&255]]^h[t[f>>>8&255]]^y[t[255&f]]}}},encryptBlock:function(e,r){this._doCryptBlock(e,r,this._keySchedule,s,f,a,d,t)},decryptBlock:function(e,r){var i=e[r+1];e[r+1]=e[r+3],e[r+3]=i,this._doCryptBlock(e,r,this._invKeySchedule,u,v,h,y,c);var i=e[r+1];e[r+1]=e[r+3],e[r+3]=i},_doCryptBlock:function(e,r,i,n,o,t,c,s){for(var f=this._nRounds,a=e[r]^i[0],d=e[r+1]^i[1],u=e[r+2]^i[2],v=e[r+3]^i[3],h=4,y=1;y<f;y++){var p=n[a>>>24]^o[d>>>16&255]^t[u>>>8&255]^c[255&v]^i[h++],l=n[d>>>24]^o[u>>>16&255]^t[v>>>8&255]^c[255&a]^i[h++],_=n[u>>>24]^o[v>>>16&255]^t[a>>>8&255]^c[255&d]^i[h++],k=n[v>>>24]^o[a>>>16&255]^t[d>>>8&255]^c[255&u]^i[h++];a=p,d=l,u=_,v=k}var p=(s[a>>>24]<<24|s[d>>>16&255]<<16|s[u>>>8&255]<<8|s[255&v])^i[h++],l=(s[d>>>24]<<24|s[u>>>16&255]<<16|s[v>>>8&255]<<8|s[255&a])^i[h++],_=(s[u>>>24]<<24|s[v>>>16&255]<<16|s[a>>>8&255]<<8|s[255&d])^i[h++],k=(s[v>>>24]<<24|s[a>>>16&255]<<16|s[d>>>8&255]<<8|s[255&u])^i[h++];e[r]=p,e[r+1]=l,e[r+2]=_,e[r+3]=k},keySize:8});r.AES=n._createHelper(l)}(),e.AES});
          //# sourceMappingURL=aes.min.js.map
          !function(e,n){"object"==typeof exports?module.exports=exports=n(require("./core.min")):"function"==typeof define&&define.amd?define(["./core.min"],n):n(e.CryptoJS)}(this,function(e){return e.enc.Utf8});
          //# sourceMappingURL=enc-utf8.min.js.map


            獲取后端公鑰


          <script th:inline="javascript">
              //獲取后端RSA公鑰并存到sessionStorage
              sessionStorage.setItem('javaPublicKey', [[${publicKey}]]);
          </script>


            重寫ajax以及生成前端密鑰對,因為我們項目中大部分都是使用$.ajax方法,所有重寫它比較合適,并且我們只攔截post請求

          head.html

           <script>
                      //獲取前端RSA公鑰密碼、AES的key,并放到window
                      let genKeyPair=rsaUtil.genKeyPair();
                      window.jsPublicKey=genKeyPair.publicKey;
                      window.jsPrivateKey=genKeyPair.privateKey;
          
                      /**
                       * 重寫jquery的ajax方法
                       */
                      let _ajax=$.ajax;//首先備份下jquery的ajax方法
                      $.ajax=function (opt) {
                          //備份opt中error和success方法
                          let fn={
                              error: function (XMLHttpRequest, textStatus, errorThrown) {
                              },
                              success: function (data, textStatus) {
                              }
                          };
                          if (opt.error) {
                              fn.error=opt.error;
                          }
                          if (opt.success) {
                              fn.success=opt.success;
                          }
          
                          //加密再傳輸
                          if (opt.type.toLowerCase()==="post") {
                              let data=opt.data;
                              //發送請求之前隨機獲取AES的key
                              let aesKey=aesUtil.genKey();
                              data={
                                  data: aesUtil.encrypt(data, aesKey),//AES加密后的數據
                                  aesKey: rsaUtil.encrypt(aesKey, sessionStorage.getItem('javaPublicKey')),//后端RSA公鑰加密后的AES的key
                                  publicKey: window.jsPublicKey//前端公鑰
                              };
                              opt.data=data;
                          }
          
                          //擴展增強處理
                          let _opt=$.extend(opt, {
                              //成功回調方法增強處理
                              success: function (data, textStatus) {
                                  if (opt.type.toLowerCase()==="post") {
                                      data=aesUtil.decrypt(data.data.data, rsaUtil.decrypt(data.data.aesKey, window.jsPrivateKey));
                                  }
                                  //先獲取明文aesKey,再用明文key去解密數據
                                  fn.success(data, textStatus);
                              }
                          });
                          return _ajax(_opt);
                      };
                  </script>

            后端代碼


            兩個自定義注解


          Decrypt

          package cn.huanzi.ims.annotation;
          
          import java.lang.annotation.ElementType;
          import java.lang.annotation.Retention;
          import java.lang.annotation.RetentionPolicy;
          import java.lang.annotation.Target;
          
          @Target({ElementType.METHOD, ElementType.TYPE})
          @Retention(RetentionPolicy.RUNTIME)
          public @interface Decrypt {
          
          }

          Encrypt

          package cn.huanzi.ims.annotation;
          
          import java.lang.annotation.ElementType;
          import java.lang.annotation.Retention;
          import java.lang.annotation.RetentionPolicy;
          import java.lang.annotation.Target;
          
          @Target({ElementType.METHOD, ElementType.TYPE})
          @Retention(RetentionPolicy.RUNTIME)
          public @interface Encrypt {
          
          }

            aop掃描所有的controller,攔截帶自定義標簽的post請求,進行解密、加密再將明文設置回去(如何使用aop?請戳我之前的博客:SpringBoot系列——aop 面向切面< https://www.cnblogs.com/huanzi-qch/p/9916478.html >

            要注意的是:

            1、我們在aop只設置了第一個參數,因此controller方法需要是實體接參且第一個參數就是,所有要求,要么有一個實體Vo參數,要么沒有參數;

            2、對于返回值,需要是統一的返回值,因為我們目前是按統一的返回值設置值的,例如本例中的Result,是我們約定好的統一返回值(后續升級可以用反射來設置值);

            3、還有一個需要注意的地方,method方法必須是要public修飾的才能設置方法的形參值,private的設置不了;

            PS:2019-06-12補充,我們之前在進行jackson序列化和反序列化忘記對date進行處理,導致時間格式錯亂,現在補充一下

          SafetyAspect.java

           package cn.huanzi.ims.aspect;
          
          import cn.huanzi.ims.annotation.Decrypt;
          import cn.huanzi.ims.annotation.Encrypt;
          import cn.huanzi.ims.common.pojo.Result;
          import cn.huanzi.ims.util.AesUtil;
          import cn.huanzi.ims.util.RsaUtil;
          import com.fasterxml.jackson.databind.DeserializationFeature;
          import com.fasterxml.jackson.databind.ObjectMapper;
          import org.apache.commons.codec.binary.Base64;
          import org.aspectj.lang.ProceedingJoinPoint;
          import org.aspectj.lang.annotation.Around;
          import org.aspectj.lang.annotation.Aspect;
          import org.aspectj.lang.annotation.Pointcut;
          import org.aspectj.lang.reflect.MethodSignature;
          import org.springframework.stereotype.Component;
          import org.springframework.web.context.request.RequestContextHolder;
          import org.springframework.web.context.request.ServletRequestAttributes;
          
          import javax.servlet.http.HttpServletRequest;
          import java.lang.annotation.Annotation;
          import java.lang.reflect.Method;
          import java.text.SimpleDateFormat;
          
          /**
           * AES + RSA 加解密AOP處理
           */
          @Aspect
          @Component
          public class SafetyAspect {
          
              /**
               * Pointcut 切入點
               * 匹配cn.huanzi.ims.*.controller包下面的所有方法
               */
              @Pointcut(value="execution(public * cn.huanzi.ims.*.controller.*.*(..))")
              public void safetyAspect() {
              }
          
              /**
               * 環繞通知
               */
              @Around(value="safetyAspect()")
              public Object around(ProceedingJoinPoint pjp) {
                  try {
                      ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                      assert attributes !=null;
                      //request對象
                      HttpServletRequest request=attributes.getRequest();
          
                      //http請求方法  post get
                      String httpMethod=request.getMethod().toLowerCase();
          
                      //method方法
                      Method method=((MethodSignature) pjp.getSignature()).getMethod();
          
                      //method方法上面的注解
                      Annotation[] annotations=method.getAnnotations();
          
                      //方法的形參參數
                      Object[] args=pjp.getArgs();
          
                      //是否有@Decrypt
                      boolean hasDecrypt=false;
                      //是否有@Encrypt
                      boolean hasEncrypt=false;
                      for (Annotation annotation : annotations) {
                          if (annotation.annotationType()==Decrypt.class) {
                              hasDecrypt=true;
                          }
                          if (annotation.annotationType()==Encrypt.class) {
                              hasEncrypt=true;
                          }
                      }
          
                      //前端公鑰
                      String publicKey=null;
          
                      //jackson
                      ObjectMapper mapper=new ObjectMapper();
                      //jackson 序列化和反序列化 date處理
                      mapper.setDateFormat( new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
          
                      //執行方法之前解密,且只攔截post請求
                      if ("post".equals(httpMethod) && hasDecrypt) {
                          //AES加密后的數據
                          String data=request.getParameter("data");
                          //后端RSA公鑰加密后的AES的key
                          String aesKey=request.getParameter("aesKey");
                          //前端公鑰
                          publicKey=request.getParameter("publicKey");
          
                          System.out.println("前端公鑰:" + publicKey);
          
                          //后端私鑰解密的到AES的key
                          byte[] plaintext=RsaUtil.decryptByPrivateKey(Base64.decodeBase64(aesKey), RsaUtil.getPrivateKey());
                          aesKey=new String(plaintext);
                          System.out.println("解密出來的AES的key:" + aesKey);
          
                          //AES解密得到明文data數據
                          String decrypt=AesUtil.decrypt(data, aesKey);
                          System.out.println("解密出來的data數據:" + decrypt);
          
                          //設置到方法的形參中,目前只能設置只有一個參數的情況
                          mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
          
                          if(args.length > 0){
                              args[0]=mapper.readValue(decrypt, args[0].getClass());
                          }
                      }
          
                      //執行并替換最新形參參數   PS:這里有一個需要注意的地方,method方法必須是要public修飾的才能設置值,private的設置不了
                      Object o=pjp.proceed(args);
          
                      //返回結果之前加密
                      if (hasEncrypt) {
                          mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                          //每次響應之前隨機獲取AES的key,加密data數據
                          String key=AesUtil.getKey();
                          System.out.println("AES的key:" + key);
                          String dataString=mapper.writeValueAsString(o);
                          System.out.println("需要加密的data數據:" + dataString);
                          String data=AesUtil.encrypt(dataString, key);
          
                          //用前端的公鑰來解密AES的key,并轉成Base64
                          String aesKey=Base64.encodeBase64String(RsaUtil.encryptByPublicKey(key.getBytes(), publicKey));
          
                          //轉json字符串并轉成Object對象,設置到Result中并賦值給返回值o
                          o=Result.of(mapper.readValue("{\"data\":\"" + data + "\",\"aesKey\":\"" + aesKey + "\"}", Object.class));
                      }
          
                      //返回
                      return o;
          
                  } catch (Throwable e) {
                      System.err.println(pjp.getSignature());
                      e.printStackTrace();
                      return Result.of(null, false, "加解密異常:" + e.getMessage());
                  }
              }
          }

            在需要進行加密解密的controller方法上加自定義注解,需要加密@Encrypt,需要解密@Decrypt,兩個都有就兩個都加

          /**
               * 登錄
               */
              @PostMapping("login")
              @Decrypt
              @Encrypt
              public Result<ImsUserVo> login(ImsUserVo userVo, HttpServletResponse response)

            到這里就準備好了,可以發現除了要在需要進行加密解密的controller方法上加自定義注解,根本不需要對之前的代碼進行修改,也不影響之前的業務,當然,因為我們直接重寫了$.ajax方法,所以我們需要對所有的post請求的controller都加注解,好在我們用的是單表繼承通用common、自動生成單表基礎增、刪、改、查接口(詳情請戳:SpringBoot系列——Spring-Data-JPA(究極進化版) 自動生成單表基礎增、刪、改、查接口< https://www.cnblogs.com/huanzi-qch/p/10281773.html >),基礎的API我們只需要在common的controller加注解就好了,自定義controller跟重寫的controller也要記得加,接下來就可以把項目跑起來進行測試


            效果演示

            未加密之前的效果

            數據直接暴露在http數據包中


            AES與RSA混合加密之后的效果

            http數據包中傳輸的是密文


            解密之后才能看到明文數據

            到這里我們實現了加解密與項目的結合,如果項目已經是按照我前面說的約定的話,即插即用,不影響項目原有業務,直接可以使用這一套,把我的代碼拿過去就可以跑起來


            后記

            前后端API交互數據加密——AES與RSA混合加密完整實例先記錄到這里,后續有空再更新升級,雖然說沒有絕對的安全,但加密總比不加密的要好,整篇文章從頭看起來也沒什么,比較簡單,實際上...中間踩了很多坑,心酸血淚史就不在這里闡述了,希望這篇博客能幫到你

            2019-09-24補充:完成這個混合加密后不久,我試著將它應用在websocket中,而后就有了另一篇文章,感興趣的可以移步前往閱讀《WebSocket數據加密——AES與RSA混合加密 < https://www.cnblogs.com/huanzi-qch/p/11010153.html >


            代碼開源

            2019-09-24補充:混合加密的代碼一直嵌在ims項目里沒有整理出來,而且ims還沒完工尚未開源出來,好在前段時間我開源的一個簡單通用的后臺管理系統,Base Admin(開源一套簡單通用的后臺管理系統< https://www.cnblogs.com/huanzi-qch/p/11534203.html >),里面整合了這套API混合加密,大家前往這個項目查看這套API混合加密代碼


            代碼已經開源、托管到我的GitHub、碼云:

            GitHub:https://github.com/huanzi-qch/base-admin

            碼云:https://gitee.com/huanzi-qch/base-admin


            中間人攻擊

            什么是中間人攻擊?維基百科:https://zh.wikipedia.org/wiki/%E4%B8%AD%E9%97%B4%E4%BA%BA%E6%94%BB%E5%87%BB

            以下介紹摘自維基百科:

            中間人攻擊(英語:Man-in-the-middle attack,縮寫:MITM)在密碼學和計算機安全領域中是指攻擊者與通訊的兩端分別創建獨立的聯系,并交換其所收到的數據,使通訊的兩端認為他們正在通過一個私密的連接與對方直接對話,但事實上整個會話都被攻擊者完全控制。在中間人攻擊中,攻擊者可以攔截通訊雙方的通話并插入新的內容。在許多情況下這是很簡單的(例如,在一個未加密的Wi-Fi 無線接入點的接受范圍內的中間人攻擊者,可以將自己作為一個中間人插入這個網絡)。

            一個中間人攻擊能成功的前提條件是攻擊者能將自己偽裝成每一個參與會話的終端,并且不被其他終端識破。中間人攻擊是一個(缺乏)相互認證的攻擊。大多數的加密協議都專門加入了一些特殊的認證方法以阻止中間人攻擊。例如,SSL協議可以驗證參與通訊的一方或雙方使用的證書是否是由權威的受信任的數字證書認證機構頒發,并且能執行雙向身份認證。


            攻擊示例

            假設愛麗絲(Alice)希望與鮑伯(Bob)通信。同時,馬洛里(Mallory)希望攔截竊會話以進行竊聽并可能在某些時候傳送給鮑伯一個虛假的消息。

          首先,愛麗絲會向鮑勃索取他的公鑰。如果Bob將他的公鑰發送給Alice,并且此時馬洛里能夠攔截到這個公鑰,就可以實施中間人攻擊。馬洛里發送給愛麗絲一個偽造的消息,聲稱自己是鮑伯,并且附上了馬洛里自己的公鑰(而不是鮑伯的)。

            愛麗絲收到公鑰后相信這個公鑰是鮑伯的,于是愛麗絲將她的消息用馬洛里的公鑰(愛麗絲以為是鮑伯的)加密,并將加密后的消息回給鮑伯。馬洛里再次截獲愛麗絲回給鮑伯的消息,并使用馬洛里自己的私鑰對消息進行解密,如果馬洛里愿意,她也可以對消息進行修改,然后馬洛里使用鮑伯原先發給愛麗絲的公鑰對消息再次加密。當鮑伯收到新加密后的消息時,他會相信這是從愛麗絲那里發來的消息。

            1.愛麗絲發送給鮑伯一條消息,卻被馬洛里截獲:

          愛麗絲“嗨,鮑勃,我是愛麗絲。給我你的公鑰” --> 馬洛里 鮑勃

            2.馬洛里將這條截獲的消息轉送給鮑伯;此時鮑伯并無法分辨這條消息是否從真的愛麗絲那里發來的:

          愛麗絲 馬洛里“嗨,鮑勃,我是愛麗絲。給我你的公鑰” --> 鮑伯

            3.鮑伯回應愛麗絲的消息,并附上了他的公鑰:

          愛麗絲 馬洛里<-- [鮑伯的公鑰]-- 鮑伯

            4.馬洛里用自己的密鑰替換了消息中鮑伯的密鑰,并將消息轉發給愛麗絲,聲稱這是鮑伯的公鑰:

          愛麗絲<-- [馬洛里的公鑰]-- 馬洛里 鮑勃

            5.愛麗絲用她以為是鮑伯的公鑰加密了她的消息,以為只有鮑伯才能讀到它:

          愛麗絲“我們在公共汽車站見面!”--[使用馬洛里的公鑰加密] --> 馬洛里 鮑勃

            6.然而,由于這個消息實際上是用馬洛里的密鑰加密的,所以馬洛里可以解密它,閱讀它,并在愿意的時候修改它。他使用鮑伯的密鑰重新加密,并將重新加密后的消息轉發給鮑伯:

          愛麗絲 馬洛里“在家等我!”--[使用鮑伯的公鑰加密] --> 鮑伯

            7.鮑勃認為,這條消息是經由安全的傳輸通道從愛麗絲那里傳來的。

            這個例子顯示了愛麗絲和鮑伯需要某種方法來確定他們是真正拿到了屬于對方的公鑰,而不是拿到來自攻擊者的公鑰。否則,這類攻擊一般都是可行的,在原理上,可以針對任何使用公鑰——密鑰技術的通訊消息發起攻擊。幸運的是,有各種不同的技術可以幫助抵御MITM攻擊。

            ----------- end -----------


            單純的加密只能防監聽偷窺,不能防中間人偽裝,那么我們應該如何阻止中間人攻擊呢?SSL協議

            HTTPS SSL協議:數字證書、CA機構、數字簽名,請看大佬的這篇,通俗易懂:看完這篇文章,我奶奶都懂了https的原理< https://www.cnblogs.com/sujing/p/10927569.html >

          版權聲明

          作者:huanzi-qch

          出處:https://www.cnblogs.com/huanzi-qch

          若標題中有“轉載”字樣,則本文版權歸原作者所有。若無轉載字樣,本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,否則保留追究法律責任的權利.

          在這里我就不再一一介紹每個步驟的具體操作了,因為在爬取老版數據的時候都已經講得非常清楚了,所以在這里我只會在重點上講述這個是這么實現的,如果想要看具體步驟請先去看我的文章內容,里面有非常詳細的介紹以及是怎么找到加密js代碼和api接口。

          私信小編01即可獲取大量Python學習資料


          58同城網站分析

          58同城的數據爬取非常簡單,唯一有點難的就是字體的加密,除此之外其他的數據用xpath即可獲取。

          想爬取不同地方的直接訪問鏈接即可:


          數據在鏈接中,直接請求獲取即可。


          字體加密破解

          既然是字體加密那么就先把字體尋找出來,尋找簡單,在開發者工具中的分類找到Font,然后搜索這個鏈接進行查找。


          已經找到這個字體了,他是在請求頁面的時候返回的,然后他還是個base64的,只需要轉換一下再保存就可以了。

          請求鏈接獲取字體

          import requests
          from lxml import etree
          
          def get_data():
              url="https://bj.58.com/chuzu/?PGTID=0d200001-0000-11e9-58e6-a658f219b27c&ClickID=1"
              headers={
                  'authority': 'bj.58.com',
                  'method': 'GET',
                  'path': '/chuzu/?PGTID=0d200001-0000-11e9-58e6-a658f219b27c&ClickID=1',
                  'scheme': 'https',
                  'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
                  'accept-encoding': 'gzip, deflate, br',
                  'accept-language': 'zh-CN,zh;q=0.9',
                  'cache-control': 'max-age=0',
                  'cookie': 'f=n; commontopbar_new_city_info=1%7C%E5%8C%97%E4%BA%AC%7Cbj; commontopbar_ipcity=bj%7C%E5%8C%97%E4%BA%AC%7C0; userid360_xml=C0E9739B2022549506AFBC01231A1DAA; time_create=1606640420140; xxzl_cid=f4a781439d9247f393d0a1629bec00df; xzuid=e0e5ea78-ac5a-491b-819d-a869ab37a7a7; xxzl_deviceid=2G3xFS3qwOviMHxtC%2FVEituhpmiI%2FJ%2BAmJ08cPBulZSe7LcSgT98WgFcyNDbzMXJ; id58=c5/nfF+bz1xVS0tAA7tjAg==; 58tj_uuid=116f1ed0-7c25-477e-8887-be3602fa2389; new_uv=1; utm_source=; spm=; init_refer=https%253A%252F%252Fbj.58.com%252Fchuzu%252Fsub%252Fpn70%252F%253Fpagetype%253Dditie%2526PGTID%253D0d3090a7-0000-1b87-3e2e-c6efe8d19973%2526ClickID%253D2; wmda_uuid=13712f08f0e555f110b1b2684ce9d709; wmda_new_uuid=1; wmda_session_id_11187958619315=1604046685879-d3ad7e5f-77f6-29d7; wmda_visited_projects=%3B11187958619315; als=0; f=n; new_session=0',
                  'sec-fetch-dest': 'document',
                  'sec-fetch-mode': 'navigate',
                  'sec-fetch-site': 'none',
                  'sec-fetch-user': '?1',
                  'upgrade-insecure-requests': '1',
                  'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36'
              }
              response=requests.get(url=url, headers=headers)
              return response.text
          
          def get_font(data):
              html=etree.HTML(data)
              script=html.xpath('//script[2]/text()')[0]
              ttf=re.findall(".*?src:url\(\'data:application/font-ttf;charset=utf-8;base64,(.*?)\'\).*?",script,re.S)[0]
              with open('fangchan-secret.ttf','wb') as f:
                  f.write(base64.b64decode(ttf))
          
          if __name__=='__main__':
              data=get_data()
              get_font(data)

          解析字體字符

          import base64
          from fontTools.ttLib import TTFont
          import re
          import requests
          from lxml import etree
          
          def get_data():
              url="https://bj.58.com/chuzu/?PGTID=0d200001-0000-11e9-58e6-a658f219b27c&ClickID=1"
              headers={
                  'authority': 'bj.58.com',
                  'method': 'GET',
                  'path': '/chuzu/?PGTID=0d200001-0000-11e9-58e6-a658f219b27c&ClickID=1',
                  'scheme': 'https',
                  'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
                  'accept-encoding': 'gzip, deflate, br',
                  'accept-language': 'zh-CN,zh;q=0.9',
                  'cache-control': 'max-age=0',
                  'cookie': 'f=n; commontopbar_new_city_info=1%7C%E5%8C%97%E4%BA%AC%7Cbj; commontopbar_ipcity=bj%7C%E5%8C%97%E4%BA%AC%7C0; userid360_xml=C0E9739B2022549506AFBC01231A1DAA; time_create=1606640420140; xxzl_cid=f4a781439d9247f393d0a1629bec00df; xzuid=e0e5ea78-ac5a-491b-819d-a869ab37a7a7; xxzl_deviceid=2G3xFS3qwOviMHxtC%2FVEituhpmiI%2FJ%2BAmJ08cPBulZSe7LcSgT98WgFcyNDbzMXJ; id58=c5/nfF+bz1xVS0tAA7tjAg==; 58tj_uuid=116f1ed0-7c25-477e-8887-be3602fa2389; new_uv=1; utm_source=; spm=; init_refer=https%253A%252F%252Fbj.58.com%252Fchuzu%252Fsub%252Fpn70%252F%253Fpagetype%253Dditie%2526PGTID%253D0d3090a7-0000-1b87-3e2e-c6efe8d19973%2526ClickID%253D2; wmda_uuid=13712f08f0e555f110b1b2684ce9d709; wmda_new_uuid=1; wmda_session_id_11187958619315=1604046685879-d3ad7e5f-77f6-29d7; wmda_visited_projects=%3B11187958619315; als=0; f=n; new_session=0',
                  'sec-fetch-dest': 'document',
                  'sec-fetch-mode': 'navigate',
                  'sec-fetch-site': 'none',
                  'sec-fetch-user': '?1',
                  'upgrade-insecure-requests': '1',
                  'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36'
              }
              response=requests.get(url=url, headers=headers)
              return response.text
          
          def get_font(data):
              html=etree.HTML(data)
              script=html.xpath('//script[2]/text()')[0]
              ttf=re.findall(".*?src:url\(\'data:application/font-ttf;charset=utf-8;base64,(.*?)\'\).*?",script,re.S)[0]
              with open('fangchan-secret.ttf','wb') as f:
                  f.write(base64.b64decode(ttf))
          
          def parse_font():
              font=TTFont('fangchan-secret.ttf')
              bestcmap=font['cmap'].getBestCmap()
              newmap=dict()
              for key in bestcmap.keys():
                  value=int(re.search(r'(\d+)', bestcmap[key]).group(1)) - 1
                  key=hex(key)
                  newmap[key]=value
              print(newmap)
          
          if __name__=='__main__':
              data=get_data()
              get_font(data)
              parse_font()


          我們發現字體編號和之前的不符合,比如:
          0x9476=7,而這里的是2,這是什么原因呢?是因為他的字體是動態生成的,每次返回的數字編號對應的值都是不同的,但是不影響我們代碼的正常運行與結果。

          import base64
          from fontTools.ttLib import TTFont
          import re
          import requests
          from lxml import etree
          
          def get_data():
              url="https://bj.58.com/chuzu/?PGTID=0d200001-0000-11e9-58e6-a658f219b27c&ClickID=1"
              headers={
                  'authority': 'bj.58.com',
                  'method': 'GET',
                  'path': '/chuzu/?PGTID=0d200001-0000-11e9-58e6-a658f219b27c&ClickID=1',
                  'scheme': 'https',
                  'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
                  'accept-encoding': 'gzip, deflate, br',
                  'accept-language': 'zh-CN,zh;q=0.9',
                  'cache-control': 'max-age=0',
                  'cookie': 'f=n; commontopbar_new_city_info=1%7C%E5%8C%97%E4%BA%AC%7Cbj; commontopbar_ipcity=bj%7C%E5%8C%97%E4%BA%AC%7C0; userid360_xml=C0E9739B2022549506AFBC01231A1DAA; time_create=1606640420140; xxzl_cid=f4a781439d9247f393d0a1629bec00df; xzuid=e0e5ea78-ac5a-491b-819d-a869ab37a7a7; xxzl_deviceid=2G3xFS3qwOviMHxtC%2FVEituhpmiI%2FJ%2BAmJ08cPBulZSe7LcSgT98WgFcyNDbzMXJ; id58=c5/nfF+bz1xVS0tAA7tjAg==; 58tj_uuid=116f1ed0-7c25-477e-8887-be3602fa2389; new_uv=1; utm_source=; spm=; init_refer=https%253A%252F%252Fbj.58.com%252Fchuzu%252Fsub%252Fpn70%252F%253Fpagetype%253Dditie%2526PGTID%253D0d3090a7-0000-1b87-3e2e-c6efe8d19973%2526ClickID%253D2; wmda_uuid=13712f08f0e555f110b1b2684ce9d709; wmda_new_uuid=1; wmda_session_id_11187958619315=1604046685879-d3ad7e5f-77f6-29d7; wmda_visited_projects=%3B11187958619315; als=0; f=n; new_session=0',
                  'sec-fetch-dest': 'document',
                  'sec-fetch-mode': 'navigate',
                  'sec-fetch-site': 'none',
                  'sec-fetch-user': '?1',
                  'upgrade-insecure-requests': '1',
                  'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36'
              }
              response=requests.get(url=url, headers=headers)
              return response.text
          
          def get_font(data):
              html=etree.HTML(data)
              script=html.xpath('//script[2]/text()')[0]
              ttf=re.findall(".*?src:url\(\'data:application/font-ttf;charset=utf-8;base64,(.*?)\'\).*?",script,re.S)[0]
              with open('fangchan-secret.ttf','wb') as f:
                  f.write(base64.b64decode(ttf))
          
          def parse_font():
              font=TTFont('fangchan-secret.ttf')
              bestcmap=font['cmap'].getBestCmap()
              newmap=dict()
              for key in bestcmap.keys():
                  value=int(re.search(r'(\d+)', bestcmap[key]).group(1)) - 1
                  key=hex(key)
                  newmap[key]=value
              return newmap
          
          def parse_data(data,newmap):
              for key,value in newmap.items():
                  key_=key.replace('0x','&#x') + ';'
                  if key_ in data:
                      data=data.replace(key_,str(value))
              html=etree.HTML(data)
              house_list=html.xpath('//ul[@class="house-list"]/li')[:-1]
              for house in house_list:
                  room=house.xpath('.//p[@class="room"]/text()')[0]
                  money=house.xpath('.//b[@class="strongbox"]/text()')[0]
                  print(room,money)
          
          if __name__=='__main__':
              data=get_data()
              get_font(data)
              newmap=parse_font()
              parse_data(data,newmap)

          聲明:本文僅供學習交流使用,請勿用于商業用途,違者后果自負。


          主站蜘蛛池模板: 成人无号精品一区二区三区| 中文字幕无线码一区二区| 久久青草精品一区二区三区| 免费无码一区二区三区蜜桃| 日本成人一区二区三区| 日本片免费观看一区二区| 日韩精品午夜视频一区二区三区| 一区二区国产精品| 亚洲av无码不卡一区二区三区| 亚洲高清毛片一区二区| 一区二区三区视频观看| 国产成人无码精品一区不卡 | 亚洲高清一区二区三区电影| 精品一区二区三区四区电影| 亚洲日韩精品一区二区三区无码| 极品人妻少妇一区二区三区| 国产香蕉一区二区三区在线视频 | 国产探花在线精品一区二区 | 国产av夜夜欢一区二区三区| 在线免费视频一区二区| 精品日本一区二区三区在线观看| 91精品一区二区三区久久久久| 丝袜人妻一区二区三区网站| 国产免费av一区二区三区| 亚洲精品伦理熟女国产一区二区| 精品亚洲一区二区| 一区二区3区免费视频| 国产一区在线视频观看| 一区二区三区福利| 国产福利电影一区二区三区,日韩伦理电影在线福 | 国产av福利一区二区三巨| 人妻互换精品一区二区| 91在线视频一区| 另类一区二区三区| 国产伦精品一区二区三区无广告| 中文字幕一区二区三匹| 一区二区三区国产精品 | 国产一区二区三区小向美奈子| 亚洲av无码一区二区三区人妖| 免费av一区二区三区| 国产一区在线视频|