逆向指标

  • 指标:有道翻译接口参数
  • 主页:https://fanyi.youdao.com/
  • 接口:https://fanyi.youdao.com/tran...
  • 逆向参数:

    • Form Data:

      • salt: 16261583727540
      • sign: 151e4b19b07ae410e0e1861a6706d30c
      • bv: 5b3e307b66a6c075d525ed231dcc8dcd

逆向过程

抓包剖析

咱们在有道翻译页面轻易输出文字,能够看到没有刷新页面,翻译后果就进去了,由此能够推断是 Ajax 加载的,关上开发者工具,抉择 XHR 过滤 Ajax 申请,能够看到有一条 URL 为 https://fanyi.youdao.com/tran... 的 POST 申请,当咱们输出“测试”的时候,他返回的数据相似于如下构造:

{    "translateResult": [        [{            "tgt": "test",            "src": "测试"        }]    ],    "errorCode": 0,    "type": "zh-CHS2en",    "smartResult": {        "entries": ["", "[试验] test\r\n", "measurement\r\n"],        "type": 1    }}

translateResult 是翻译的后果,smartResult 是智能举荐的其余翻译,那么这个 URL 就是咱们须要的翻译接口了。

因为是 POST 申请,咱们察看它的 Form Data:

  • i:待翻译的字符串;
  • from:待翻译的语言;
  • to:目标语言;
  • lts:工夫戳;
  • smartresultclientdoctypeversionkeyfrom:固定值;
  • action:实时翻译 FY_BY_REALTlME、手动点击翻译 FY_BY_CLICKBUTTION
  • saltsignbv 的值每次会扭转,须要进一步剖析。

参数逆向

saltsignbv 三个加密参数,全局搜寻任意一个,搜寻后果比拟多,顺次比照,能够发现 fanyi.min.js 文件第 8969 行左右开始,Form Data 所有的参数都齐全了,埋下断点调试一下,能够看到所有数据和最终后果统一,加密的四个参数都在 r 当中取值,跟踪 r,往上找能够看到 r = v.generateSaltSign(n);,其中 n 是输出的待翻译的字符串:

持续跟进 generateSaltSign 函数,点击跳转到 r 函数,这里能够看到要害的加密代码:

var r = function(e) {    var t = n.md5(navigator.appVersion)      , r = "" + (new Date).getTime()      , i = r + parseInt(10 * Math.random(), 10);    return {        ts: r,        bv: t,        salt: i,        sign: n.md5("fanyideskweb" + e + i + "Y2FYu%TNSbMCxc3t2u^XT")    }};

剖析这段要害加密代码:

  • navigator.appVersion 就是 UserAgent
  • bv 的值由 UserAgent 通过 MD5 加密失去
  • ts 的值为 13 位工夫戳
  • salt 的值由 ts 的值加上一个 0-9 的随机整数失去
  • sign 的值由待翻译的字符串、salt 的值和另外两个固定的字符串组成,再由 MD5 加密失去最终后果

这个过程比较简单,能够间接应用 Python 来复现:

import timeimport randomimport hashlibquery = "待翻译字符串"user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"lts = str(int(time.time() * 1000))                                # 以毫秒为单位的 13 位工夫戳salt = lts + str(random.randint(0, 9))                            # 13 位工夫戳+随机数字,生成 salt 值sign = "fanyideskweb" + query + salt + "Y2FYu%TNSbMCxc3t2u^XT"    # 拼接字符串组成 signsign = hashlib.md5(sign.encode()).hexdigest()                     # 将 sign 进行 MD5 加密,生成最终 sign 值bv = hashlib.md5(user_agent.encode()).hexdigest()                 # 对 UA 进行 MD5 加密,生成 bv 值

或者间接援用 JS,应用 nodejs 外面的加密模块 CryptoJS 来进行 MD5 加密,改写 JS 如下:

// 援用 crypto-js 加密模块var CryptoJS = require('crypto-js')function getEncryptedParams(data, ua) {    var bv = CryptoJS.MD5(ua).toString()        , lts = "" + (new Date).getTime()        , salt = lts + parseInt(10 * Math.random(), 10)    var sign = CryptoJS.MD5('fanyideskweb'+data+salt+']BjuETDhU)zqSxf-=B#7m').toString()    return {bv: bv, lts: lts, salt: salt, sign: sign}}

