Skip to content

Commit

Permalink
update kuwo api (kuwoDES support)
Browse files Browse the repository at this point in the history
  • Loading branch information
nondanee committed Feb 16, 2020
1 parent 1251b9d commit 586d043
Show file tree
Hide file tree
Showing 5 changed files with 308 additions and 14 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions src/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ module.exports = {
}
},
miguapi: {
encrypt: object => {
encryptBody: object => {
const text = JSON.stringify(object)
const derive = (password, salt, keyLength, ivSize) => { // EVP_BytesToKey
salt = salt || Buffer.alloc(0)
Expand Down Expand Up @@ -97,4 +97,6 @@ module.exports = {
.once('finish', () => resolve(digest.read()))
})
}
}
}

try {module.exports.kuwoapi = require('./kwDES')} catch(e) {}
291 changes: 291 additions & 0 deletions src/kwDES.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
/*
Thanks to
https://github.com/XuShaohua/kwplayer/blob/master/kuwo/DES.py
https://github.com/Levi233/MusicPlayer/blob/master/app/src/main/java/com/chenhao/musicplayer/utils/crypt/KuwoDES.java
*/

const Long = (
typeof(BigInt) === 'function' // BigInt support in Node 10+
? n => (n = BigInt(n), ({
low: Number(n),
valueOf: () => n.valueOf(),
toString: () => n.toString(),
not: () => Long(~n),
isNegative: () => n < 0,
or: x => Long(n | BigInt(x)),
and: x => Long(n & BigInt(x)),
xor: x => Long(n ^ BigInt(x)),
equals: x => n === BigInt(x),
multiply: x => Long(n * BigInt(x)),
shiftLeft: x => Long(n << BigInt(x)),
shiftRight: x => Long(n >> BigInt(x)),
}))
: (...args) => new (require('long'))(...args)
)

const range = n => Array.from(new Array(n).keys())
const power = (base, index) => Array(index).fill().reduce((result) => result.multiply(base), Long(1))
const LongArray = (...array) => array.map(n => n === -1 ? Long(-1, -1) : Long(n))

// EXPANSION
const arrayE = LongArray(
31, 0, 1, 2, 3, 4, -1, -1,
3, 4, 5, 6, 7, 8, -1, -1,
7, 8, 9, 10, 11, 12, -1, -1,
11, 12, 13, 14, 15, 16, -1, -1,
15, 16, 17, 18, 19, 20, -1, -1,
19, 20, 21, 22, 23, 24, -1, -1,
23, 24, 25, 26, 27, 28, -1, -1,
27, 28, 29, 30, 31, 30, -1, -1,
)

// INITIAL_PERMUTATION
const arrayIP = LongArray(
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7,
56, 48, 40, 32, 24, 16, 8, 0,
58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
)

// INVERSE_PERMUTATION
const arrayIP_1 = LongArray(
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25,
32, 0, 40, 8, 48, 16, 56, 24,
)

// ROTATES
const arrayLs = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
const arrayLsMask = LongArray(0, 0x100001, 0x300003)
const arrayMask = range(64).map(n => power(2, n))
arrayMask[arrayMask.length - 1] = arrayMask[arrayMask.length - 1].multiply(-1)

// PERMUTATION
const arrayP = LongArray(
15, 6, 19, 20, 28, 11, 27, 16,
0, 14, 22, 25, 4, 17, 30, 9,
1, 7, 23, 13, 31, 26, 2, 8,
18, 12, 29, 5, 21, 10, 3, 24,
)

// PERMUTED_CHOICE1
const arrayPC_1 = LongArray(
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, 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,
)

// PERMUTED_CHOICE2
const arrayPC_2 = LongArray(
13, 16, 10, 23, 0, 4, -1, -1,
2, 27, 14, 5, 20, 9, -1, -1,
22, 18, 11, 3, 25, 7, -1, -1,
15, 6, 26, 19, 12, 1, -1, -1,
40, 51, 30, 36, 46, 54, -1, -1,
29, 39, 50, 44, 32, 47, -1, -1,
43, 48, 38, 55, 33, 52, -1, -1,
45, 41, 49, 35, 28, 31, -1, -1,
)

