diff --git a/android/app/src/main/java/r/r/refreezer/DeezerDecryptor.java b/android/app/src/main/java/r/r/refreezer/DeezerDecryptor.java index 8fc7e64..5515712 100644 --- a/android/app/src/main/java/r/r/refreezer/DeezerDecryptor.java +++ b/android/app/src/main/java/r/r/refreezer/DeezerDecryptor.java @@ -9,13 +9,32 @@ import java.security.MessageDigest; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; +import javax.crypto.spec.IvParameterSpec; public class DeezerDecryptor { + private final Cipher cipher; - public static void decryptFile(String trackId, String inputFilename, String outputFilename) throws IOException { + /** + * Constructor initializes the key and cipher for the given track ID. + * @param trackId Track ID used to generate decryption key + * @throws Exception If there is an issue initializing the cipher + */ + public DeezerDecryptor(String trackId) throws Exception { + this.cipher = Cipher.getInstance("Blowfish/CBC/NoPadding"); + byte[] IV = {00, 01, 02, 03, 04, 05, 06, 07}; + SecretKeySpec Skey = new SecretKeySpec(getKey(trackId), "Blowfish"); + cipher.init(Cipher.DECRYPT_MODE, Skey, new IvParameterSpec(IV)); + } + + /** + * Decrypts a file by reading it in chunks and decrypting every 3rd chunk of exactly 2048 bytes. + * @param inputFilename The input file to decrypt + * @param outputFilename The output file to write the decrypted data + * @throws IOException If an I/O error occurs + */ + public void decryptFile(String inputFilename, String outputFilename) throws IOException { try (FileInputStream fis = new FileInputStream(inputFilename); FileOutputStream fos = new FileOutputStream(outputFilename)) { - byte[] key = getKey(trackId); byte[] buffer = new byte[2048]; int bytesRead; int chunkCounter = 0; @@ -23,7 +42,7 @@ public class DeezerDecryptor { while ((bytesRead = fis.read(buffer)) != -1) { // Only every 3rd chunk of exactly 2048 bytes should be decrypted if (bytesRead == 2048 && (chunkCounter % 3) == 0) { - buffer = decryptChunk(key, buffer); + buffer = decryptChunk(buffer); } fos.write(buffer, 0, bytesRead); chunkCounter++; @@ -33,6 +52,11 @@ public class DeezerDecryptor { } } + /** + * Converts a byte array to a hexadecimal string. + * @param bytes Byte array to convert + * @return Hexadecimal string representation of the byte array + */ public static String bytesToHex(byte[] bytes) { final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); char[] hexChars = new char[bytes.length * 2]; @@ -47,7 +71,7 @@ public class DeezerDecryptor { /** * Generates the Track decryption key based on the provided track ID and a secret. * @param id Track ID used to generate decryption key - * @return Decryption key for Track + * @return Decryption key for Track */ static byte[] getKey(String id) { final String secret = "g4el58wc0zvf9na1"; @@ -57,11 +81,11 @@ public class DeezerDecryptor { byte[] md5id = md5.digest(); String idmd5 = bytesToHex(md5id).toLowerCase(); String key = ""; - for(int i=0; i<16; i++) { + for (int i = 0; i < 16; i++) { int s0 = idmd5.charAt(i); - int s1 = idmd5.charAt(i+16); + int s1 = idmd5.charAt(i + 16); int s2 = secret.charAt(i); - key += (char)(s0^s1^s2); + key += (char) (s0 ^ s1 ^ s2); } return key.getBytes(); } catch (Exception e) { @@ -70,25 +94,37 @@ public class DeezerDecryptor { } } + /** + * Decrypts a 2048-byte chunk of data using the pre-initialized Blowfish cipher. + * @param data 2048-byte chunk of data to decrypt + * @return Decrypted 2048-byte chunk + */ + private byte[] decryptChunk(byte[] data) { + try { + return cipher.doFinal(data); + } catch (Exception e) { + Log.e("D", e.toString()); + return new byte[0]; + } + } + /** * Decrypts a 2048-byte chunk of data using the Blowfish algorithm in CBC mode with no padding. * The decryption key and the initial vector (IV) are used to decrypt the data. * @param key Track key * @param data 2048-byte chunk of data to decrypt * @return Decrypted 2048-byte chunk - * */ - static byte[] decryptChunk(byte[] key, byte[] data) { + public static byte[] decryptChunk(byte[] key, byte[] data) { try { byte[] IV = {00, 01, 02, 03, 04, 05, 06, 07}; SecretKeySpec Skey = new SecretKeySpec(key, "Blowfish"); Cipher cipher = Cipher.getInstance("Blowfish/CBC/NoPadding"); - cipher.init(Cipher.DECRYPT_MODE, Skey, new javax.crypto.spec.IvParameterSpec(IV)); + cipher.init(Cipher.DECRYPT_MODE, Skey, new IvParameterSpec(IV)); return cipher.doFinal(data); - }catch (Exception e) { + } catch (Exception e) { Log.e("D", e.toString()); return new byte[0]; } } - } \ No newline at end of file diff --git a/android/app/src/main/java/r/r/refreezer/DownloadService.java b/android/app/src/main/java/r/r/refreezer/DownloadService.java index e8dfd34..ee7b4fd 100644 --- a/android/app/src/main/java/r/r/refreezer/DownloadService.java +++ b/android/app/src/main/java/r/r/refreezer/DownloadService.java @@ -447,7 +447,8 @@ public class DownloadService extends Service { if (qualityInfo.encrypted) { try { File decFile = new File(tmpFile.getPath() + ".DEC"); - DeezerDecryptor.decryptFile(download.streamTrackId, tmpFile.getPath(), decFile.getPath()); + DeezerDecryptor decryptor = new DeezerDecryptor(download.streamTrackId); + decryptor.decryptFile(tmpFile.getPath(), decFile.getPath()); tmpFile.delete(); tmpFile = decFile; } catch (Exception e) {