{"id":17693953,"url":"https://github.com/ogios/youdao-translater","last_synced_at":"2025-06-18T17:38:00.085Z","repository":{"id":160441641,"uuid":"635324654","full_name":"ogios/YouDao-Translater","owner":"ogios","description":"有道翻译，web端api接口，纯python，无需js，主要重写了非正常的base64解码和使用数组进行AES解密的方法","archived":false,"fork":false,"pushed_at":"2023-05-02T14:10:50.000Z","size":332,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-23T01:51:41.301Z","etag":null,"topics":["aes-128","api","base64-decoding","cbc","reverse-engineering","translator","youdao","youdao-fanyi","youdao-fanyi-api"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ogios.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-05-02T13:02:08.000Z","updated_at":"2024-06-27T02:08:54.000Z","dependencies_parsed_at":null,"dependency_job_id":"bc41e8d5-efd1-48ef-8d95-b13cf5ceb713","html_url":"https://github.com/ogios/YouDao-Translater","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ogios/YouDao-Translater","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ogios%2FYouDao-Translater","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ogios%2FYouDao-Translater/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ogios%2FYouDao-Translater/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ogios%2FYouDao-Translater/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ogios","download_url":"https://codeload.github.com/ogios/YouDao-Translater/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ogios%2FYouDao-Translater/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260600300,"owners_count":23034687,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aes-128","api","base64-decoding","cbc","reverse-engineering","translator","youdao","youdao-fanyi","youdao-fanyi-api"],"created_at":"2024-10-24T13:47:27.440Z","updated_at":"2025-06-18T17:37:55.073Z","avatar_url":"https://github.com/ogios.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# YouDao-Translater\n纯python，无需js，主要重写了非正常的base64解码和使用数组进行AES解密的方法\n\u003e 本来觉得这都是一个被做烂的玩意了，但是实际去看的时候，想要完全隔绝掉js环境还是需要下一些心思去看人家代码和找资料  \n\u003e 通过这些学到了一点内容，不过和时间成本相比还是太大了\n\n讲一下主要内容\n\u003e ps: 逆向后的加密参数，例如key，iv等都是固定的，可能随着时间变化就改变了，如果失效了大概是这个原因  \n\n`Translater.py` 主要负责接口设置与请求，`YDDecoder.py` 主要负责解密返回的数据，`lang.json` 里存放着所有语言对应的键，默认使用英文转中文\n\n## 说明\n仅限学术交流，如有冒犯等联系我删除\n\n## 接口以及参数\n关于sign之类的参数和接口这里就不再赘述，简单地跟栈就可以直接找到，没有任何难度，如果是小白可以去b站上找一下教程，这个案例的教程很多\n\n## js解密\n一句话 `crypto.createCipheriv(\"aes-128-cbc\", key, iv);` 就完事了，key与iv都是通过md5加密输出digest()得到的一串uint8序列  \n下面将对这个方法的内部实现聊上几句，以便手动实现python版本的解码与解密\n\n## base64解码\n**返回的数据并非使用常规的base64所加密**，可以尝试使用下面python或js验证:\n```python\nimport base64\na = \"...\" #返回的数据\nb = base64.b64decode(a)\n```\n```javascript\nvar a = \"\" #返回的数据\nvar b = window.atob(a)\n```\n或许会解码成功，或许会直接报错，我这里大部分是报错的，即使解码成功，在后面的AES解密也会出错，因为解码结果和实际正确的结果完全不同(至少我这里是这样的)\n\n在nodejs中，存在一个叫 `Buffer.from()` 的方法，该方法可以传入字符串并以合适的格式解码返回  \n这其中就支持base64，它与正常的base64加解密不同(下面我说的内容或许并不正确，实在找不到确切的资料)  \n根据网上找的资料来看，python的base64与js的atob这类方法会自动根据规则在原数据上删除或者添加内容以便解密，具体情况看下面\n![atob报错](https://raw.githubusercontent.com/ogios/YouDao-Translater/main/example_pics/atob%E6%8A%A5%E9%94%99.jpg)\n\n使用自定义的或者Buffer.from就可以成功解码并解密\n\n![成功解密](https://github.com/ogios/YouDao-Translater/blob/main/example_pics/js%E8%A7%A3%E5%AF%86%E6%88%90%E5%8A%9F.jpg)\n```javascript\nvar a = \"...\" #返回的数据\nvar b = Buffer.from(a, \"base64\")\n```\n如果使用这两句，base64的解码问题就迎刃而解了，很简单吧，如果只是想简单地逆向出来一个接口，这一步就已经够了  \n但我试图尽力降低要求，可惜python中貌似并不存在类似Buffer.from直接可以解码base64的方法\n\n所幸，我直接跟进了解码的源头一步步找，下面是源码：\n```javascript\n// 初始化i (i是一个用来对照的表，其中有许多空值，只在特定位置放东西)\nfor (var r = [], i = [], s = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\", a = 0, u = s.length; a \u003c u; a++)\n    r[a] = s[a],\n    i[s.charCodeAt(a)] = a;\ni[\"-\".charCodeAt(0)] = 62,\ni[\"_\".charCodeAt(0)] = 63\n\n// 计算补位('=')的数量与字符串总长\nfunction h(t) {\n    var e = t.length;\n    if (e % 4 \u003e 0)\n        throw new Error(\"Invalid string. Length must be a multiple of 4\");\n    var n = t.indexOf(\"=\");\n    -1 === n \u0026\u0026 (n = e);\n    var r = n === e ? 0 : 4 - n % 4;\n    return [n, r]\n}\n\n// 计算解码后数据的长度\nfunction c(t, e, n) {\n    return 3 * (e + n) / 4 - n\n}\n\n// 解码的总方法\nfunction l(t) {\n    var e, n, r = h(t), s = r[0], a = r[1], u = new o(c(t, s, a)), f = 0, l = a \u003e 0 ? s - 4 : s;\n    for (n = 0; n \u003c l; n += 4)\n        e = i[t.charCodeAt(n)] \u003c\u003c 18 | i[t.charCodeAt(n + 1)] \u003c\u003c 12 | i[t.charCodeAt(n + 2)] \u003c\u003c 6 | i[t.charCodeAt(n + 3)],\n        u[f++] = e \u003e\u003e 16 \u0026 255,\n        u[f++] = e \u003e\u003e 8 \u0026 255,\n        u[f++] = 255 \u0026 e;\n    // return u\n    return 2 === a \u0026\u0026 (e = i[t.charCodeAt(n)] \u003c\u003c 2 | i[t.charCodeAt(n + 1)] \u003e\u003e 4,\n    u[f++] = 255 \u0026 e),\n    1 === a \u0026\u0026 (e = i[t.charCodeAt(n)] \u003c\u003c 10 | i[t.charCodeAt(n + 1)] \u003c\u003c 4 | i[t.charCodeAt(n + 2)] \u003e\u003e 2,\n    u[f++] = e \u003e\u003e 8 \u0026 255,\n    u[f++] = 255 \u0026 e),\n    u\n}\n```\n所幸这段代码不长，且大部分工作都只是手艺活不需要动太多的脑子改语法，下面是修改过后使用类进行包装后的代码片段\n```python\nclass BufferFromb64Decoder:\n\tdef __init__(self):\n\t\tself.i = self.initI()\n\t\n\tdef initI(self):\n\t\ts = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"\n\t\tr = [None] * len(s)\n\t\ti = [None] * 123\n\t\tfor a in range(len(s)):\n\t\t\tr[a] = s[a],\n\t\t\ti[ord(s[a])] = a\n\t\ti[ord(\"-\")] = 62\n\t\ti[ord(\"_\")] = 63\n\t\treturn i\n\t\n\tdef h(self, t: str):\n\t\te = len(t)\n\t\tif e % 4 \u003e 0:\n\t\t\traise Exception(\"Invalid string. Length must be a multiple of 4\")\n\t\tn = t.index(\"=\") if \"=\" in t else -1\n\t\tif n == -1:\n\t\t\tn = e\n\t\tif n == e:\n\t\t\tr = 0\n\t\telse:\n\t\t\tr = 4 - n % 4\n\t\treturn [n, r]\n\t\n\tdef c(self, t, e, n):\n\t\treturn int(3 * (e + n) / 4 - n)\n\t\n\tdef deocde(self, t):\n\t\tr = self.h(t)\n\t\ts = r[0]\n\t\ta = r[1]\n\t\tu = np.zeros(self.c(t, s, a), dtype=\"uint8\")\n\t\tf = 0\n\t\tif a \u003e 0:\n\t\t\tl = s - 4\n\t\telse:\n\t\t\tl = s\n\t\tn = 0\n\t\twhile n \u003c l:\n\t\t\te = self.i[ord(t[n])] \u003c\u003c 18 | self.i[ord(t[n + 1])] \u003c\u003c 12 | self.i[ord(t[n + 2])] \u003c\u003c 6 | self.i[ord(t[n + 3])]\n\t\t\tu[f] = e \u003e\u003e 16 \u0026 255\n\t\t\tf += 1\n\t\t\tu[f] = e \u003e\u003e 8 \u0026 255\n\t\t\tf += 1\n\t\t\tu[f] = 255 \u0026 e\n\t\t\tf += 1\n\t\t\tn += 4\n\t\tif a == 2:\n\t\t\te = self.i[ord(t[n])] \u003c\u003c 2 | self.i[ord(t[n + 1])] \u003e\u003e 4\n\t\t\tu[f] = 255 \u0026 e\n\t\t\tf += 1\n\t\tif a == 1:\n\t\t\te = self.i[ord(t[n])] \u003c\u003c 10 | self.i[ord(t[n + 1])] \u003c\u003c 4 | self.i[ord(t[n + 2])] \u003e\u003e 2\n\t\t\tu[f] = e \u003e\u003e 8 \u0026 255\n\t\t\tf += 1\n\t\t\tu[f] = 255 \u0026 e\n\t\t\tf += 1\n\t\treturn u\n```\n这样就已经完成了base64的解码工作\n\n## AES解密\nAES解密的部分很简单，跟着来就完事了，涉及到的代码很少  \n\nAES解密的key与iv也与平常使用的有所不同，key与iv是固定在js文件里的，但却被转换为了uint8的一串序列，但具体实现方法很简单:\n```javascript\ncrypto.createHash(\"md5\").update(text).digest()\n```\n输出长度位16的uint8序列，转为python代码如下，稍微复杂一两句话:\n```python\n\tdef getMD5(self, text: str):\n\t\treturn hashlib.md5(text.encode(\"UTF-8\"))\n\t\n\tdef toUint8(self, text):\n\t\tdig = self.getMD5(text).digest()\n\t\tbyte = bytearray(dig)\n\t\treturn np.frombuffer(byte, dtype=\"uint8\")\n```\n完事之后直接配合base64解码下来的数据进行正常的AES解密即可\n```python\n\tdef decode(self, text):\n\t\tkey = self.toUint8(self.decode_key).tobytes()\n\t\tiv = self.toUint8(self.decode_iv).tobytes()\n\t\ttext = self.b64Decoder.deocde(text)\n\t\tcipher = AES.new(key, AES.MODE_CBC, iv)\n\t\tdecrypt = cipher.decrypt(text.tobytes())\n\t\tdata = unpad(decrypt, AES.block_size).decode(\"utf-8\")\n\t\treturn data\n```\n需要注意的是，解密下来的内容是存在非正常字符的，在最后面，使用unpad去除padding即可  \n\n由于本人才疏学浅，这里面更加细致的密码学内容并未涉及到我的脑子里，所以具体原理本人并不清楚，只是根据逆向的结果配合代码和实验下来的成果所反推的\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fogios%2Fyoudao-translater","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fogios%2Fyoudao-translater","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fogios%2Fyoudao-translater/lists"}