const matrixNSBox = [[
14, 4, 3, 15, 2, 13, 5, 3,
13, 14, 6, 9, 11, 2, 0, 5,
4, 1, 10, 12, 15, 6, 9, 10,
1, 8, 12, 7, 8, 11, 7, 0,
0, 15, 10, 5, 14, 4, 9, 10,
7, 8, 12, 3, 13, 1, 3, 6,
15, 12, 6, 11, 2, 9, 5, 0,
4, 2, 11, 14, 1, 7, 8, 13, ], [
15, 0, 9, 5, 6, 10, 12, 9,
8, 7, 2, 12, 3, 13, 5, 2,
1, 14, 7, 8, 11, 4, 0, 3,
14, 11, 13, 6, 4, 1, 10, 15,
3, 13, 12, 11, 15, 3, 6, 0,
4, 10, 1, 7, 8, 4, 11, 14,
13, 8, 0, 6, 2, 15, 9, 5,
7, 1, 10, 12, 14, 2, 5, 9, ], [
10, 13, 1, 11, 6, 8, 11, 5,
9, 4, 12, 2, 15, 3, 2, 14,
0, 6, 13, 1, 3, 15, 4, 10,
14, 9, 7, 12, 5, 0, 8, 7,
13, 1, 2, 4, 3, 6, 12, 11,
0, 13, 5, 14, 6, 8, 15, 2,
7, 10, 8, 15, 4, 9, 11, 5,
9, 0, 14, 3, 10, 7, 1, 12, ], [
7, 10, 1, 15, 0, 12, 11, 5,
14, 9, 8, 3, 9, 7, 4, 8,
13, 6, 2, 1, 6, 11, 12, 2,
3, 0, 5, 14, 10, 13, 15, 4,
13, 3, 4, 9, 6, 10, 1, 12,
11, 0, 2, 5, 0, 13, 14, 2,
8, 15, 7, 4, 15, 1, 10, 7,
5, 6, 12, 11, 3, 8, 9, 14, ], [
2, 4, 8, 15, 7, 10, 13, 6,
4, 1, 3, 12, 11, 7, 14, 0,
12, 2, 5, 9, 10, 13, 0, 3,
1, 11, 15, 5, 6, 8, 9, 14,
14, 11, 5, 6, 4, 1, 3, 10,
2, 12, 15, 0, 13, 2, 8, 5,
11, 8, 0, 15, 7, 14, 9, 4,
12, 7, 10, 9, 1, 13, 6, 3, ], [
12, 9, 0, 7, 9, 2, 14, 1,
10, 15, 3, 4, 6, 12, 5, 11,
1, 14, 13, 0, 2, 8, 7, 13,
15, 5, 4, 10, 8, 3, 11, 6,
10, 4, 6, 11, 7, 9, 0, 6,
4, 2, 13, 1, 9, 15, 3, 8,
15, 3, 1, 14, 12, 5, 11, 0,
2, 12, 14, 7, 5, 10, 8, 13, ], [
4, 1, 3, 10, 15, 12, 5, 0,
2, 11, 9, 6, 8, 7, 6, 9,
11, 4, 12, 15, 0, 3, 10, 5,
14, 13, 7, 8, 13, 14, 1, 2,
13, 6, 14, 9, 4, 1, 2, 14,
11, 13, 5, 0, 1, 10, 8, 3,
0, 11, 3, 5, 9, 4, 15, 2,
7, 8, 12, 15, 10, 7, 6, 12, ], [
13, 7, 10, 0, 6, 9, 5, 15,
8, 4, 3, 10, 11, 14, 12, 5,
2, 11, 9, 6, 15, 12, 0, 3,
4, 1, 14, 13, 1, 2, 7, 8,
1, 2, 12, 15, 10, 4, 0, 3,
13, 14, 6, 9, 7, 8, 9, 6,
15, 1, 5, 12, 3, 10, 14, 5,
8, 7, 11, 0, 4, 13, 2, 11, ],
]

const bitTransform = (arrInt, n, l) => { // int[], int, long : long
let l2 = Long(0)
range(n).forEach(i => {
if (arrInt[i].isNegative() || (l.and(arrayMask[arrInt[i].low]).equals(0)))
return
l2 = l2.or(arrayMask[i])
})
return l2
}

const DES64 = (longs, l) => { // long[], long
let out = Long(0)
let SOut = Long(0)
const pR = range(8).map(() => Long(0))
const pSource = [Long(0), Long(0)]
let L = Long(0)
let R = Long(0)
out = bitTransform(arrayIP, 64, l)
pSource[0] = out.and(0xFFFFFFFF)
pSource[1] = out.and(-4294967296).shiftRight(32)

range(16).forEach(i => {
R = Long(pSource[1])
R = bitTransform(arrayE, 64, R)
R = R.xor(longs[i])
range(8).forEach(j => {
pR[j] = R.shiftRight(j * 8).and(255)
})
SOut = Long(0)
range(8).reverse().forEach(sbi => {
SOut = SOut.shiftLeft(4).or(matrixNSBox[sbi][pR[sbi]])
})
R = bitTransform(arrayP, 32, SOut)
L = Long(pSource[0])
pSource[0] = Long(pSource[1])
pSource[1] = L.xor(R)
})
pSource.reverse()
out = pSource[1].shiftLeft(32).and(-4294967296).or(
pSource[0].and(0xFFFFFFFF)
)
out = bitTransform(arrayIP_1, 64, out)
return out
}


