当前位置: 移动技术网 > IT编程>开发语言>Java > 58同城AES签名接口逆向分析

58同城AES签名接口逆向分析

2019年10月10日  | 移动技术网IT编程  | 我要评论

背景:需要获取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>456151546@qq.com</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

  

关于呼呼:会点爬虫,会点后端,会点前端,会点逆向,会点数据分析,会点算法,一个喜欢陈奕迅的

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网