1. 程式人生 > >58同城AES簽名介面分析

58同城AES簽名介面分析

背景:需要獲取58同城上面釋出的職位資訊,其中的包括職位的招聘要求,薪資福利,公司的資訊,招聘者的聯絡方式。(中級爬蟲的難度係數)

 

  • 職位詳情頁分析

 某個職位詳情頁的連結

https://qy.m.58.com/m_detail/29379880488200/

 

 

開啟以上鍊接並且F12進入開發者模式

 

 

 

 

我們可以看見聯絡方式需要登陸後才可以檢視。

 

登陸後,右擊滑鼠檢視頁面的原始碼,發現html頁面並沒有電話號碼,這裡初步的猜測是通過ajax來載入渲染的(一般都是這種套路)

 

 

 

 

  • 全域性搜尋分析

由上面可見聯絡方式所在的div塊是mobMsg和freecall,全域性對這兩個關鍵字做搜尋,一步一步走下去。

<div class="msgGui">
          <h3>聯絡方式</h3>
        </div>
        <dl>
          <dt>聯絡電話:</dt>
          <dd class="mobMsg">
            <div id="freecall"></div>
          </dd>
                    <dt>電子郵箱:</dt>
          <dd>[email protected]</dd>
                              <dt>公司網址:</dt>
          <dd class="bColr">
            <a href="http://http://WWW.SHSHENXINGKEMAO.COM">http://http://WWW.SHSHENXINGKEMAO.COM</a>
          </dd>
                  </dl>
</div>

  

        可惜的是,這次沒有找到第二個mobMsg或者freecall關鍵字,當然啦,不是每一次全域性搜尋都是奏效的。

 

       這裡繼續觀察其他的請求,上面也是猜測是ajax請求做渲染的,故需要將注意力移到XHR模組和JS模組。

 

 

 

 

在JS模組找到如下標識的請求,可以看見請求返回的內容有一個叫virtualNum欄位,顧名思義這個欄位的內容可能要和我們找到電話有關係。

 

請求的連結

https://zpservice.58.com/numberProtection/biz/enterprise/mBind/?uid=29379880488200&callback=jsonp_callback2

返回的內容

{"msg":"ok","code":"0","virtualNum":"1wSca13IEbrpJNlYBR3OEQ=="}

 

這次再根據virtualNum做一次全部搜尋。

 

 

 

 

這裡印證了我們上面說的ajax做請求並渲染頁面的做法,大多數的前端開發都是採用這種套路。

$.ajax({
        type: "get",
        url: "//zpservice.58.com/numberProtection/biz/enterprise/mBind/?uid=" + userId + "&callback=?",
        dataType: "jsonp",
        success: function(data) {
            switch (data.code) {
            case "0":
                insertNum(data.virtualNum);
                break;
            case "4":
                $("#freecall").html("企業未公開");
                break;
            case "2":
                $("#freecall").html('<a href="' + "//m.m.58.com/login/?path=" + window.location.href + '">登入後可檢視</a>');
                break;
            case "3":
            case "6":
                insertNum(data.virtualNum);
                break;
            case "5":
            case "1":
            default:
                console.error(data.msg);
                break
            }
        },
        error: function(err) {
            console.log(err)
        }
    })

 

由上面的JS我們可以知道前端頁面是根據後臺介面返回的結果做相應的操作,當code等於0和6的時候,就會呼叫insertNum()函式,那我們就繼續往下看看這個insertNum函式究竟在做什麼事情。

 

function insertNum(data) {
        $("#freecall").html(decrypt(data))
    }
    function decrypt(word) {
        var key = CryptoJS.enc.Utf8.parse("5749812cr3412345");
        var decrypt = CryptoJS.AES.decrypt(word, key, {
            mode: CryptoJS.mode.ECB,
            padding: CryptoJS.pad.Pkcs7
        });
        return CryptoJS.enc.Utf8.stringify(decrypt).toString()
    }

 

function insertNum(data)這個函式傳入剛才返回的virtualNum欄位的內容,對欄位的內容進行解密的操作。具體怎麼解密上面的程式碼一目瞭然。

 

