Generating the sign Parameter
The sign parameter obsevred in Youdao's translate API appears as a 32-character hexadecimal string, indicative of MD5 hashing.
Trace the JavaScript logic to locate where sign is produced:
const clientId = "fanyideskweb";
const productTag = "webfanyi";
function md5Hex(input) {
return require('crypto').createHash('md5').update(String(input)).digest('hex');
}
function buildSign(ts, secret) {
const raw = `client=${clientId}&mysticTime=${ts}&product=${productTag}&key=${secret}`;
return md5Hex(raw);
}
function assembleParams(secret) {
const now = Date.now();
return { sign: buildSign(now, secret) };
}
During dynamic aanlysis, two distinct calls to buildSign occur. The second call uses a dynamic secret value (Vy4EQ1uwPkUoqvcP1nIu6WiAjxFeA3Y3) tied to the actual translation request. This must be used for reproducing the signature.
Node.js version:
const crypto = require('crypto');
const clientId = "fanyideskweb";
const productTag = "webfanyi";
const fixedSecret = "Vy4EQ1uwPkUoqvcP1nIu6WiAjxFeA3Y3";
function md5Hex(val) {
return crypto.createHash('md5').update(val).digest('hex');
}
function makeSignature(ts, sec) {
const payload = `client=${clientId}&mysticTime=${ts}&product=${productTag}&key=${sec}`;
return md5Hex(payload);
}
function produceSign() {
const moment = Date.now();
return { sign: makeSignature(moment, fixedSecret) };
}
console.log(produceSign());
Python equivalent:
import time
import hashlib
ts = str(int(time.time() * 1000))
def compute_sign():
raw = f"client=fanyideskweb&mysticTime={ts}&product=webfanyi&key=Vy4EQ1uwPkUoqvcP1nIu6WiAjxFeA3Y3"
return hashlib.md5(raw.encode()).hexdigest()
print(compute_sign())
Decrypting the Encrypted Response
The API returns encrypted text that must be decoded via AES-128-CBC using static key material.
Locate the decryption routine in the JS source:
const keyMaterial = 'ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl';
const ivMaterial = 'ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4';
function md5Bytes(str) {
return crypto.createHash('md5').update(str).digest();
}
function decipherPayload(b64Text) {
const keyBuf = Buffer.alloc(16, md5Bytes(keyMaterial));
const ivBuf = Buffer.alloc(16, md5Bytes(ivMaterial));
const decipher = crypto.createDecipheriv('aes-128-cbc', keyBuf, ivBuf);
let plain = decipher.update(b64Text, 'base64', 'utf-8');
plain += decipher.final('utf-8');
return plain;
}
// b64Text is the encrypted response
console.log(decipherPayload(b64Text));
Python implemantation:
import base64
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
def decode_response(b64_input):
key_raw = "ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl"
iv_raw = "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4"
key = hashlib.md5(key_raw.encode()).digest()
iv = hashlib.md5(iv_raw.encode()).digest()
cipher = AES.new(key, AES.MODE_CBC, iv)
raw = base64.urlsafe_b64decode(b64_input)
clear = unpad(cipher.decrypt(raw), AES.block_size)
return clear.decode()
Complete Python Workflow
import time
import requests
import base64
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
ts = str(int(time.time() * 1000))
def generate_signature():
src = f"client=fanyideskweb&mysticTime={ts}&product=webfanyi&key=Vy4EQ1uwPkUoqvcP1nIu6WiAjxFeA3Y3"
return hashlib.md5(src.encode()).hexdigest()
def fetch_encrypted(text):
hdr = {
'Referer': 'https://fanyi.youdao.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
}
payload = {
'i': text,
'from': 'en',
'to': 'zh-CHS',
'useTerm': 'false',
'domain': '0',
'dictResult': 'true',
'keyid': 'webfanyi',
'sign': generate_signature(),
'client': 'fanyideskweb',
'product': 'webfanyi',
'appVersion': '1.0.0',
'vendor': 'web',
'pointParam': 'client,mysticTime,product',
'mysticTime': ts,
'keyfrom': 'fanyi.web',
'mid': '1',
'screen': '1',
'model': '1',
'network': 'wifi',
'abtest': '0',
'yduuid': 'abcdefg',
}
resp = requests.post('https://dict.youdao.com/webtranslate', headers=hdr, data=payload).text
return resp
def decrypt_response(b64_text):
km = "ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl"
iv = "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4"
key = hashlib.md5(km.encode()).digest()
iv_bytes = hashlib.md5(iv.encode()).digest()
cipher = AES.new(key, AES.MODE_CBC, iv_bytes)
data = base64.urlsafe_b64decode(b64_text)
plain = unpad(cipher.decrypt(data), AES.block_size)
return plain.decode()
if __name__ == '__main__':
word = 'apple'
enc = fetch_encrypted(word)
print(decrypt_response(enc))