char-slop/tidal-dl
tidal ripper
git clone https://git.t4t.associates/char-slop/tidal-dl
3841d9b
main
1use aes::{ Aes128 , Aes256 }; 2use anyhow::{ Context , Result }; 3use cbc:: Decryptor ; 4use cipher::{ block_padding:: NoPadding , BlockDecryptMut , KeyIvInit , StreamCipher }; 5 6const MASTER_KEY : [ u8 ; 32 ] =* include_bytes! ( "../assets/master.key.bin" ); 7 8/// Split a TIDAL `keyId` security token into the (key, nonce) pair. 9pub fn decrypt_security_token ( token : & str ) ->Result <([ u8 ; 16 ], [ u8 ; 8 ])> { 10use base64::{ engine:: general_purpose:: STANDARD , Engine as _}; 11let bytes =STANDARD . decode ( token). context ( "security token base64" ) ?; 12 anyhow:: ensure!( bytes. len () >16 &&( bytes. len () -16 ) %16 ==0 , "bad security token length" ); 13let ( iv, cipher) = bytes. split_at ( 16 ); 14let mut buf = cipher. to_vec (); 15Decryptor ::< Aes256 >:: new_from_slices ( & MASTER_KEY , iv) 16. map_err ( |e| anyhow:: anyhow!( "key/iv: {e}" )) ? 17. decrypt_padded_mut ::< NoPadding >( & mut buf) 18. map_err ( |e| anyhow:: anyhow!( "security token decrypt: {e}" )) ?; 19 anyhow:: ensure!( buf. len () >=24 , "decrypted token too short" ); 20Ok (( buf[ ..16 ]. try_into (). unwrap (), buf[ 16 ..24 ]. try_into (). unwrap ())) 21} 22 23/// AES-128-CTR decrypt (nonce || big-endian counter, matching pycryptodome's 24/// `Counter.new(64, prefix=nonce)`). 25pub fn decrypt_bytes ( data : Vec < u8 >, key : & [ u8 ; 16 ], nonce : & [ u8 ; 8 ]) ->Vec < u8 > { 26let mut iv =[ 0u8 ; 16 ]; 27 iv[ ..8 ]. copy_from_slice ( nonce); 28let mut cipher = ctr:: Ctr128BE ::< Aes128 >:: new_from_slices ( key, & iv). expect ( "ctr key/iv" ); 29let mut buf = data; 30 cipher. apply_keystream ( & mut buf); 31 buf 32} 33 34# [ cfg ( test )] 35mod tests{ 36use super :: * ; 37use cipher::{ block_padding:: NoPadding , BlockEncryptMut }; 38 39# [ test ] 40fn security_token_round_trip () { 41let key =* b"0123456789abcdef" ; 42let nonce =* b"12345678" ; 43let mut plain =Vec :: new (); 44 plain. extend_from_slice ( & key); 45 plain. extend_from_slice ( & nonce); 46 plain. extend_from_slice ( & [ 0u8 ; 8 ]); 47let iv =[ 7u8 ; 16 ]; 48let mut buf = plain. clone (); 49 cbc:: Encryptor ::< Aes256 >:: new_from_slices ( & MASTER_KEY , & iv) 50. unwrap () 51. encrypt_padded_mut ::< NoPadding >( & mut buf, plain. len ()) 52. unwrap (); 53let mut token_raw = iv. to_vec (); 54 token_raw. extend_from_slice ( & buf); 55use base64::{ engine:: general_purpose:: STANDARD , Engine as _}; 56let ( k, n) =decrypt_security_token ( & STANDARD . encode ( token_raw)). unwrap (); 57assert_eq! ( k, key); 58assert_eq! ( n, nonce); 59} 60 61# [ test ] 62fn ctr_round_trip () { 63let key =* b"0123456789abcdef" ; 64let nonce =* b"12345678" ; 65let data =b"flac audio bytes..." . to_vec (); 66let enc ={ 67let mut iv =[ 0u8 ; 16 ]; 68 iv[ ..8 ]. copy_from_slice ( & nonce); 69let mut c = ctr:: Ctr128BE ::< Aes128 >:: new (( & key). into (), ( & iv). into ()); 70let mut b = data. clone (); 71 c. apply_keystream ( & mut b); 72 b 73}; 74assert_eq! ( decrypt_bytes ( enc, & key, & nonce), data); 75} 76}