關於CryptoJS請大家移步至如下傳送門:

https://cryptojs.gitbook.io/docs/
https://stackoverflow.com/questions/51005488/how-to-use-cryptojs-in-javascrip
https://github.com/brix/crypto-js

  


從其官方的介紹中:

  • CryptoJS是 標準和安全密碼演算法的JavaScript實現

  • CryptoJS是使用最佳實踐和模式在JavaScript中實現的標準安全加密演算法的不斷增長的集合。它們速度很快,並且具有一致且簡單的介面。

  • CryptoJS說到底也就是js常用的安全密碼演算法的JS實現,如果對資料安全性有考慮的前端開發人員,那麼這個庫類都需要了解並且熟練使用。

 

綜合上面的分析我們知道58同城是用CryptoJS.AES.decrypt這個方法做電話號碼的加密解密。

 

  • 後端先對原來真實的號碼做AES加密編碼

  • 前端獲取得到加密的編碼根據加密的金鑰(這個金鑰現在是5749812cr3412345,上面截圖也可以看見)在進行AES解密即可得到電話號碼的原文

 

後端的java程式碼實現

 

 

//解密電話號碼
    public String decodoTel(String html,Page page){

        if (html.contains("must be login")){       //如果提示登陸則返回fail
            return "fail";
        }
        html = html.substring(html.indexOf("{"),html.lastIndexOf("}")+1);
        JSONObject json = JSONObject.parseObject(html);
        String virtualNum = json.getString("virtualNum");
        int code = json.getInteger("code");
        String telNum = StringUtils.EMPTY;

        if (code==0||code ==6){
            try {
                telNum = AESUtil.aesDecrypt(virtualNum, "5749812cr3412345");   //decrypKey這個金鑰58現在這個階段是這個,以後可能會變
            } catch (Exception e) {
                e.printStackTrace();
                logger.error("58tongcheng decrypKey has change").tag1("58tongcheng").tag2("decrypKey change").id(page.getRequest().getTrackId()).commit();
                return  "fail";
            }
        }
        else if (code == 2){
            logger.error("58tongcheng need login").tag1("58tongcheng").tag2("need login").id(page.getRequest().getTrackId()).commit();
        }

        return telNum;
    }

 

核心AES程式碼

package com.gemdata.crawler.generic.util;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;


/**
 * AES的加密和解密*/
public class AESUtil {
    //金鑰 (需要前端和後端保持一致)
    private static final String KEY = "5749812cr3412345";    //現在這階段這個金鑰是58同城的,以後如果遇到新的一個加密解密方法,自行修改
    //演算法
    private static final String ALGORITHMSTR = "AES/ECB/PKCS5Padding";
    
    /** 
     * aes解密 
     * @param encrypt   內容 
     * @return 
     * @throws Exception 
     */  
    public static String aesDecrypt(String encrypt) {
        try {
            return aesDecrypt(encrypt, KEY);
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }  
    }  
      
    /** 
     * aes加密 
     * @param content 
     * @return 
     * @throws Exception 
     */  
    public static String aesEncrypt(String content) {
        try {
            return aesEncrypt(content, KEY);
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }  
    }  
  
    /** 
     * 將byte[]轉為各種進位制的字串 
     * @param bytes byte[] 
     * @param radix 可以轉換進位制的範圍,從Character.MIN_RADIX到Character.MAX_RADIX,超出範圍後變為10進位制 
     * @return 轉換後的字串 
     */  
    public static String binary(byte[] bytes, int radix){  
        return new BigInteger(1, bytes).toString(radix);// 這裡的1代表正數  
    }  
  
    /** 
     * base 64 encode 
     * @param bytes 待編碼的byte[] 
     * @return 編碼後的base 64 code 
     */  
    public static String base64Encode(byte[] bytes){  
        return Base64.encodeBase64String(bytes);  
    }  
  
