Files
MusicServer/app/sdk/platforms/xm/decoder.py
T
snowy-yang dfc06bf4de feat(python): 完成 Python 全量迁移——剩余平台、登录通道与公共层修复
- 新增 5sing/apple/xm/me/qt/lr 平台包与取流实现(含 xm-sign、MissEvan 签名、蜻蜓 HMAC、Apple developer token)
- 新增 wy/kg/tx/lr 登录:扫码、手机号、账密(kg 20028 风控指纹)、cookie 绑定、kg/tx 续期
- request 层:保留多值 Set-Cookie;JSON body 紧凑化(修复 kg 网关验签);直连不经系统代理(对齐 Node)
- 登录路由统一 Body(embed=True),修复扫码轮询 422;lifespan 接入登录续期调度
- ruff/format/pyright 全绿;14 平台真实联网与 HTTP 路由验证通过
2026-10-23 21:55:45 +08:00

355 lines
4.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""喜马拉雅播放地址解码(对应 sdk/xm/decoder.ts,移植 Ximalaya-Downloader-Next adapters/decoder.py)。
算法:URL-safe Base64 → 尾 16 字节为 IV,正文先查 S-Box 置换表,
再按 16 字节块与 IV 异或、按 32 字节块与固定密钥异或,还原明文 URL。
PC 桌面端(device=win)另有 AES-ECB 变体(密钥取自官方客户端 4.0.14 asar)。
"""
from __future__ import annotations
import base64
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from app.sdk.platforms.kg.util import pkcs7_unpad
#: S-Box 置换表(www2/mweb2 设备类型)
PERMUTATION_TABLE_O = [
183,
174,
108,
16,
131,
159,
250,
5,
239,
110,
193,
202,
153,
137,
251,
176,
119,
150,
47,
204,
97,
237,
1,
71,
177,
42,
88,
218,
166,
82,
87,
94,
14,
195,
69,
127,
215,
240,
225,
197,
238,
142,
123,
44,
219,
50,
190,
29,
181,
186,
169,
98,
139,
185,
152,
13,
141,
76,
6,
157,
200,
132,
182,
49,
20,
116,
136,
43,
155,
194,
101,
231,
162,
242,
151,
213,
53,
60,
26,
134,
211,
56,
28,
223,
107,
161,
199,
15,
229,
61,
96,
41,
66,
158,
254,
21,
165,
253,
103,
89,
3,
168,
40,
246,
81,
95,
58,
31,
172,
78,
99,
45,
148,
187,
222,
124,
55,
203,
235,
64,
68,
149,
180,
35,
113,
207,
118,
111,
91,
38,
247,
214,
7,
212,
209,
189,
241,
18,
115,
173,
25,
236,
121,
249,
75,
57,
216,
10,
175,
112,
234,
164,
70,
206,
198,
255,
140,
230,
12,
32,
83,
46,
245,
0,
62,
227,
72,
191,
156,
138,
248,
114,
220,
90,
84,
170,
128,
19,
24,
122,
146,
80,
39,
37,
8,
34,
22,
11,
93,
130,
63,
154,
244,
160,
144,
79,
23,
133,
92,
54,
102,
210,
65,
67,
27,
196,
201,
106,
143,
52,
74,
100,
217,
179,
48,
233,
126,
117,
184,
226,
85,
171,
167,
86,
2,
147,
17,
135,
228,
252,
105,
30,
192,
129,
178,
120,
36,
145,
51,
163,
77,
205,
73,
4,
188,
125,
232,
33,
243,
109,
224,
104,
208,
221,
59,
9,
]
XOR_KEY_A = [
204,
53,
135,
197,
39,
73,
58,
160,
79,
24,
12,
83,
180,
250,
101,
60,
206,
30,
10,
227,
36,
95,
161,
16,
135,
150,
235,
116,
242,
116,
165,
171,
]
#: PC 桌面端 playUrlList 的 AES-ECB 密钥(客户端 4.0.14 asar Gt 函数提取)
WIN_AES_KEY = bytes.fromhex("aaad3e4fd540b0f79dca95606e72bf93")
def _b64url_to_buf(text: str) -> bytes:
cleaned = text.replace("_", "/").replace("-", "+")
cleaned += "=" * (-len(cleaned) % 4)
return base64.b64decode(cleaned)
def decode_www2_url(encrypted: str) -> str:
"""www2/mweb2 加密 URL → 明文 URL。"""
if not encrypted:
return ""
if encrypted.startswith("http"):
return encrypted
decoded = _b64url_to_buf(encrypted)
if len(decoded) < 16:
raise RuntimeError("密文长度不足(缺少 IV)")
body = bytearray(decoded[:-16])
iv = decoded[-16:]
for i in range(len(body)):
body[i] = PERMUTATION_TABLE_O[body[i]]
for i in range(0, len(body), 16):
count = min(16, len(body) - i)
for j in range(count):
body[i + j] ^= iv[j]
for i in range(0, len(body), 32):
count = min(32, len(body) - i)
for j in range(count):
body[i + j] ^= XOR_KEY_A[j]
return bytes(body).decode("utf-8", errors="replace")
def decode_win_url(encrypted: str) -> str:
"""PC 桌面端(device=win)AES-ECB 加密 URL → 明文 URL(解出的是带签名付费地址)。"""
if not encrypted:
return ""
if encrypted.startswith("http"):
return encrypted
decryptor = Cipher(algorithms.AES(WIN_AES_KEY), modes.ECB()).decryptor()
plain = decryptor.update(_b64url_to_buf(encrypted)) + decryptor.finalize()
return pkcs7_unpad(plain).decode("utf-8", errors="replace")