Files
MusicServer/sdk/common/qrcDecode.ts
T
snowy-yang 354df2e739 refactor: 架构整固——分层单向化、全量 TypeScript、直接运行
- 移除 bd/mg 两平台及磁盘残留产物;清理 api-test 死脚本、musicSdk/message+options 死模块、infSign vendor
- musicSdk/ 改名 sdk/;src/common、src/auth、src/apis 整体移入 sdk/,依赖方向定为 src → sdk 严格单向
- src/ 手写 JS 全量转 .ts,删除 JS 类型垫片 modules.d.ts;源码 import 统一 .ts 后缀
- Node 原生类型剥离直接运行 .ts(engines >=22.18),取消 tsc 发射产物,tsc 仅类型检查(pnpm typecheck)
- 删除 kwdes 备用实现与 src/index.ts 转发垫片;测试移至 test/smoke-test.ts
- common/request.ts 补显式导出类型;修复 set-cookie 单值形态、ApiError 类字段等隐患

验证:tsc 零错误,smoke-test 41/41
2026-09-25 23:06:23 +08:00

253 lines
13 KiB
TypeScript
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.
import { inflate, constants } from 'zlib'
/**
* QRC 歌词解密(3DES-ECB + zlib inflate)
*
* 由原 C++ native 插件(qrc_decode.node)1:1 移植为纯 JS 实现。
* 加密算法为标准 3DES(DES-EDE3,ECB 模式、无填充),密钥固定。
* 数据流程:hex 字符串 -> 字节 -> 3DES 解密 -> zlib inflate -> UTF-8 文本。
*/
const DES_ENCRYPT = 1
const DES_DECRYPT = 0
// DES S-box。注意:QRC 使用的是非标准 DES 变体,
// 其中 S2[23]=15、S4[53]=10 两处与标准 DES 不同(标准为 14、1)。
// 这两个值与原 native 插件(qrc_decode.node)二进制完全一致,不可改为标准值,
// 否则解密结果错误。下表逐字节对应原 C++ 实现。
const sbox = [
[
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,
],
]
const key_rnd_shift = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
const 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,
]
const 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,
]
const 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,
]
// 全部以无符号 32 位处理,>>> 0 保证溢出行为与 C++ uint32_t 一致
const bitnum = (a, b, c) => (((a[((b / 32) | 0) * 4 + 3 - (((b % 32) / 8) | 0)] >>> (7 - (b % 8))) & 1) << c) >>> 0
const bitnum_intr = (a, b, c) => (((a >>> (31 - b)) & 1) << c) >>> 0
const bitnum_intl = (a, b, c) => (((((a << b) >>> 0) & 0x80000000) >>> 0) >>> c) >>> 0
const sbox_bit = a => (a & 32) | ((a & 31) >>> 1) | ((a & 1) << 4)
// 初始置换:返回 [s0, s1]
const initial_permutation = (input) => {
const s0 = (
bitnum(input, 57, 31) | bitnum(input, 49, 30) | bitnum(input, 41, 29) | bitnum(input, 33, 28) |
bitnum(input, 25, 27) | bitnum(input, 17, 26) | bitnum(input, 9, 25) | bitnum(input, 1, 24) |
bitnum(input, 59, 23) | bitnum(input, 51, 22) | bitnum(input, 43, 21) | bitnum(input, 35, 20) |
bitnum(input, 27, 19) | bitnum(input, 19, 18) | bitnum(input, 11, 17) | bitnum(input, 3, 16) |
bitnum(input, 61, 15) | bitnum(input, 53, 14) | bitnum(input, 45, 13) | bitnum(input, 37, 12) |
bitnum(input, 29, 11) | bitnum(input, 21, 10) | bitnum(input, 13, 9) | bitnum(input, 5, 8) |
bitnum(input, 63, 7) | bitnum(input, 55, 6) | bitnum(input, 47, 5) | bitnum(input, 39, 4) |
bitnum(input, 31, 3) | bitnum(input, 23, 2) | bitnum(input, 15, 1) | bitnum(input, 7, 0)
) >>> 0
const s1 = (
bitnum(input, 56, 31) | bitnum(input, 48, 30) | bitnum(input, 40, 29) | bitnum(input, 32, 28) |
bitnum(input, 24, 27) | bitnum(input, 16, 26) | bitnum(input, 8, 25) | bitnum(input, 0, 24) |
bitnum(input, 58, 23) | bitnum(input, 50, 22) | bitnum(input, 42, 21) | bitnum(input, 34, 20) |
bitnum(input, 26, 19) | bitnum(input, 18, 18) | bitnum(input, 10, 17) | bitnum(input, 2, 16) |
bitnum(input, 60, 15) | bitnum(input, 52, 14) | bitnum(input, 44, 13) | bitnum(input, 36, 12) |
bitnum(input, 28, 11) | bitnum(input, 20, 10) | bitnum(input, 12, 9) | bitnum(input, 4, 8) |
bitnum(input, 62, 7) | bitnum(input, 54, 6) | bitnum(input, 46, 5) | bitnum(input, 38, 4) |
bitnum(input, 30, 3) | bitnum(input, 22, 2) | bitnum(input, 14, 1) | bitnum(input, 6, 0)
) >>> 0
return [s0, s1]
}
// 逆置换:将 s0/s1 写回 8 字节 out
const inverse_permutation = (s0, s1, out) => {
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)) & 0xff
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)) & 0xff
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)) & 0xff
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)) & 0xff
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)) & 0xff
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)) & 0xff
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)) & 0xff
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)) & 0xff
}
// Feistel 轮函数 f(state, key)
const des_f = (state, key) => {
const t1 = (
bitnum_intl(state, 31, 0) | (((state & 0xF0000000) >>> 0) >>> 1) | bitnum_intl(state, 4, 5) |
bitnum_intl(state, 3, 6) | (((state & 0x0F000000) >>> 0) >>> 3) | bitnum_intl(state, 8, 11) |
bitnum_intl(state, 7, 12) | (((state & 0x00F00000) >>> 0) >>> 5) | bitnum_intl(state, 12, 17) |
bitnum_intl(state, 11, 18) | (((state & 0x000F0000) >>> 0) >>> 7) | bitnum_intl(state, 16, 23)
) >>> 0
const t2 = (
bitnum_intl(state, 15, 0) | (((state & 0x0000F000) << 15) >>> 0) | bitnum_intl(state, 20, 5) |
bitnum_intl(state, 19, 6) | (((state & 0x00000F00) << 13) >>> 0) | bitnum_intl(state, 24, 11) |
bitnum_intl(state, 23, 12) | (((state & 0x000000F0) << 11) >>> 0) | bitnum_intl(state, 28, 17) |
bitnum_intl(state, 27, 18) | (((state & 0x0000000F) << 9) >>> 0) | bitnum_intl(state, 0, 23)
) >>> 0
const lrgstate = [
(t1 >>> 24) & 0xFF, (t1 >>> 16) & 0xFF, (t1 >>> 8) & 0xFF,
(t2 >>> 24) & 0xFF, (t2 >>> 16) & 0xFF, (t2 >>> 8) & 0xFF,
]
for (let i = 0; i < 6; i++) lrgstate[i] ^= key[i]
let 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)]
) >>> 0
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)
) >>> 0
}
// 单次 DES 加/解密一个 8 字节分组(轮密钥 schedule 为 16x6)
const des_crypt = (input, schedule, output) => {
let [s0, s1] = initial_permutation(input)
for (let i = 0; i < 15; i++) {
const prev = s1
s1 = (des_f(s1, schedule[i]) ^ s0) >>> 0
s0 = prev
}
s0 = (des_f(s1, schedule[15]) ^ s0) >>> 0
inverse_permutation(s0, s1, output)
}
// 生成 16 轮子密钥
const key_schedule = (key, mode) => {
const schedule = Array.from({ length: 16 }, () => new Uint8Array(6))
let c = 0
let d = 0
for (let i = 0; i < 28; i++) {
c = (c | bitnum(key, key_perm_c[i], 31 - i)) >>> 0
d = (d | bitnum(key, key_perm_d[i], 31 - i)) >>> 0
}
for (let i = 0; i < 16; i++) {
c = ((((c << key_rnd_shift[i]) >>> 0) | (c >>> (28 - key_rnd_shift[i]))) & 0xFFFFFFF0) >>> 0
d = ((((d << key_rnd_shift[i]) >>> 0) | (d >>> (28 - key_rnd_shift[i]))) & 0xFFFFFFF0) >>> 0
const togen = mode === DES_DECRYPT ? 15 - i : i
for (let j = 0; j < 24; j++) {
schedule[togen][(j / 8) | 0] |= bitnum_intr(c, key_compression[j], 7 - (j % 8))
}
for (let j = 24; j < 48; j++) {
schedule[togen][(j / 8) | 0] |= bitnum_intr(d, key_compression[j] - 27, 7 - (j % 8))
}
}
return schedule
}
// 3DES(EDE3)密钥编排,返回 3 组 schedule
const tripledes_key_setup = (key, mode) => {
if (mode === DES_ENCRYPT) {
return [
key_schedule(key.subarray(0, 8), DES_ENCRYPT),
key_schedule(key.subarray(8, 16), DES_DECRYPT),
key_schedule(key.subarray(16, 24), DES_ENCRYPT),
]
}
return [
key_schedule(key.subarray(16, 24), DES_DECRYPT),
key_schedule(key.subarray(8, 16), DES_ENCRYPT),
key_schedule(key.subarray(0, 8), DES_DECRYPT),
]
}
// 3DES 加/解密一个 8 字节分组
const tripledes_crypt = (input, schedule, output) => {
const buf = new Uint8Array(8)
des_crypt(input, schedule[0], buf)
des_crypt(buf, schedule[1], output)
des_crypt(output, schedule[2], buf)
output.set(buf)
}
// QRC 固定密钥:!@#)(*$%123ZXC!@!@#)(NHL
const QRC_KEY = Buffer.from([
0x21, 0x40, 0x23, 0x29, 0x28, 0x2a, 0x24, 0x25, 0x31, 0x32, 0x33, 0x5a,
0x58, 0x43, 0x21, 0x40, 0x21, 0x40, 0x23, 0x29, 0x28, 0x4e, 0x48, 0x4c,
])
const handleInflate = (data: Buffer): Promise<Buffer> => new Promise((resolve, reject) => {
// Z_SYNC_FLUSH:容忍尾部不完整的 zlib 流(对应原实现忽略 Z_BUF_ERROR 的行为)
inflate(data, { finishFlush: constants.Z_SYNC_FLUSH }, (err, result) => {
if (err) reject(err)
else resolve(result)
})
})
/**
* 解密腾讯 QRC 歌词。
* @param {string} hexData 服务端返回的十六进制字符串
* @returns {Promise<string>} 解密并解压后的歌词文本(解析失败时返回空字符串)
*/
export const decodeQrc = async(hexData) => {
if (!hexData || hexData.length % 2 !== 0) return ''
const encrypted = Buffer.from(hexData, 'hex')
if (encrypted.length === 0) return ''
const schedule = tripledes_key_setup(QRC_KEY, DES_DECRYPT)
const block = new Uint8Array(8)
for (let i = 0; i + 8 <= encrypted.length; i += 8) {
tripledes_crypt(encrypted.subarray(i, i + 8), schedule, block)
encrypted.set(block, i)
}
try {
const result = await handleInflate(encrypted)
return result.toString('utf8')
} catch {
return ''
}
}