first commit

This commit is contained in:
cool.gitter.not.me.again.duh
2025-05-31 15:51:18 -06:00
commit 089cb3dc5a
34 changed files with 9012 additions and 0 deletions

View File

@@ -0,0 +1,448 @@
#!/usr/bin/python3
from hashlib import md5 as __md5
from binascii import (
a2b_hex as __a2b_hex,
b2a_hex as __b2a_hex
)
from Crypto.Cipher.Blowfish import (
new as __newBlowfish,
MODE_CBC as __MODE_CBC
)
from Crypto.Cipher import AES
from Crypto.Util import Counter
import os
from deezspot.libutils.logging_utils import logger
__secret_key = "g4el58wc0zvf9na1"
__secret_key2 = b"jo6aey6haid2Teih"
__idk = __a2b_hex("0001020304050607")
def md5hex(data: str):
hashed = __md5(
data.encode()
).hexdigest()
return hashed
def gen_song_hash(song_id, song_md5, media_version):
"""
Generate a hash for the song using its ID, MD5 and media version.
Args:
song_id: The song's ID
song_md5: The song's MD5 hash
media_version: The media version
Returns:
str: The generated hash
"""
try:
# Combine the song data
data = f"{song_md5}{media_version}{song_id}"
# Generate hash using SHA1
import hashlib
hash_obj = hashlib.sha1()
hash_obj.update(data.encode('utf-8'))
return hash_obj.hexdigest()
except Exception as e:
logger.error(f"Failed to generate song hash: {str(e)}")
raise
def __calcbfkey(songid):
"""
Calculate the Blowfish decrypt key for a given song ID.
Args:
songid: String song ID
Returns:
The Blowfish decryption key
"""
try:
h = md5hex(songid)
logger.debug(f"MD5 hash of song ID '{songid}': {h}")
# Build the key through XOR operations as per Deezer's algorithm
bfkey = "".join(
chr(
ord(h[i]) ^ ord(h[i + 16]) ^ ord(__secret_key[i])
)
for i in range(16)
)
# Log the generated key in hex format for debugging
logger.debug(f"Generated Blowfish key: {bfkey.encode().hex()}")
return bfkey
except Exception as e:
logger.error(f"Error calculating Blowfish key: {str(e)}")
raise
def __blowfishDecrypt(data, key):
"""
Decrypt a single block of data using Blowfish in CBC mode.
Args:
data: The encrypted data block (must be a multiple of 8 bytes)
key: The Blowfish key as a string
Returns:
The decrypted data
"""
try:
# Ensure data is a multiple of Blowfish block size (8 bytes)
if len(data) % 8 != 0:
logger.warning(f"Data length {len(data)} is not a multiple of 8 bytes - Blowfish requires 8-byte blocks")
# Pad data to a multiple of 8 if needed (though this should be avoided)
padding = 8 - (len(data) % 8)
data += b'\x00' * padding
logger.warning(f"Padded data with {padding} null bytes")
# Create Blowfish cipher in CBC mode with initialization vector
c = __newBlowfish(
key.encode(), __MODE_CBC, __idk
)
# Decrypt the data
decrypted = c.decrypt(data)
logger.debug(f"Decrypted {len(data)} bytes of data")
return decrypted
except Exception as e:
logger.error(f"Error in Blowfish decryption: {str(e)}")
raise
def decrypt_blowfish_track(crypted_audio, song_id, md5_origin, song_path):
"""
Decrypt the audio file using Blowfish encryption.
Args:
crypted_audio: The encrypted audio data
song_id: The song ID for generating the key
md5_origin: The MD5 hash of the track
song_path: Path where to save the decrypted file
"""
try:
# Calculate the Blowfish key
bf_key = __calcbfkey(song_id)
# For debugging - log the key being used
logger.debug(f"Using Blowfish key for decryption: {bf_key.encode().hex()}")
# Prepare to process the file
block_size = 2048 # Size of each block to process
# We need to reconstruct the data from potentially variable-sized chunks into
# fixed-size blocks for proper decryption
buffer = bytearray()
block_count = 0 # Count of completed blocks
# Open the output file
with open(song_path, 'wb') as output_file:
# Process each incoming chunk of data
for chunk in crypted_audio:
if not chunk:
continue
# Add current chunk to our buffer
buffer.extend(chunk)
# Process as many complete blocks as we can
while len(buffer) >= block_size:
# Extract a block from buffer
block = buffer[:block_size]
buffer = buffer[block_size:]
# Only decrypt every third block
is_encrypted = (block_count % 3 == 0)
if is_encrypted:
# Ensure the block is a multiple of 8 bytes (Blowfish block size)
if len(block) == block_size and len(block) % 8 == 0:
try:
# Create a fresh cipher with the initialization vector for each block
# This is crucial - we need to reset the IV for each encrypted block
cipher = __newBlowfish(bf_key.encode(), __MODE_CBC, __idk)
# Decrypt the block
block = cipher.decrypt(block)
logger.debug(f"Decrypted block {block_count} (size: {len(block)})")
except Exception as e:
logger.error(f"Failed to decrypt block {block_count}: {str(e)}")
# Continue with the encrypted block rather than failing completely
# Write the block (decrypted or not) to the output file
output_file.write(block)
block_count += 1
# Write any remaining data in the buffer (this won't be decrypted as it's a partial block)
if buffer:
logger.debug(f"Writing final partial block of size {len(buffer)}")
output_file.write(buffer)
logger.debug(f"Successfully decrypted and saved Blowfish-encrypted file to {song_path}")
except Exception as e:
logger.error(f"Failed to decrypt Blowfish file: {str(e)}")
raise
def decryptfile(crypted_audio, ids, song_path):
"""
Decrypt the audio file using either AES or Blowfish encryption.
Args:
crypted_audio: The encrypted audio data
ids: The track IDs containing encryption info
song_path: Path where to save the decrypted file
"""
try:
# Check encryption type
encryption_type = ids.get('encryption_type', 'aes')
# Check if this is a FLAC file based on file extension
is_flac = song_path.lower().endswith('.flac')
if encryption_type == 'aes':
# Get the AES encryption key and nonce
key = bytes.fromhex(ids['key'])
nonce = bytes.fromhex(ids['nonce'])
# For AES-CTR, we can decrypt chunk by chunk
counter = Counter.new(128, initial_value=int.from_bytes(nonce, byteorder='big'))
cipher = AES.new(key, AES.MODE_CTR, counter=counter)
# Open the output file
with open(song_path, 'wb') as f:
# Process the data in chunks
for chunk in crypted_audio:
if chunk:
# Decrypt the chunk and write to file
decrypted_chunk = cipher.decrypt(chunk)
f.write(decrypted_chunk)
logger.debug(f"Successfully decrypted and saved AES-encrypted file to {song_path}")
elif encryption_type == 'blowfish':
# Customize Blowfish decryption based on file type
if is_flac:
logger.debug("Detected FLAC file - using special FLAC decryption handling")
decrypt_blowfish_flac(
crypted_audio,
str(ids['track_id']),
ids['md5_origin'],
song_path
)
else:
# Use standard Blowfish decryption for MP3
decrypt_blowfish_track(
crypted_audio,
str(ids['track_id']),
ids['md5_origin'],
song_path
)
else:
raise ValueError(f"Unknown encryption type: {encryption_type}")
except Exception as e:
logger.error(f"Failed to decrypt file: {str(e)}")
raise
def decrypt_blowfish_flac(crypted_audio, song_id, md5_origin, song_path):
"""
Special decryption function for FLAC files using Blowfish encryption.
This implementation follows Deezer's encryption scheme exactly.
In Deezer's encryption scheme:
- Data is processed in 2048-byte blocks
- Only every third block is encrypted (blocks 0, 3, 6, etc.)
- Partial blocks at the end of the file are not encrypted
- FLAC file structure must be preserved exactly
- The initialization vector is reset for each encrypted block
Args:
crypted_audio: Iterator of the encrypted audio data chunks
song_id: The song ID for generating the key
md5_origin: The MD5 hash of the track
song_path: Path where to save the decrypted file
"""
try:
# Calculate the Blowfish key
bf_key = __calcbfkey(song_id)
# For debugging - log the key being used
logger.debug(f"Using Blowfish key for decryption: {bf_key.encode().hex()}")
# Prepare to process the file
block_size = 2048 # Size of each block to process
# We need to reconstruct the data from potentially variable-sized chunks into
# fixed-size blocks for proper decryption
buffer = bytearray()
block_count = 0 # Count of completed blocks
# Open the output file
with open(song_path, 'wb') as output_file:
# Process each incoming chunk of data
for chunk in crypted_audio:
if not chunk:
continue
# Add current chunk to our buffer
buffer.extend(chunk)
# Process as many complete blocks as we can
while len(buffer) >= block_size:
# Extract a block from buffer
block = buffer[:block_size]
buffer = buffer[block_size:]
# Determine if this block should be decrypted (every third block)
if block_count % 3 == 0:
# Ensure we have a complete block for decryption and it's a multiple of 8 bytes
if len(block) == block_size and len(block) % 8 == 0:
try:
# Create a fresh cipher with the initialization vector for each block
# This is crucial - we need to reset the IV for each encrypted block
cipher = __newBlowfish(bf_key.encode(), __MODE_CBC, __idk)
# Decrypt the block
block = cipher.decrypt(block)
logger.debug(f"Decrypted block {block_count} (size: {len(block)})")
except Exception as e:
logger.error(f"Failed to decrypt block {block_count}: {str(e)}")
# Continue with the encrypted block rather than failing completely
# Write the block (decrypted or not) to the output file
output_file.write(block)
block_count += 1
# Write any remaining data in the buffer (this won't be decrypted as it's a partial block)
if buffer:
logger.debug(f"Writing final partial block of size {len(buffer)}")
output_file.write(buffer)
# Final validation
if os.path.getsize(song_path) > 0:
with open(song_path, 'rb') as f:
if f.read(4) == b'fLaC':
logger.info(f"FLAC file header verification passed")
else:
logger.warning("FLAC file doesn't begin with proper 'fLaC' signature")
logger.info(f"Successfully decrypted FLAC file to {song_path} ({os.path.getsize(song_path)} bytes)")
# Run the detailed analysis
analysis = analyze_flac_file(song_path)
if analysis.get("potential_issues"):
logger.warning(f"Decryption completed but analysis found issues: {analysis['potential_issues']}")
else:
logger.info("FLAC analysis indicates the file structure is valid")
else:
logger.error("Decrypted file is empty - decryption likely failed")
except Exception as e:
logger.error(f"Failed to decrypt Blowfish FLAC file: {str(e)}")
raise
def analyze_flac_file(file_path, limit=100):
"""
Analyze a FLAC file at the binary level for debugging purposes.
This function helps identify issues with file structure that might cause
playback problems.
Args:
file_path: Path to the FLAC file
limit: Maximum number of blocks to analyze
Returns:
A dictionary with analysis results
"""
try:
results = {
"file_size": 0,
"has_flac_signature": False,
"block_structure": [],
"metadata_blocks": 0,
"potential_issues": []
}
if not os.path.exists(file_path):
results["potential_issues"].append("File does not exist")
return results
# Get file size
file_size = os.path.getsize(file_path)
results["file_size"] = file_size
if file_size < 8:
results["potential_issues"].append("File too small to be a valid FLAC")
return results
with open(file_path, 'rb') as f:
# Check FLAC signature (first 4 bytes should be 'fLaC')
header = f.read(4)
results["has_flac_signature"] = (header == b'fLaC')
if not results["has_flac_signature"]:
results["potential_issues"].append(f"Missing FLAC signature. Found: {header}")
# Read and analyze metadata blocks
# FLAC format: https://xiph.org/flac/format.html
try:
# Go back to position after signature
f.seek(4)
# Read metadata blocks
last_block = False
block_count = 0
while not last_block and block_count < limit:
block_header = f.read(4)
if len(block_header) < 4:
break
# First bit of first byte indicates if this is the last metadata block
last_block = (block_header[0] & 0x80) != 0
# Last 7 bits of first byte indicate block type
block_type = block_header[0] & 0x7F
# Next 3 bytes indicate length of block data
block_length = (block_header[1] << 16) | (block_header[2] << 8) | block_header[3]
# Record block info
block_info = {
"position": f.tell() - 4,
"type": block_type,
"length": block_length,
"is_last": last_block
}
results["block_structure"].append(block_info)
# Skip to next block
f.seek(block_length, os.SEEK_CUR)
block_count += 1
results["metadata_blocks"] = block_count
# Check for common issues
if block_count == 0:
results["potential_issues"].append("No metadata blocks found")
# Check for STREAMINFO block (type 0) which should be present
has_streaminfo = any(block["type"] == 0 for block in results["block_structure"])
if not has_streaminfo:
results["potential_issues"].append("Missing STREAMINFO block")
except Exception as e:
results["potential_issues"].append(f"Error analyzing metadata: {str(e)}")
return results
except Exception as e:
logger.error(f"Error analyzing FLAC file: {str(e)}")
return {"error": str(e)}