baesd ruste crypto?!!?1'1??!?!!?!?
This commit is contained in:
parent
6994f35062
commit
30153cfb8a
|
@ -1,4 +1,10 @@
|
||||||
node_modules/
|
/target
|
||||||
|
/dist
|
||||||
|
**/*.rs.bk
|
||||||
|
Cargo.lock
|
||||||
|
bin/
|
||||||
|
pkg/
|
||||||
|
wasm-pack.log
|
||||||
worker/
|
worker/
|
||||||
|
node_modules/
|
||||||
.cargo-ok
|
.cargo-ok
|
||||||
package-lock.json
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
[package]
|
||||||
|
name = "dzserver"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["uh wot <uhwot@protonmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
block-modes = "0.8"
|
||||||
|
blowfish = "0.8"
|
||||||
|
aes = "0.7"
|
||||||
|
md-5 = "0.9"
|
||||||
|
hex = "0.4"
|
||||||
|
|
||||||
|
js-sys = "0.3"
|
||||||
|
wasm-bindgen = "=0.2.74"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
wasm-bindgen-test = "0.3"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
# Tell `rustc` to optimize for small code size.
|
||||||
|
opt-level = "s"
|
||||||
|
lto = true
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
70
index.js
70
index.js
|
@ -1,58 +1,10 @@
|
||||||
const Router = require('./router')
|
const Router = require('./router')
|
||||||
const aesjs = require('aes-js');
|
|
||||||
const ID3Writer = require('browser-id3-writer');
|
const ID3Writer = require('browser-id3-writer');
|
||||||
const Blowfish = require('./blowfish');
|
|
||||||
|
|
||||||
addEventListener('fetch', event => {
|
addEventListener('fetch', event => {
|
||||||
event.respondWith(handleRequest(event.request))
|
event.respondWith(handleRequest(event.request))
|
||||||
})
|
})
|
||||||
|
|
||||||
const trackCDNKey = [106, 111, 54, 97, 101, 121, 54, 104, 97, 105, 100, 50, 84, 101, 105, 104]
|
|
||||||
const bfSecret = [103, 52, 101, 108, 53, 56, 119, 99, 48, 122, 118, 102, 57, 110, 97, 49]
|
|
||||||
|
|
||||||
function str2bin(str) {
|
|
||||||
return Array.from(str).map(function (item) {
|
|
||||||
return item.charCodeAt(0);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function bin2str(bin) {
|
|
||||||
return String.fromCharCode.apply(String, bin);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function bf_key(id) {
|
|
||||||
let id_md5 = await crypto.subtle.digest('MD5', new Uint8Array(str2bin(id)))
|
|
||||||
id_md5 = aesjs.utils.hex.fromBytes(new Uint8Array(id_md5))
|
|
||||||
|
|
||||||
key = []
|
|
||||||
for (let i = 0; i < 16; i++) {
|
|
||||||
key.push(id_md5[i].charCodeAt(0) ^ id_md5[i + 16].charCodeAt(0) ^ bfSecret[i])
|
|
||||||
}
|
|
||||||
return bin2str(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function url_gen(md5_origin, format, id, media_version) {
|
|
||||||
const cipher = new aesjs.ModeOfOperation.ecb(trackCDNKey)
|
|
||||||
|
|
||||||
let result = [md5_origin, format, id, media_version].join('\xa4')
|
|
||||||
|
|
||||||
let result_md5 = await crypto.subtle.digest('MD5', new Uint8Array(str2bin(result)))
|
|
||||||
|
|
||||||
result_md5 = aesjs.utils.hex.fromBytes(new Uint8Array(result_md5))
|
|
||||||
|
|
||||||
result = result_md5 + '\xa4' + result + '\xa4'
|
|
||||||
|
|
||||||
// zero-padding
|
|
||||||
if (result.length % 16) {
|
|
||||||
result += '\x00'.repeat(16 - result.length % 16)
|
|
||||||
}
|
|
||||||
|
|
||||||
result = aesjs.utils.hex.fromBytes(cipher.encrypt(str2bin(result)))
|
|
||||||
|
|
||||||
// cdn template with first character of md5 string + hash
|
|
||||||
return `https://cdns-proxy-${md5_origin[0]}.dzcdn.net/mobile/1/${result}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const format_string_to_num = {
|
const format_string_to_num = {
|
||||||
'64': '10',
|
'64': '10',
|
||||||
'128': '1',
|
'128': '1',
|
||||||
|
@ -113,7 +65,9 @@ async function track(id, format, access_token, tagging) {
|
||||||
return new Response(JSON.stringify(json.error), { status: 403, headers: { 'content-type': "application/json" } })
|
return new Response(JSON.stringify(json.error), { status: 403, headers: { 'content-type': "application/json" } })
|
||||||
}
|
}
|
||||||
|
|
||||||
result = await track_url(json, format)
|
const wasm = await import('./pkg')
|
||||||
|
|
||||||
|
result = await track_url(json, format, wasm.stream_url)
|
||||||
if (typeof result === 'object') {
|
if (typeof result === 'object') {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -189,18 +143,16 @@ async function track(id, format, access_token, tagging) {
|
||||||
id = json.alternative.id.toString()
|
id = json.alternative.id.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
const bfKey = await bf_key(id)
|
const cipher = new wasm.Cipher(id)
|
||||||
|
|
||||||
const length = Number(track.headers.get('Content-Length'))
|
const length = Number(track.headers.get('Content-Length'))
|
||||||
|
|
||||||
pipeDecryptedStream(writer, track.body, length, bfKey)
|
pipeDecryptedStream(writer, track.body, length, cipher)
|
||||||
|
|
||||||
return new Response(readable, { status: 200, headers: { 'content-type': 'audio/mpeg' } })
|
return new Response(readable, { status: 200, headers: { 'content-type': 'audio/mpeg' } })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pipeDecryptedStream(writer, body, length, bfKey) {
|
async function pipeDecryptedStream(writer, body, length, cipher) {
|
||||||
const cipher = new Blowfish(bfKey, Blowfish.MODE.CBC, Blowfish.PADDING.NULL)
|
|
||||||
cipher.setIv('\x00\x01\x02\x03\x04\x05\x06\x07')
|
|
||||||
|
|
||||||
const reader = body.getReader({ mode: 'byob' })
|
const reader = body.getReader({ mode: 'byob' })
|
||||||
let byteCount = 0
|
let byteCount = 0
|
||||||
let end = false
|
let end = false
|
||||||
|
@ -224,10 +176,10 @@ async function pipeDecryptedStream(writer, body, length, bfKey) {
|
||||||
|
|
||||||
if (byteCount % 6144 === 0 && !end) {
|
if (byteCount % 6144 === 0 && !end) {
|
||||||
// encrypted chunk
|
// encrypted chunk
|
||||||
chunk = cipher.decode(chunk, Blowfish.TYPE.UINT8_ARRAY)
|
chunk = cipher.decrypt_chunk(chunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.write(chunk)
|
await writer.write(chunk)
|
||||||
byteCount += 2048
|
byteCount += 2048
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +187,7 @@ async function pipeDecryptedStream(writer, body, length, bfKey) {
|
||||||
await writer.close()
|
await writer.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function track_url(json, format) {
|
function track_url(json, format, url_func) {
|
||||||
// needed if track has fallback, like https://www.deezer.com/track/11835714
|
// needed if track has fallback, like https://www.deezer.com/track/11835714
|
||||||
if (json.alternative) {
|
if (json.alternative) {
|
||||||
json = json.alternative
|
json = json.alternative
|
||||||
|
@ -255,7 +207,7 @@ async function track_url(json, format) {
|
||||||
|
|
||||||
format = format_string_to_num[format]
|
format = format_string_to_num[format]
|
||||||
|
|
||||||
return await url_gen(md5_origin, format, id, media_version)
|
return url_func(md5_origin, format, id, media_version)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function m3u8(type, id, format, access_token, tagging) {
|
async function m3u8(type, id, format, access_token, tagging) {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -10,10 +10,10 @@
|
||||||
"author": "uh_wot <uhwot@protonmail.com>",
|
"author": "uh_wot <uhwot@protonmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@wasm-tool/wasm-pack-plugin": "^1.5.0",
|
||||||
"prettier": "^1.17.0"
|
"prettier": "^1.17.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"aes-js": "^3.1.2",
|
|
||||||
"browser-id3-writer": "^4.4.0",
|
"browser-id3-writer": "^4.4.0",
|
||||||
"serverless-cloudflare-workers": "^1.2.0"
|
"serverless-cloudflare-workers": "^1.2.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
use js_sys::Uint8Array;
|
||||||
|
|
||||||
|
use blowfish::Blowfish;
|
||||||
|
use aes::Aes128;
|
||||||
|
use block_modes::{BlockMode, Cbc, Ecb};
|
||||||
|
use block_modes::block_padding::{NoPadding, ZeroPadding};
|
||||||
|
use md5::{Md5, Digest};
|
||||||
|
|
||||||
|
type BfCbc = Cbc<Blowfish, NoPadding>;
|
||||||
|
type Aes128Ecb = Ecb<Aes128, ZeroPadding>;
|
||||||
|
|
||||||
|
const TRACK_CDN_KEY: [u8; 16] = [106, 111, 54, 97, 101, 121, 54, 104, 97, 105, 100, 50, 84, 101, 105, 104];
|
||||||
|
const BF_SECRET: [u8; 16] = [103, 52, 101, 108, 53, 56, 119, 99, 48, 122, 118, 102, 57, 110, 97, 49];
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct Cipher {
|
||||||
|
cipher: BfCbc
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl Cipher {
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new(id: &str) -> Self {
|
||||||
|
let id_md5 = format!("{:x}", Md5::digest(id.as_bytes()));
|
||||||
|
let id_md5 = id_md5.as_bytes();
|
||||||
|
let mut key = [0u8; 16];
|
||||||
|
for i in 0..16 {
|
||||||
|
key[i] = id_md5[i] ^ id_md5[i+16] ^ BF_SECRET[i]
|
||||||
|
};
|
||||||
|
Self { cipher: BfCbc::new_from_slices(&key, &[0, 1, 2, 3, 4, 5, 6, 7]).unwrap() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt_chunk(&self, chunk: &mut [u8]) -> Uint8Array {
|
||||||
|
let chunk = self.cipher.clone().decrypt(chunk).unwrap();
|
||||||
|
unsafe { Uint8Array::view(&chunk) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn stream_url(md5_origin: &str, format: &str, id: i32, media_version: u8) -> String {
|
||||||
|
// md5 origin + format num + id + media version
|
||||||
|
let metadata = [
|
||||||
|
md5_origin.as_bytes(),
|
||||||
|
&[b'\xa4'],
|
||||||
|
format.as_bytes(),
|
||||||
|
&[b'\xa4'],
|
||||||
|
id.to_string().as_bytes(),
|
||||||
|
&[b'\xa4'],
|
||||||
|
media_version.to_string().as_bytes(),
|
||||||
|
].concat();
|
||||||
|
|
||||||
|
let hash = format!("{:x}", Md5::digest(&metadata));
|
||||||
|
|
||||||
|
// md5 hash + previous metadata
|
||||||
|
let metadata_hash = [
|
||||||
|
hash.as_bytes(),
|
||||||
|
&[b'\xa4'],
|
||||||
|
&metadata,
|
||||||
|
&[b'\xa4'],
|
||||||
|
].concat();
|
||||||
|
|
||||||
|
let cipher = Aes128Ecb::new_from_slices(&TRACK_CDN_KEY, Default::default()).unwrap();
|
||||||
|
let ciphertext = cipher.encrypt_vec(&metadata_hash);
|
||||||
|
|
||||||
|
format!("https://cdns-proxy-{}.dzcdn.net/mobile/1/{}", md5_origin.chars().next().unwrap(), hex::encode(ciphertext))
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
const path = require("path");
|
||||||
|
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: {
|
||||||
|
index: "./index.js"
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new WasmPackPlugin({
|
||||||
|
crateDirectory: __dirname,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
};
|
|
@ -1,5 +1,6 @@
|
||||||
name = "dz"
|
name = "dz"
|
||||||
type = "webpack"
|
type = "webpack"
|
||||||
|
webpack_config = "webpack.config.js"
|
||||||
account_id = "03479b0523a52b140e0dabac40cb0fc8"
|
account_id = "03479b0523a52b140e0dabac40cb0fc8"
|
||||||
workers_dev = true
|
workers_dev = true
|
||||||
route = ""
|
route = ""
|
||||||
|
|
Loading…
Reference in New Issue