残缺代码

youdao_encrypt.js

获取加密参数 saltsignbv

// 援用 crypto-js 加密模块var CryptoJS = require('crypto-js')function getEncryptedParams(data, ua) {    var bv = CryptoJS.MD5(ua).toString(),        lts = "" + (new Date).getTime(),        salt = lts + parseInt(10 * Math.random(), 10)    var sign = CryptoJS.MD5('fanyideskweb' + data + salt + ']BjuETDhU)zqSxf-=B#7m').toString()    return { bv: bv, lts: lts, salt: salt, sign: sign }}// var ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"// var data = "测试"// console.log(getEncryptedParams(data, ua));

youdaofanyi.py

#!/usr/bin/env python3# -*- coding: utf-8 -*-import timeimport randomimport hashlibimport execjsimport requeststranslate_url = 'https://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule'user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'def get_translation_result(parameters):    headers = {        'User-Agent': user_agent,        'Host': 'fanyi.youdao.com',        'Origin': 'https://fanyi.youdao.com',        'Referer': 'https://fanyi.youdao.com/',        'X-Requested-With': 'XMLHttpRequest',        'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"',        'Cookie': 'OUTFOX_SEARCH_USER_ID="-1848382357@10.169.0.84"; ___rl__test__cookies=1625907853887; OUTFOX_SEARCH_USER_ID_NCOO=132978720.55854891'    }    response = requests.post(url=translate_url, headers=headers, data=parameters)    result = response.json()['translateResult'][0][0]['tgt']    return resultdef get_parameters_by_python(query, translate_from, translate_to):    lts = str(int(time.time() * 1000))                                # 以毫秒为单位的 13 位工夫戳    salt = lts + str(random.randint(0, 9))                            # 13 位工夫戳+随机数字,生成 salt 值    sign = "fanyideskweb" + query + salt + "Y2FYu%TNSbMCxc3t2u^XT"    # 拼接字符串组成 sign    sign = hashlib.md5(sign.encode()).hexdigest()                     # 将 sign 进行 MD5 加密,生成最终 sign 值    bv = hashlib.md5(user_agent.encode()).hexdigest()                 # 对 UA 进行 MD5 加密,生成 bv 值    parameters = {        'i': query,        'from': translate_from,        'to': translate_to,        'smartresult': 'dict',        'client': 'fanyideskweb',        'salt': salt,        'sign': sign,        'lts': lts,        'bv': bv,        'doctype': 'json',        'version': '2.1',        'keyfrom': 'fanyi.web',        'action': 'FY_BY_REALTlME'    }    return parametersdef get_parameters_by_javascript(query, translate_from, translate_to):    with open('youdao_encrypt.js', 'r', encoding='utf-8') as f:        youdao_js = f.read()    params = execjs.compile(youdao_js).call('get_params', query, user_agent)    # 通过 JavaScript 代码获取各个参数    bv = hashlib.md5(user_agent.encode()).hexdigest()                           # 对 UA 进行 MD5 加密,生成 bv 值    parameters = {        'i': query,        'from': translate_from,        'to': translate_to,        'smartresult': 'dict',        'client': 'fanyideskweb',        'salt': params['salt'],        'sign': params['sign'],        'lts': params['lts'],        'bv': bv,        'doctype': 'json',        'version': '2.1',        'keyfrom': 'fanyi.web',        'action': 'FY_BY_REALTlME'    }    return parametersdef main():    query = input('请输出要翻译的文字:')    # 原始语言,目标语言,默认主动解决    translate_from = translate_to = 'AUTO'    # 通过 Python 获取加密参数或者通过 JavaScript 获取参数,二选一    param = get_parameters_by_python(query, translate_from, translate_to)    # param = get_parameters_by_javascript(query, translate_from, translate_to)    result = get_translation_result(param)    print('翻译的后果为:', result)if __name__ == '__main__':    main()