use blowfish::cipher::{BlockDecrypt, KeyInit}; use blowfish::Blowfish; use byteorder::BigEndian; /// Generate Blowfish key for Deezer track decryption pub fn generate_blowfish_key(track_id: &str) -> Vec { const SECRET: &[u8] = b"g4el58wc0zvf9na1"; // MD5 hash of track ID let id_md5 = format!("{:x}", md5::compute(track_id.as_bytes())); // Generate 16-byte key by XORing MD5 parts with secret let mut bf_key = Vec::with_capacity(16); for i in 0..16 { let md5_byte1 = id_md5.as_bytes()[i]; let md5_byte2 = id_md5.as_bytes()[i + 16]; let secret_byte = SECRET[i]; bf_key.push(md5_byte1 ^ md5_byte2 ^ secret_byte); } bf_key } /// Decrypt a single 2048-byte chunk using Blowfish CBC pub fn decrypt_chunk(chunk: &[u8], blowfish_key: &[u8]) -> Vec { let cipher = Blowfish::::new_from_slice(blowfish_key) .expect("Invalid key length"); let mut result = chunk.to_vec(); let iv = [0u8, 1, 2, 3, 4, 5, 6, 7]; // Decrypt in CBC mode let mut prev_block = iv; for chunk_idx in (0..result.len()).step_by(8) { if chunk_idx + 8 <= result.len() { let mut block = [0u8; 8]; block.copy_from_slice(&result[chunk_idx..chunk_idx + 8]); let encrypted_block = block; cipher.decrypt_block((&mut block).into()); // XOR with previous ciphertext (CBC mode) for i in 0..8 { block[i] ^= prev_block[i]; } result[chunk_idx..chunk_idx + 8].copy_from_slice(&block); prev_block = encrypted_block; } } result } /// Decrypt track data using Deezer's encryption scheme /// Every 3 chunks (6144 bytes), only the first 2048 bytes are encrypted pub fn decrypt_track(data: &[u8], track_id: &str) -> Vec { const CHUNK_SIZE: usize = 2048; const WINDOW_SIZE: usize = CHUNK_SIZE * 3; // 6144 let blowfish_key = generate_blowfish_key(track_id); let mut result = Vec::with_capacity(data.len()); let mut offset = 0; while offset < data.len() { let remaining = data.len() - offset; if remaining >= WINDOW_SIZE { // Full window: decrypt first 2048 bytes, keep next 4096 as-is let encrypted_chunk = &data[offset..offset + CHUNK_SIZE]; let plain_part = &data[offset + CHUNK_SIZE..offset + WINDOW_SIZE]; let decrypted = decrypt_chunk(encrypted_chunk, &blowfish_key); result.extend_from_slice(&decrypted); result.extend_from_slice(plain_part); offset += WINDOW_SIZE; } else if remaining >= CHUNK_SIZE { // Partial window: decrypt first 2048 bytes, keep rest as-is let encrypted_chunk = &data[offset..offset + CHUNK_SIZE]; let plain_part = &data[offset + CHUNK_SIZE..]; let decrypted = decrypt_chunk(encrypted_chunk, &blowfish_key); result.extend_from_slice(&decrypted); result.extend_from_slice(plain_part); offset = data.len(); } else { // Less than 2048 bytes: keep as-is result.extend_from_slice(&data[offset..]); offset = data.len(); } } result } /// Streaming decryption state for processing data chunk-by-chunk pub struct StreamingDecryptor { blowfish_key: Vec, buffer: Vec, } impl StreamingDecryptor { const CHUNK_SIZE: usize = 2048; const WINDOW_SIZE: usize = Self::CHUNK_SIZE * 3; // 6144 pub fn new(track_id: &str) -> Self { Self { blowfish_key: generate_blowfish_key(track_id), buffer: Vec::new(), } } /// Process incoming data and return decrypted output /// May return less data than input as it buffers to maintain 6144-byte windows pub fn process(&mut self, data: &[u8]) -> Vec { self.buffer.extend_from_slice(data); let mut output = Vec::new(); // Process complete windows while self.buffer.len() >= Self::WINDOW_SIZE { let encrypted_chunk = &self.buffer[0..Self::CHUNK_SIZE]; let plain_part = &self.buffer[Self::CHUNK_SIZE..Self::WINDOW_SIZE]; let decrypted = decrypt_chunk(encrypted_chunk, &self.blowfish_key); output.extend_from_slice(&decrypted); output.extend_from_slice(plain_part); self.buffer.drain(0..Self::WINDOW_SIZE); } output } /// Finalize decryption and return any remaining buffered data pub fn finalize(self) -> Vec { if self.buffer.is_empty() { return Vec::new(); } let remaining = self.buffer.len(); if remaining >= Self::CHUNK_SIZE { // Partial window: decrypt first 2048 bytes, keep rest as-is let encrypted_chunk = &self.buffer[0..Self::CHUNK_SIZE]; let plain_part = &self.buffer[Self::CHUNK_SIZE..]; let decrypted = decrypt_chunk(encrypted_chunk, &self.blowfish_key); let mut output = Vec::with_capacity(remaining); output.extend_from_slice(&decrypted); output.extend_from_slice(plain_part); output } else { // Less than 2048 bytes: keep as-is self.buffer } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_key_generation() { let key = generate_blowfish_key("99756342"); assert_eq!(key.len(), 16); // Key should be: 3333676c346d7c62372b7c3e6d32626c (in hex) assert_eq!(format!("{:02x}", key[0]), "33"); assert_eq!(format!("{:02x}", key[1]), "33"); } }