Deezer protection 2020/04
yne opened this issue Β· 37 comments
After 12 years, Deezer finally implemented a proper tracks protection mechanism at CDN level. π
Meaning:
- You can't fetch FLAC/MP3_320 with basic account anymore Β―\_(γ)_/Β― meh
user_token
+track_token
needed for track fetching- the
MD5_ORIGIN
is only used for deciphering (dzr-db
is now only relevant for fetching)
playlist/song fetching using new API:
DZR_SID=fr12345_______________________________89
function gw(){ curl -s "https://www.deezer.com/ajax/gw-light.php?method=$1&input=3&api_version=1.0&api_token=$3" -H "cookie: sid=$2" $@;}
# 1 - User info
USR_NFO=$(gw deezer.getUserData $DZR_SID)
USR_TOK=$(jq -r .results.USER_TOKEN <<< $USR_NFO)
USR_LIC=$(jq -r .results.USER.OPTIONS.license_token <<< $USR_NFO)
API_TOK=$(jq -r .results.checkForm <<< $USR_NFO)
# 2a - Track to Tokens
TRK_IDS=137955757,960539
TRK_NFO=$(gw song.getListData $DZR_SID $API_TOK --data-binary '{"sng_ids":['"$TRK_IDS"']}')
TRK_TOK=$(jq -r [.results.data[].TRACK_TOKEN] <<< "$TRK_NFO")
TRK_MD5=$(jq -r [.results.data[].MD5_ORIGIN] <<< "$TRK_NFO")
# 2b - Playlist to Tokens
LST_ID=6324727784
LST_NFO=$(gw deezer.pagePlaylist $DZR_SID $API_TOK --data-binary '{"playlist_id":"'$LST_ID'","lang":"en","nb":2000}')
TRK_TOK=$(jq -r [.results.SONGS.data[].TRACK_TOKEN] <<< "$LST_NFO")
TRK_MD5=$(jq -r [.results.SONGS.data[].MD5_ORIGIN] <<< "$LST_NFO")
# 3 - Token to URL
TRK_SRC=$(curl -s 'https://media.deezer.com/v1/get_url' --data-binary '{"license_token":"'"$USR_LIC"'","media":[{"type":"FULL","formats":[{"cipher":"BF_CBC_STRIPE","format":"MP3_128"}]}],"track_tokens":'"$TRK_TOK"'}')
TRK_URL=$(jq -r .data[].media[].sources[1].url <<< "$TRK_SRC")
echo "$TRK_URL"
I'm almost done with the new API.
I'm leaving this here, just in case the DZR_CBC changed
105579760 > cff9546b99302010612dec77700605dc > http://e-cdn-proxy-c.deezer.com/mobile/1/54EBDBF1268A9912B99F12BBDDD3DACBB1E9255A8BDBF4C3AA05645E0C5FC96FCB5A7A65072DAC6B0B3A7E30B10033C34520ED85B94ED597A131D97425D6B17BA3AC13C1119D23A3EF4B3B85EF0345F1
cff9546b99302010612dec77700605dc:105579760.enc.gz
cff9546b99302010612dec77700605dc:105579760.dec.gz
So, just to confirm. The current instructions don't work anymore then, right? I tried to follow them and I just get segmentation faults. Thanks!
Deezer probably deprecated the old unsecure API
edit: they didn't dzr 5761b1cd2b80e5c8ad8a0dbecd4c7d13:16235816 | mpv -
still work
I shall find the motivation to redo the work for the new API
Is there anything that I could help with?
could you give me more context for the segfault so I can reproduce it ?
- argument+env did you used
- OS/distrib
- dzr version/tag
my dzr
work fine here with older track that dzr-db
can resolve
Of course.
- Where can I send you the sensitive info so that I don't share it here?
- Windows, but I used the Windows Subsystem for Linux (Ubuntu 18.04.02 LTS; kernel: 4.4.0-19041 Microsoft)
- Tried both versions from this tag
The thing that I suspect might be an issue is that in the AES key there's an ASCII 26 character, and even pasting that into the Terminal is causing problems, so I'm guessing it could be a lead? I'm not sure.
both DZR_AES and DZR_CBC keys are plain ascii strings
Did you found them from the web player (wee: wiki) or did you found them on the internet ?
I extracted them from the web player via the Wiki instructions. I can post them here, if you think it's fine.
Since i'm pretty sure they are wrong, you can post them here.
Also, try compare them to what others people uses as DZR_AES DZR_CBC on the internet
Thanks,
Try a pre-resolved md5 track:
DZR_SID=none ./dzr-ubuntu-latest 5e45db8562beee18000ba19b2480a6db:997764 > some.mp3
For the record, the tail and head of the keys are
DZR_CBC=g4.............1
DZR_AES=jo.............h
- The ubuntu-latest version don't work (deezer return a 403 error), but don't segfault either:
DZR_DBG=1 DZR_SID=none ./dzr-ubuntu-latest 5e45db8562beee18000ba19b2480a6db:997764 > out
GET /mobile/1/3335663262363064303434366635326237306362643236653533613764656461A43565343564623835363262656565313830303062613139623234383061366462A430A4393937373634A430A4030303 HTTP/1.0
Host: e-cdn-proxy-5.deezer.com
Content-Length: 0
HTTP/1.0 403 Forbidden
Access-Control-Expose-Headers: x-deezer-client-ip, content-length, content-range
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Content-Type: text/html; charset=UTF-8
Date: Sat, 28 Nov 2020 15:53:10 GMT
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Last-Modified: Sat, 28 Nov 2020 15:53:10 GMT
Pragma: no-cache
Server: Apache
X-Host: blm-prxmob-63
Content-Length: 0
Connection: close
- The static version is working for me:
GET /mobile/1/B3261CC4224D4F833DB0D5CDF9D4BACA144325D2CDE3EAE073F4BEDB6E14501B07813B291CD286E89D1556904327913F50C25E93118FE9F43B1F77C9032E2C906DD9927B0CCDB7FA9069623ED2CC98B0 HTTP/1.0
Host: e-cdn-proxy-5.deezer.com
Content-Length: 0
HTTP/1.0 200 OK
Accept-Ranges: bytes
Access-Control-Expose-Headers: x-deezer-client-ip, content-length, content-range
Age: 39335
Cache-Control: public
Content-Type: audio/mpeg
Date: Sat, 28 Nov 2020 15:56:41 GMT
Expires: Tue, 27 Apr 2021 15:56:41 GMT
Last-Modified: Sat, 28 Nov 2020 05:01:06 GMT
Pragma:
Server: ECAcc (sgb/C740)
X-Cache: HIT
X-Deezer-Cache: blm-prxmob-53
X-Deezer-Streamer: real
X-Host: blm-prxmob-53
Content-Length: 3418069
Connection: close
The static version use an internal crypto lib while the other rely on openSSL.
So the bug seems to be the way OpenSSL is used to generate the URL
sure you can use strace
or gdb
, I hope you are familiar with them. If not, here is a quick example :
DZR_DBG=1 DZR_SID=none gdb --args ./dzr_static-ubuntu-latest 5e45db8562beee18000ba19b2480a6db:997764
# you shall see a (gdb) prompt, type "run" then press enter
(gdb) run
# some times later the segfault shall appear with a stacktrace
segfault at 0x......
# just like before, when prompted, type "where" then press enter
(gdb) where
π€¦ I didn't have Internet connectivity within my Ubuntu instance this whole time!
The pre-resolved MD5 track works now.
However, the following does not.
$ gdb --args ./dzr_static-ubuntu-latest 1022348772
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./dzr_static-ubuntu-latest...(no debugging symbols found)...done.
(gdb) run
Starting program: /mnt/c/Users/Milos/Downloads/dzr_static-ubuntu-latest 1022348772
Program received signal SIGSEGV, Segmentation fault.
0x0000000000401ee0 in ?? ()
(gdb) where
#0 0x0000000000401ee0 in ?? ()
#1 0x0000000000401a37 in ?? ()
#2 0x000000000040049f in ?? ()
#3 0x0000000000401e86 in ?? ()
#4 0x0000000000000000 in ?? ()
(gdb)
Hum I did not included debug symbols while compiling, so the stacktrace is not as helpful as I expected.
Could you run out of gdb but with DZR_DBG=1 ?
I'm pretty sure it's the track MD5 fetching issue (probably because deezer API has changed)
POST /ajax/gw-light.php?method=song.getListData&api_version=1.0&input=3&api_token=********** HTTP/1.0
Host: www.deezer.com
Content-Length: 24
Cookie: sid=fr************************
{"sng_ids":[1022348772]}HTTP/1.0 200 OK
Server: Apache
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
P3P: policyref="/w3c/p3p.xml" CP="IDC DSP COR CURa ADMa OUR IND PHY ONL COM STA"
X-Frame-Options: SAMEORIGIN
x-deezer-client-ip: // omitted
X-Host: blm-web-05
X-UA-Compatible: IE=edge,chrome=1,requiresActiveX=true
X-Content-Type-Options: nosniff
Content-Type: application/json; charset=utf-8
Date: Sat, 28 Nov 2020 17:14:07 GMT
Content-Length: 1724
Connection: close
Set-Cookie: sid=fr********************; path=/; domain=.deezer.com; HttpOnly
x-org: FR
Set-Cookie: // omitted
Set-Cookie: // omitted
After that, this response is printed
{
"error": [],
"results": {
"data": [
{
"SNG_ID": "1022348772",
"ALB_ID": "160914432",
"ALB_PICTURE": "1de89effa1e524e5ca9ed5ee99251b23",
"ALB_TITLE": "The Raging Wrath Of The Easter Bunny Demo",
"ARTISTS": [
{
"ART_ID": "2193",
"ROLE_ID": "0",
"ARTISTS_SONGS_ORDER": "0",
"ART_NAME": "Mr. Bungle",
"ARTIST_IS_DUMMY": false,
"ART_PICTURE": "8bf7975dc16f27aa48b55f1e30e57eaa",
"RANK": "449576",
"LOCALES": {
"lang_ko": { "name": "\ubbf8\uc2a4\ud130 \ubc99\uae00" }
},
"__TYPE__": "artist"
}
],
"ART_ID": "2193",
"ART_NAME": "Mr. Bungle",
"DIGITAL_RELEASE_DATE": "2020-10-30",
"DISK_NUMBER": "1",
"DURATION": "175",
"EXPLICIT_LYRICS": "0",
"EXPLICIT_TRACK_CONTENT": {
"EXPLICIT_LYRICS_STATUS": 0,
"EXPLICIT_COVER_STATUS": 2
},
"GENRE_ID": "0",
"ISRC": "QM5WW1500836",
"LYRICS_ID": 0,
"PHYSICAL_RELEASE_DATE": "2020-10-30",
"PROVIDER_ID": "30",
"RANK_SNG": "100000",
"SMARTRADIO": 0,
"SNG_TITLE": "Grizzly Adams",
"STATUS": 1,
"TRACK_NUMBER": "1",
"TYPE": 0,
"UPLOAD_ID": 0,
"USER_ID": 0,
"VERSION": "",
"MD5_ORIGIN": "4fd6b3089f7eb1eafdecf043b3af8c5c",
"FILESIZE_AAC_64": "0",
"FILESIZE_MP3_64": "0",
"FILESIZE_MP3_128": "2814536",
"FILESIZE_MP3_256": "0",
"FILESIZE_MP3_320": "7036342",
"FILESIZE_MP4_RA1": "0",
"FILESIZE_MP4_RA2": "0",
"FILESIZE_MP4_RA3": "0",
"FILESIZE_FLAC": "0",
"FILESIZE": "2814536",
"GAIN": "-13.7",
"MEDIA_VERSION": "2",
"TRACK_TOKEN": "AAAAAV_ChV9fw56f8aWzoT_x8rPypkxltmJwAvI8uc7oRTOKSB_Wex0_2WfOcr0L6WbW_g6sBjwuOGnVNT57nE2TZr5G4ja4RIiPk3VEibVAnAKTWzqZIwvV4j0eLfUwTp2Yb3NN3mLym1qgeko",
"TRACK_TOKEN_EXPIRE": 1606655647,
"MEDIA": [
{
"TYPE": "preview",
"HREF": "http://cdn-preview-4.deezer.com/stream/c-4451d2d4a9f2e4dfe4446b90d75ae04d-2.mp3"
}
],
"RIGHTS": {
"STREAM_ADS_AVAILABLE": true,
"STREAM_ADS": "2000-01-01",
"STREAM_SUB_AVAILABLE": true,
"STREAM_SUB": "2000-01-01"
},
"__TYPE__": "song"
}
],
"count": 1,
"total": 1,
"filtered_count": 0
}
}
And right after that, the segfault occurs.
And I guess the following works ?
dzr 4fd6b3089f7eb1eafdecf043b3af8c5c:1022348772
Nope.
$ DZR_DBG=1 DZR_SID=none ./dzr_static-ubuntu-latest 4fd6b3089f7eb1eafdecf043b3af8c5c:1022348772
Segmentation fault (core dumped)
$ DZR_DBG=1 ./dzr_static-ubuntu-latest 4fd6b3089f7eb1eafdecf043b3af8c5c:1022348772
Segmentation fault (core dumped)
And just for kicks:
$ DZR_DBG=1 ./dzr-ubuntu-latest 4fd6b3089f7eb1eafdecf043b3af8c5c:1022348772
*** stack smashing detected ***: <unknown> terminated
Aborted (core dumped)
And I am connected now π
$ ping www.google.com
PING www.google.com (172.217.19.100) 56(84) bytes of data.
64 bytes from muc03s07-in-f100.1e100.net (172.217.19.100): icmp_seq=1 ttl=119 time=8.83 ms
64 bytes from muc03s07-in-f100.1e100.net (172.217.19.100): icmp_seq=2 ttl=119 time=8.70 ms
64 bytes from muc03s07-in-f100.1e100.net (172.217.19.100): icmp_seq=3 ttl=119 time=8.70 ms
64 bytes from muc03s07-in-f100.1e100.net (172.217.19.100): icmp_seq=4 ttl=119 time=8.67 ms
^C
--- www.google.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3002ms
rtt min/avg/max/mdev = 8.672/8.728/8.830/0.060 ms
What about the combo dzr_static + DZR_SID=none + pre-MD5 ?
DZR_DBG=1 DZR_SID=none ./dzr_static-ubuntu-latest 4fd6b3089f7eb1eafdecf043b3af8c5c:1022348772
That's the first thing I tried, and it segfaults. :(
None of my friends or colleagues are using Windows and my laptop does not have enough RAM to spin a Win10+WSL virtual machine so if you feel comfortable enough with C I suggest you to build a debug version with gcc -g ...
and gdb
it.
Or alternatively add many fprintf(stderr, "debug at %i\n", __LINE__);
to find out the exact line it segfault
I'll try. I never did C, but I'll try when I get some spare time, and I'll let you know. Thank you for all the help, it's much appreciated!
Hello,
I don't know if I'm writing in the right place, but I read you about the encryption of the tracks. I myself started to reverse engeenering the deezer api, and I also have an encryption problem. It seems that passwords only are hashed by the application before being sent to the server. Looks like they're also encrypted with MD5, but it looks like the sid is also involved. Here is an example.
229efb0a5de006306f559118faa7418c
Is the corresponding hash of
Testtest123
With like sid
frea9add98a4f1b05eda6dd06dc2ed0a492ed653
Do you have any idea of ββthe process used for the encryption?
Hi,
The track decryption has never changed since deezer was created: it's always MD5 + trackid + DZR_CBC key
The only things that changed is how to retreive these 3 values for the decryption.
the old method still works. So what is the point?
Given all the security design mistakes they made so far (and they buggy react website), I won't say that deezer devs are smart people.
But if they were, they would probably kill the old/unrestricted API and only use they token+CDN one, which IMO is the best design they could come up with...
props to them for that...
I'm not bootlicking....
... (deezer please hire me).
the old method still works. So what is the point?
They measure the number of pirates. But pssst!
(A more likely possibility maybe that they do not want to lose paying users who have not updated their desktop client for a while. (?))
They also seems to support some smart-speaker which (I guess), are not so easy to migrate to the new API ?
yes, FLAC quality require a even higher "Hi-Fi" level.
I'm halfway there in my c -> shell migration.
I re-implemented the deezer bf-cbc-stripe
decoder in pure shell (openssl
is required for bf-cbc decryption part)
Now I just need to also implement the trackId => cdn_url
to finish the job
dzr-dec
#!/bin/sh
track_id=$1
dzr_cbc_hex=$(printf "$DZR_CBC" | hexdump -e '16/1 "%02x"')
track_md5_l=$(printf "$track_id" | openssl md5 -r|cut -b1-16 | hexdump -e '16/1 "%02x"')
track_md5_r=$(printf "$track_id" | openssl md5 -r|cut -b17-32 | hexdump -e '16/1 "%02x"')
track_key=$(for k in $(seq 1 2 32); do
a=$(printf $dzr_cbc_hex | cut -b $k-$(($k+1)))
b=$(printf $track_md5_l | cut -b $k-$(($k+1)))
c=$(printf $track_md5_r | cut -b $k-$(($k+1)))
printf '%02x' "$((0x$a ^ 0x$b ^ 0x$c))"
done)
stripe_size=2048
while true; do
dd bs=$stripe_size count=1 status=none | openssl bf-cbc -nopad -bufsize $stripe_size -K $track_key -iv 0001020304050607 -d 1>&4
{ LC_ALL=POSIX dd bs=$stripe_size count=2 2>&3 >&4; } 3>&1 | grep -qe '^0[+]0 ' && break
done 4>&1
Usage:
# Note: DZR_CBC must be set for dzr-dec
wget -qO- http://e-cdn-proxy-c.deezer.com/mobile/1/54EBDBF1268A9912B99F12BBDDD3DACBB1E9255A8BDBF4C3AA05645E0C5FC96FCB5A7A65072DAC6B0B3A7E30B10033C34520ED85B94ED597A131D97425D6B17BA3AC13C1119D23A3EF4B3B85EF0345F1 | ./dzr-dec 105579760 | mpv -
Have you considered something like PHP?
For creating a CLI player ? definitely not :)
It has builtin functions for MD5 and OpenSSL:
Just like every languages.
I've created dzr because I'm tired of all the project that rely on a 100MB electron app to wget
a mp3.
So my goal was to create a simple program thats "runs everywhere" (even on PSP/PSVita). Hence the static binaries that a dependency-free.
But the API is changing too fast for a C project. So I'm switching to a pure shell version (windows user will have to use WSL but they are not my main target, and vice versa).
I can help with the code if you are interested.
Yep ! I'm trying to find a way to get the MD5_SUM without an account. If you have any info on that subject I'm interested :).
Yeah, you can do this with
deezer.ping
. But onlyMP3_128
will be available.
Do you miss DZR_FMT=9 to get your FLAC ?
I just need to add caching + track bursting to this snippet and we'll be good
function gw(){ curl -s "https://www.deezer.com/ajax/gw-light.php?method=$1&input=3&api_version=1.0&api_token=$3" -H "cookie: sid=$2" $@;}
TRK_IDS=796309342
DZR_URL="www.deezer.com/ajax/gw-light.php?method=deezer.ping&api_version=1.0&api_token"
DZR_SID=$(curl -s "$DZR_URL" | jq -r .results.SESSION)
USR_NFO=$(gw deezer.getUserData $DZR_SID)
USR_TOK=$(jq -r .results.USER_TOKEN <<< $USR_NFO)
USR_LIC=$(jq -r .results.USER.OPTIONS.license_token <<< $USR_NFO)
API_TOK=$(jq -r .results.checkForm <<< $USR_NFO)
TRK_NFO=$(gw song.getListData $DZR_SID $API_TOK --data-binary '{"sng_ids":['"$TRK_IDS"']}')
TRK_TOK=$(jq -r .results.data[].TRACK_TOKEN <<< "$TRK_NFO")
curl 'https://media.deezer.com/v1/get_url' --data-binary '{"license_token":"'$USR_LIC'","media":[{"type":"FULL","formats":[{"cipher":"BF_CBC_STRIPE","format":"MP3_128"}]}],"track_tokens":["'$TRK_TOK'"]}'
Yeah I know, but are you sad from not having FLAC ?
Anyway you can still fetch them from the old API if you have the track MD5, but they will probably remove this API soon or later
$ wget -qO- http://e-cdn-proxy-5.deezer.com/mobile/1/051A0D9CB84625B6DF14D0EAE76C3F3D209866510365835BEA5EDAC36A51C47D52CF4A7F90843C3D7BEFAD851C91B0352F8AD3284ABDB46125E85F642E6B6C708E60F3461A3748635DC8FD29A059C309 | ./dzr-dec 16235816 | mpv -
[file] Reading from stdin...
(+) Audio --aid=1 (flac 2ch 44100Hz)
AO: [pulse] 44100Hz stereo 2ch s16
A: 00:00:12 / 00:04:34 (4%) Cache: 133s/15MB
The new API is now ready and working in the last version (in pure bash + openssl).
No ARL/API_KEY required, only DZR_CBC
yeah, better get all your FLAC before the old "MD5" API is removed ;)
I might create another dzr-url-old
wich still get URL for FLAC/MP3_320 but I wouldn't waste much time on since its going to be deprecated.