Files
MusicServer/app/sdk/platforms/tx/qrc_decode.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

1083 lines
20 KiB
Python

"""QRC 歌词解密(移植自 sdk/tx/qrcDecode.ts)。
数据流程:hex 字符串 → 字节 → 3DES(DES-EDE3, ECB, 无填充) 解密 → zlib inflate → UTF-8 文本。
注意:QRC 用的是**非标准** DES 变体,S-box 中 S2[23]=15、S4[53]=10 两处与标准 DES
(14、1)不同。这两个值与原 C++ native 插件一致,改成标准值会导致解密结果全错。
下表逐字节对应原实现,不要「顺手修正」。
原 TS 的 inflate 用 `finishFlush: Z_SYNC_FLUSH` 容忍尾部不完整流(3DES 分组补位会产生
尾部垃圾/截断),Python 侧同样先尝试完整解压,失败后分块累积已解出部分。
"""
from __future__ import annotations
import zlib
from collections.abc import Sequence
DES_ENCRYPT = 1
DES_DECRYPT = 0
_MASK32 = 0xFFFFFFFF
_MASK8 = 0xFF
SBOX: tuple[tuple[int, ...], ...] = (
(
14,
4,
13,
1,
2,
15,
11,
8,
3,
10,
6,
12,
5,
9,
0,
7,
0,
15,
7,
4,
14,
2,
13,
1,
10,
6,
12,
11,
9,
5,
3,
8,
4,
1,
14,
8,
13,
6,
2,
11,
15,
12,
9,
7,
3,
10,
5,
0,
15,
12,
8,
2,
4,
9,
1,
7,
5,
11,
3,
14,
10,
0,
6,
13,
),
(
15,
1,
8,
14,
6,
11,
3,
4,
9,
7,
2,
13,
12,
0,
5,
10,
3,
13,
4,
7,
15,
2,
8,
15,
12,
0,
1,
10,
6,
9,
11,
5,
0,
14,
7,
11,
10,
4,
13,
1,
5,
8,
12,
6,
9,
3,
2,
15,
13,
8,
10,
1,
3,
15,
4,
2,
11,
6,
7,
12,
0,
5,
14,
9,
),
(
10,
0,
9,
14,
6,
3,
15,
5,
1,
13,
12,
7,
11,
4,
2,
8,
13,
7,
0,
9,
3,
4,
6,
10,
2,
8,
5,
14,
12,
11,
15,
1,
13,
6,
4,
9,
8,
15,
3,
0,
11,
1,
2,
12,
5,
10,
14,
7,
1,
10,
13,
0,
6,
9,
8,
7,
4,
15,
14,
3,
11,
5,
2,
12,
),
(
7,
13,
14,
3,
0,
6,
9,
10,
1,
2,
8,
5,
11,
12,
4,
15,
13,
8,
11,
5,
6,
15,
0,
3,
4,
7,
2,
12,
1,
10,
14,
9,
10,
6,
9,
0,
12,
11,
7,
13,
15,
1,
3,
14,
5,
2,
8,
4,
3,
15,
0,
6,
10,
10,
13,
8,
9,
4,
5,
11,
12,
7,
2,
14,
),
(
2,
12,
4,
1,
7,
10,
11,
6,
8,
5,
3,
15,
13,
0,
14,
9,
14,
11,
2,
12,
4,
7,
13,
1,
5,
0,
15,
10,
3,
9,
8,
6,
4,
2,
1,
11,
10,
13,
7,
8,
15,
9,
12,
5,
6,
3,
0,
14,
11,
8,
12,
7,
1,
14,
2,
13,
6,
15,
0,
9,
10,
4,
5,
3,
),
(
12,
1,
10,
15,
9,
2,
6,
8,
0,
13,
3,
4,
14,
7,
5,
11,
10,
15,
4,
2,
7,
12,
9,
5,
6,
1,
13,
14,
0,
11,
3,
8,
9,
14,
15,
5,
2,
8,
12,
3,
7,
0,
4,
10,
1,
13,
11,
6,
4,
3,
2,
12,
9,
5,
15,
10,
11,
14,
1,
7,
6,
0,
8,
13,
),
(
4,
11,
2,
14,
15,
0,
8,
13,
3,
12,
9,
7,
5,
10,
6,
1,
13,
0,
11,
7,
4,
9,
1,
10,
14,
3,
5,
12,
2,
15,
8,
6,
1,
4,
11,
13,
12,
3,
7,
14,
10,
15,
6,
8,
0,
5,
9,
2,
6,
11,
13,
8,
1,
4,
10,
7,
9,
5,
0,
15,
14,
2,
3,
12,
),
(
13,
2,
8,
4,
6,
15,
11,
1,
10,
9,
3,
14,
5,
0,
12,
7,
1,
15,
13,
8,
10,
3,
7,
4,
12,
5,
6,
11,
0,
14,
9,
2,
7,
11,
4,
1,
9,
12,
14,
2,
0,
6,
10,
13,
15,
3,
5,
8,
2,
1,
14,
7,
4,
10,
8,
13,
15,
12,
9,
0,
3,
5,
6,
11,
),
)
KEY_RND_SHIFT = (1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1)
KEY_PERM_C = (
56,
48,
40,
32,
24,
16,
8,
0,
57,
49,
41,
33,
25,
17,
9,
1,
58,
50,
42,
34,
26,
18,
10,
2,
59,
51,
43,
35,
)
KEY_PERM_D = (
62,
54,
46,
38,
30,
22,
14,
6,
61,
53,
45,
37,
29,
21,
13,
5,
60,
52,
44,
36,
28,
20,
12,
4,
27,
19,
11,
3,
)
KEY_COMPRESSION = (
13,
16,
10,
23,
0,
4,
2,
27,
14,
5,
20,
9,
22,
18,
11,
3,
25,
7,
15,
6,
26,
19,
12,
1,
40,
51,
30,
36,
46,
54,
29,
39,
50,
44,
32,
47,
43,
48,
38,
55,
33,
52,
45,
41,
49,
35,
28,
31,
)
# QRC 固定密钥:!@#)(*$%123ZXC!@!@#)(NHL
QRC_KEY = bytes(
(
0x21,
0x40,
0x23,
0x29,
0x28,
0x2A,
0x24,
0x25,
0x31,
0x32,
0x33,
0x5A,
0x58,
0x43,
0x21,
0x40,
0x21,
0x40,
0x23,
0x29,
0x28,
0x4E,
0x48,
0x4C,
)
)
Schedule = list[bytearray]
def _bitnum(buf: bytes, bit: int, shift: int) -> int:
"""按 64 位大端位序取 buf 的第 bit 位(bit: 0=MSB),左移 shift。"""
index = (bit // 32) * 4 + 3 - ((bit % 32) // 8)
return (((buf[index] >> (7 - (bit % 8))) & 1) << shift) & _MASK32
def _bitnum_intr(value: int, bit: int, shift: int) -> int:
return (((value >> (31 - bit)) & 1) << shift) & _MASK32
def _bitnum_intl(value: int, bit: int, shift: int) -> int:
return (((value << bit) & 0x80000000) & _MASK32) >> shift
def _sbox_bit(value: int) -> int:
return (value & 32) | ((value & 31) >> 1) | ((value & 1) << 4)
def _initial_permutation(block: bytes) -> tuple[int, int]:
s0 = (
_bitnum(block, 57, 31)
| _bitnum(block, 49, 30)
| _bitnum(block, 41, 29)
| _bitnum(block, 33, 28)
| _bitnum(block, 25, 27)
| _bitnum(block, 17, 26)
| _bitnum(block, 9, 25)
| _bitnum(block, 1, 24)
| _bitnum(block, 59, 23)
| _bitnum(block, 51, 22)
| _bitnum(block, 43, 21)
| _bitnum(block, 35, 20)
| _bitnum(block, 27, 19)
| _bitnum(block, 19, 18)
| _bitnum(block, 11, 17)
| _bitnum(block, 3, 16)
| _bitnum(block, 61, 15)
| _bitnum(block, 53, 14)
| _bitnum(block, 45, 13)
| _bitnum(block, 37, 12)
| _bitnum(block, 29, 11)
| _bitnum(block, 21, 10)
| _bitnum(block, 13, 9)
| _bitnum(block, 5, 8)
| _bitnum(block, 63, 7)
| _bitnum(block, 55, 6)
| _bitnum(block, 47, 5)
| _bitnum(block, 39, 4)
| _bitnum(block, 31, 3)
| _bitnum(block, 23, 2)
| _bitnum(block, 15, 1)
| _bitnum(block, 7, 0)
) & _MASK32
s1 = (
_bitnum(block, 56, 31)
| _bitnum(block, 48, 30)
| _bitnum(block, 40, 29)
| _bitnum(block, 32, 28)
| _bitnum(block, 24, 27)
| _bitnum(block, 16, 26)
| _bitnum(block, 8, 25)
| _bitnum(block, 0, 24)
| _bitnum(block, 58, 23)
| _bitnum(block, 50, 22)
| _bitnum(block, 42, 21)
| _bitnum(block, 34, 20)
| _bitnum(block, 26, 19)
| _bitnum(block, 18, 18)
| _bitnum(block, 10, 17)
| _bitnum(block, 2, 16)
| _bitnum(block, 60, 15)
| _bitnum(block, 52, 14)
| _bitnum(block, 44, 13)
| _bitnum(block, 36, 12)
| _bitnum(block, 28, 11)
| _bitnum(block, 20, 10)
| _bitnum(block, 12, 9)
| _bitnum(block, 4, 8)
| _bitnum(block, 62, 7)
| _bitnum(block, 54, 6)
| _bitnum(block, 46, 5)
| _bitnum(block, 38, 4)
| _bitnum(block, 30, 3)
| _bitnum(block, 22, 2)
| _bitnum(block, 14, 1)
| _bitnum(block, 6, 0)
) & _MASK32
return s0, s1
def _inverse_permutation(s0: int, s1: int, out: bytearray) -> None:
out[3] = (
_bitnum_intr(s1, 7, 7)
| _bitnum_intr(s0, 7, 6)
| _bitnum_intr(s1, 15, 5)
| _bitnum_intr(s0, 15, 4)
| _bitnum_intr(s1, 23, 3)
| _bitnum_intr(s0, 23, 2)
| _bitnum_intr(s1, 31, 1)
| _bitnum_intr(s0, 31, 0)
) & _MASK8
out[2] = (
_bitnum_intr(s1, 6, 7)
| _bitnum_intr(s0, 6, 6)
| _bitnum_intr(s1, 14, 5)
| _bitnum_intr(s0, 14, 4)
| _bitnum_intr(s1, 22, 3)
| _bitnum_intr(s0, 22, 2)
| _bitnum_intr(s1, 30, 1)
| _bitnum_intr(s0, 30, 0)
) & _MASK8
out[1] = (
_bitnum_intr(s1, 5, 7)
| _bitnum_intr(s0, 5, 6)
| _bitnum_intr(s1, 13, 5)
| _bitnum_intr(s0, 13, 4)
| _bitnum_intr(s1, 21, 3)
| _bitnum_intr(s0, 21, 2)
| _bitnum_intr(s1, 29, 1)
| _bitnum_intr(s0, 29, 0)
) & _MASK8
out[0] = (
_bitnum_intr(s1, 4, 7)
| _bitnum_intr(s0, 4, 6)
| _bitnum_intr(s1, 12, 5)
| _bitnum_intr(s0, 12, 4)
| _bitnum_intr(s1, 20, 3)
| _bitnum_intr(s0, 20, 2)
| _bitnum_intr(s1, 28, 1)
| _bitnum_intr(s0, 28, 0)
) & _MASK8
out[7] = (
_bitnum_intr(s1, 3, 7)
| _bitnum_intr(s0, 3, 6)
| _bitnum_intr(s1, 11, 5)
| _bitnum_intr(s0, 11, 4)
| _bitnum_intr(s1, 19, 3)
| _bitnum_intr(s0, 19, 2)
| _bitnum_intr(s1, 27, 1)
| _bitnum_intr(s0, 27, 0)
) & _MASK8
out[6] = (
_bitnum_intr(s1, 2, 7)
| _bitnum_intr(s0, 2, 6)
| _bitnum_intr(s1, 10, 5)
| _bitnum_intr(s0, 10, 4)
| _bitnum_intr(s1, 18, 3)
| _bitnum_intr(s0, 18, 2)
| _bitnum_intr(s1, 26, 1)
| _bitnum_intr(s0, 26, 0)
) & _MASK8
out[5] = (
_bitnum_intr(s1, 1, 7)
| _bitnum_intr(s0, 1, 6)
| _bitnum_intr(s1, 9, 5)
| _bitnum_intr(s0, 9, 4)
| _bitnum_intr(s1, 17, 3)
| _bitnum_intr(s0, 17, 2)
| _bitnum_intr(s1, 25, 1)
| _bitnum_intr(s0, 25, 0)
) & _MASK8
out[4] = (
_bitnum_intr(s1, 0, 7)
| _bitnum_intr(s0, 0, 6)
| _bitnum_intr(s1, 8, 5)
| _bitnum_intr(s0, 8, 4)
| _bitnum_intr(s1, 16, 3)
| _bitnum_intr(s0, 16, 2)
| _bitnum_intr(s1, 24, 1)
| _bitnum_intr(s0, 24, 0)
) & _MASK8
def _des_f(state: int, key: Sequence[int]) -> int:
"""Feistel 轮函数。"""
t1 = (
_bitnum_intl(state, 31, 0)
| ((state & 0xF0000000) >> 1)
| _bitnum_intl(state, 4, 5)
| _bitnum_intl(state, 3, 6)
| ((state & 0x0F000000) >> 3)
| _bitnum_intl(state, 8, 11)
| _bitnum_intl(state, 7, 12)
| ((state & 0x00F00000) >> 5)
| _bitnum_intl(state, 12, 17)
| _bitnum_intl(state, 11, 18)
| ((state & 0x000F0000) >> 7)
| _bitnum_intl(state, 16, 23)
) & _MASK32
t2 = (
_bitnum_intl(state, 15, 0)
| (((state & 0x0000F000) << 15) & _MASK32)
| _bitnum_intl(state, 20, 5)
| _bitnum_intl(state, 19, 6)
| (((state & 0x00000F00) << 13) & _MASK32)
| _bitnum_intl(state, 24, 11)
| _bitnum_intl(state, 23, 12)
| (((state & 0x000000F0) << 11) & _MASK32)
| _bitnum_intl(state, 28, 17)
| _bitnum_intl(state, 27, 18)
| (((state & 0x0000000F) << 9) & _MASK32)
| _bitnum_intl(state, 0, 23)
) & _MASK32
lrgstate = [
(t1 >> 24) & _MASK8,
(t1 >> 16) & _MASK8,
(t1 >> 8) & _MASK8,
(t2 >> 24) & _MASK8,
(t2 >> 16) & _MASK8,
(t2 >> 8) & _MASK8,
]
for i in range(6):
lrgstate[i] ^= key[i]
s = (
(SBOX[0][_sbox_bit(lrgstate[0] >> 2)] << 28)
| (SBOX[1][_sbox_bit(((lrgstate[0] & 0x03) << 4) | (lrgstate[1] >> 4))] << 24)
| (SBOX[2][_sbox_bit(((lrgstate[1] & 0x0F) << 2) | (lrgstate[2] >> 6))] << 20)
| (SBOX[3][_sbox_bit(lrgstate[2] & 0x3F)] << 16)
| (SBOX[4][_sbox_bit(lrgstate[3] >> 2)] << 12)
| (SBOX[5][_sbox_bit(((lrgstate[3] & 0x03) << 4) | (lrgstate[4] >> 4))] << 8)
| (SBOX[6][_sbox_bit(((lrgstate[4] & 0x0F) << 2) | (lrgstate[5] >> 6))] << 4)
| SBOX[7][_sbox_bit(lrgstate[5] & 0x3F)]
) & _MASK32
return (
_bitnum_intl(s, 15, 0)
| _bitnum_intl(s, 6, 1)
| _bitnum_intl(s, 19, 2)
| _bitnum_intl(s, 20, 3)
| _bitnum_intl(s, 28, 4)
| _bitnum_intl(s, 11, 5)
| _bitnum_intl(s, 27, 6)
| _bitnum_intl(s, 16, 7)
| _bitnum_intl(s, 0, 8)
| _bitnum_intl(s, 14, 9)
| _bitnum_intl(s, 22, 10)
| _bitnum_intl(s, 25, 11)
| _bitnum_intl(s, 4, 12)
| _bitnum_intl(s, 17, 13)
| _bitnum_intl(s, 30, 14)
| _bitnum_intl(s, 9, 15)
| _bitnum_intl(s, 1, 16)
| _bitnum_intl(s, 7, 17)
| _bitnum_intl(s, 23, 18)
| _bitnum_intl(s, 13, 19)
| _bitnum_intl(s, 31, 20)
| _bitnum_intl(s, 26, 21)
| _bitnum_intl(s, 2, 22)
| _bitnum_intl(s, 8, 23)
| _bitnum_intl(s, 18, 24)
| _bitnum_intl(s, 12, 25)
| _bitnum_intl(s, 29, 26)
| _bitnum_intl(s, 5, 27)
| _bitnum_intl(s, 21, 28)
| _bitnum_intl(s, 10, 29)
| _bitnum_intl(s, 3, 30)
| _bitnum_intl(s, 24, 31)
) & _MASK32
def _des_crypt(block: bytes, schedule: Sequence[Sequence[int]], out: bytearray) -> None:
"""单次 DES 加/解密一个 8 字节分组。"""
s0, s1 = _initial_permutation(block)
for i in range(15):
s0, s1 = s1, (_des_f(s1, schedule[i]) ^ s0) & _MASK32
s0 = (_des_f(s1, schedule[15]) ^ s0) & _MASK32
_inverse_permutation(s0, s1, out)
def _key_schedule(key: bytes, mode: int) -> Schedule:
"""生成 16 轮子密钥,每轮 6 字节。"""
schedule = [bytearray(6) for _ in range(16)]
c = 0
d = 0
for i in range(28):
c = (c | _bitnum(key, KEY_PERM_C[i], 31 - i)) & _MASK32
d = (d | _bitnum(key, KEY_PERM_D[i], 31 - i)) & _MASK32
for i in range(16):
shift = KEY_RND_SHIFT[i]
c = (((c << shift) & _MASK32) | (c >> (28 - shift))) & 0xFFFFFFF0
d = (((d << shift) & _MASK32) | (d >> (28 - shift))) & 0xFFFFFFF0
togen = 15 - i if mode == DES_DECRYPT else i
row = schedule[togen]
for j in range(24):
row[j // 8] |= _bitnum_intr(c, KEY_COMPRESSION[j], 7 - (j % 8))
for j in range(24, 48):
row[j // 8] |= _bitnum_intr(d, KEY_COMPRESSION[j] - 27, 7 - (j % 8))
return schedule
def _tripledes_key_setup(key: bytes, mode: int) -> tuple[Schedule, Schedule, Schedule]:
"""3DES(EDE3) 密钥编排,返回 3 组 16x6 轮密钥。"""
if mode == DES_ENCRYPT:
return (
_key_schedule(key[0:8], DES_ENCRYPT),
_key_schedule(key[8:16], DES_DECRYPT),
_key_schedule(key[16:24], DES_ENCRYPT),
)
return (
_key_schedule(key[16:24], DES_DECRYPT),
_key_schedule(key[8:16], DES_ENCRYPT),
_key_schedule(key[0:8], DES_DECRYPT),
)
def _tripledes_crypt(block: bytes, schedule: tuple[Schedule, Schedule, Schedule]) -> bytes:
"""3DES 加/解密一个 8 字节分组(ECB,无填充)。"""
buf = bytearray(8)
mid = bytearray(8)
_des_crypt(block, schedule[0], buf)
_des_crypt(bytes(buf), schedule[1], mid)
_des_crypt(bytes(mid), schedule[2], buf)
return bytes(buf)
def _inflate(data: bytes) -> bytes:
"""zlib 解压,容忍尾部不完整/多余字节(对应原实现的 Z_SYNC_FLUSH)。"""
try:
return zlib.decompress(data)
except zlib.error:
pass
# 流未正常收尾(3DES 分组补位会在尾部留垃圾)时,flush 报错属预期,已解出的部分仍可用
for wbits in (15, 15 + 32, -15):
try:
decompressor = zlib.decompressobj(wbits)
out = decompressor.decompress(data)
except zlib.error:
continue
try:
out += decompressor.flush()
except zlib.error:
pass
if out:
return out
# 帧中间损坏时逐块喂入,保住出错点之前已解出的前缀
for wbits in (15, 15 + 32, -15):
try:
decompressor = zlib.decompressobj(wbits)
except zlib.error:
continue
out = bytearray()
try:
for offset in range(0, len(data), 64):
out += decompressor.decompress(data[offset : offset + 64])
out += decompressor.flush()
except zlib.error:
if out:
return bytes(out)
continue
if out:
return bytes(out)
return b""
def decode_qrc(hex_data: str | None) -> str:
"""解密腾讯 QRC 歌词。
:param hex_data: 服务端返回的十六进制字符串
:return: 解密并解压后的歌词文本(解析失败时返回空字符串)
"""
if not hex_data or len(hex_data) % 2 != 0:
return ""
try:
encrypted = bytes.fromhex(hex_data)
except ValueError:
return ""
if not encrypted:
return ""
schedule = _tripledes_key_setup(QRC_KEY, DES_DECRYPT)
plain = bytearray()
for offset in range(0, len(encrypted) - 7, 8):
plain += _tripledes_crypt(encrypted[offset : offset + 8], schedule)
# 尾部不足 8 字节的分组原样保留(inflate 只看有效部分,与原文 set 不上剩余块一致)
plain += encrypted[len(plain) :]
return _inflate(bytes(plain)).decode("utf-8", errors="replace")