const subKeys = (l, longs, n) => { // long, long[], int
let l2 = bitTransform(arrayPC_1, 56, l)
range(16).forEach(i => {
l2 = (
l2.and(arrayLsMask[arrayLs[i]]).shiftLeft(28 - arrayLs[i]).or(
l2.and(arrayLsMask[arrayLs[i]].not()).shiftRight(arrayLs[i])
)
)
longs[i] = bitTransform(arrayPC_2, 64, l2)
})
if (n === 1) {
range(8).forEach(j => {
[longs[j], longs[15 - j]] = [longs[15 - j], longs[j]]
})
}
}

const crypt = (msg, key, mode) => {
// 处理密钥块
let l = Long(0)
range(8).forEach(i => {
l = Long(key[i]).shiftLeft(i * 8).or(l)
})

const j = Math.floor(msg.length / 8)
// arrLong1 存放的是转换后的密钥块, 在解密时只需要把这个密钥块反转就行了

const arrLong1 = range(16).map(() => Long(0))
subKeys(l, arrLong1, mode)

// arrLong2 存放的是前部分的明文
const arrLong2 = range(j).map(() => Long(0))

range(j).forEach(m => {
range(8).forEach(n => {
arrLong2[m] = Long(msg[n + m * 8]).shiftLeft(n * 8).or(arrLong2[m])
})
})

// 用于存放密文
const arrLong3 = range(Math.floor((1 + 8 * (j + 1)) / 8)).map(() => Long(0))

// 计算前部的数据块(除了最后一部分)
range(j).forEach(i1 => {
arrLong3[i1] = DES64(arrLong1, arrLong2[i1])
})

// 保存多出来的字节
const arrByte1 = msg.slice(j * 8)
let l2 = Long(0)

range(msg.length % 8).forEach(i1 => {
l2 = Long(arrByte1[i1]).shiftLeft(i1 * 8).or(l2)
})

// 计算多出的那一位(最后一位)
if (arrByte1.length || mode === 0) arrLong3[j] = DES64(arrLong1, l2) // 解密不需要

// 将密文转为字节型
const arrByte2 = range(8 * arrLong3.length).map(() => 0)
let i4 = 0
arrLong3.forEach(l3 => {
range(8).forEach(i6 => {
arrByte2[i4] = l3.shiftRight(i6 * 8).and(255).low
i4 += 1
})
})
return Buffer.from(arrByte2)
}

const SECRET_KEY = Buffer.from('ylzsxkwm')
const encrypt = msg => crypt(msg, SECRET_KEY, 0)
const decrypt = msg => crypt(msg, SECRET_KEY, 1)
const encryptQuery = query => encrypt(Buffer.from(query)).toString('base64')

module.exports = {encrypt, decrypt, encryptQuery}
21 changes: 11 additions & 10 deletions src/provider/kuwo.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const cache = require('../cache')
const insure = require('./insure')
const select = require('./select')
const crypto = require('../crypto')
const request = require('../request')

const format = song => ({
Expand Down Expand Up @@ -54,19 +55,19 @@ const search = info => {
}

const track = id => {
const url =
'http://antiserver.kuwo.cn/anti.s?' +
'type=convert_url&format=mp3&response=url&rid=MUSIC_' + id
// 'http://www.kuwo.cn/url?' +
// 'format=mp3&response=url&type=convert_url3&br=320kmp3&rid=' + id
const url = (crypto.kuwoapi
? 'http://mobi.kuwo.cn/mobi.s?f=kuwo&q=' + crypto.kuwoapi.encryptQuery(
'corp=kuwo&p2p=1&type=convert_url2&sig=0&format=' + ['flac', 'mp3'].slice(1).join('|') + '&rid=' + id
)
: 'http://antiserver.kuwo.cn/anti.s?type=convert_url&format=mp3&response=url&rid=MUSIC_' + id // flac refuse
// : 'http://www.kuwo.cn/url?format=mp3&response=url&type=convert_url3&br=320kmp3&rid=' + id // flac refuse
)

return request('GET', url)
return request('GET', url, {'user-agent': 'okhttp/3.10.0'})
.then(response => response.body())
.then(body => {
if (body.startsWith('http'))
return body
else
return Promise.reject()
const url = (body.match(/http[^\s$"]+/) || [])[0]
return url || Promise.reject()
})
.catch(() => insure().kuwo.track(id))
}
Expand Down
2 changes: 1 addition & 1 deletion src/provider/migu.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const search = info => {
const track = id => {
const url =
'http://music.migu.cn/v3/api/music/audioPlayer/getPlayInfo?' +
'dataType=2&' + crypto.miguapi.encrypt({copyrightId: id.toString()})
'dataType=2&' + crypto.miguapi.encryptBody({copyrightId: id.toString()})

return request('GET', url, headers)
.then(response => response.json())
Expand Down

0 comments on commit 586d043

Please sign in to comment.