    /** 
     * base 64 decode 
     * @param base64Code 待解碼的base 64 code 
     * @return 解碼後的byte[] 
     * @throws Exception 
     */  
    public static byte[] base64Decode(String base64Code) throws Exception{  
        //return StringUtils.isEmpty(base64Code) ? null : new BASE64Decoder().decodeBuffer(base64Code); 
        return StringUtils.isEmpty(base64Code) ? null : new Base64().decodeBase64(base64Code);  

    }  
  
      
    /** 
     * AES加密 
     * @param content 待加密的內容 
     * @param encryptKey 加密金鑰 
     * @return 加密後的byte[] 
     * @throws Exception 
     */  
    public static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception {  
        KeyGenerator kgen = KeyGenerator.getInstance("AES");  
        kgen.init(128);  
        Cipher cipher = Cipher.getInstance(ALGORITHMSTR);  
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES"));  
  
        return cipher.doFinal(content.getBytes("utf-8"));  
    }  
  
  
    /** 
     * AES加密為base 64 code 
     * @param content 待加密的內容 
     * @param encryptKey 加密金鑰 
     * @return 加密後的base 64 code 
     * @throws Exception 
     */  
    public static String aesEncrypt(String content, String encryptKey) throws Exception {  
        return base64Encode(aesEncryptToBytes(content, encryptKey));  
    }  
  
    /** 
     * AES解密 
     * @param encryptBytes 待解密的byte[] 
     * @param decryptKey 解密金鑰 
     * @return 解密後的String 
     * @throws Exception 
     */  
    public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception {  
        KeyGenerator kgen = KeyGenerator.getInstance("AES");  
        kgen.init(128);  
  
        Cipher cipher = Cipher.getInstance(ALGORITHMSTR);  
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), "AES"));  
        byte[] decryptBytes = cipher.doFinal(encryptBytes);  
        return new String(decryptBytes);  
    }  
  
  
    /** 
     * 將base 64 code AES解密 
     * @param encryptStr 待解密的base 64 code 
     * @param decryptKey 解密金鑰 
     * @return 解密後的string 
     * @throws Exception 
     */  
    public static String aesDecrypt(String encryptStr, String decryptKey) throws Exception {  
        return StringUtils.isEmpty(encryptStr) ? null : aesDecryptByBytes(base64Decode(encryptStr), decryptKey);  
    }



    //MD5摘要
    public static String MD5(String sourceStr)
{
        String result = "";
        try
        {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(sourceStr.getBytes());
            byte b[] = md.digest();
            int i;
            StringBuffer buf = new StringBuffer("");
            for (int offset = 0; offset < b.length; offset++)
            {
                i = b[offset];
                if (i < 0)
                    i += 256;
                if (i < 16)
                    buf.append("0");
                buf.append(Integer.toHexString(i));
            }
            result = buf.toString();
        } catch (NoSuchAlgorithmException e)
        {
            System.out.println(e);
        }
        return result;
    }


    
    /**
     * 測試
     */
    public static void main(String[] args) throws Exception {  
        String content = "13044154254";  
        System.out.println("加密前:" + content);  
        System.out.println("加密金鑰和解密金鑰:" + KEY);  
        String encrypt = aesEncrypt(content, KEY);  
        System.out.println("加密後:" + encrypt);
        String decrypt = aesDecrypt(encrypt, "5749812cr3412345");
        System.out.println("解密後:" + decrypt);  
    } 
}

 

 

其中AES解碼的程式碼段,參考瞭如下的連結。

 

https://www.chenwenguan.com/aes-encryption-decryption

 

最後的結果如下,直接貼上上面的程式碼執行即可。

  

 

 

 本文首發於本人的公眾號,需要轉載請把原文連結帶上

https://mp.weixin.qq.com/s?__biz=MzIyNTcwMzA5NQ==&mid=2247483852&idx=1&sn=ac3903d00679779d5457c2785e0083b3&chksm=e87ae414df0d6d024107f375eb29bff075170101cdafecb8c945e5d725447f2db262d43ee3f9&token=797684618&lang=zh_CN#rd

  

關於呼呼:會點爬蟲,會點後端,會點前端,會點逆向,會點資料分析,會點演算法,一個喜歡陳奕迅的