diff --git a/SMLoadr.js b/SMLoadr.js index 3d0b00b..249cdab 100644 --- a/SMLoadr.js +++ b/SMLoadr.js @@ -20,7 +20,6 @@ require('./node_modules/cache-manager/lib/stores/memory'); const requestPlus = require('request-plus'); const id3Writer = require('./libs/browser-id3-writer'); const flacMetadata = require('./libs/flac-metadata'); -const crypto = require('crypto'); const inquirer = require('inquirer'); const fs = require('fs'); const stream = require('stream'); @@ -37,6 +36,9 @@ const configFile = 'SMLoadrConfig.json'; const ConfigService = require('./src/service/ConfigService'); let configService = new ConfigService(configFile); +const EncryptionService = require('./src/service/EncryptionService'); +let encryptionService = new EncryptionService(); + const Log = require('log'); let DOWNLOAD_DIR = 'DOWNLOADS/'; @@ -1973,15 +1975,9 @@ function sanitizeFilename(fileName) { * @returns {String} */ function getTrackDownloadUrl(trackInfos, trackQuality) { - const step1 = [trackInfos.MD5_ORIGIN, trackQuality, trackInfos.SNG_ID, trackInfos.MEDIA_VERSION].join('¤'); - - let step2 = crypto.createHash('md5').update(step1, 'ascii').digest('hex') + '¤' + step1 + '¤'; - while (step2.length % 16 > 0) step2 += ' '; - - const step3 = crypto.createCipheriv('aes-128-ecb', 'jo6aey6haid2Teih', '').update(step2, 'ascii', 'hex'); const cdn = trackInfos.MD5_ORIGIN[0]; - return 'https://e-cdns-proxy-' + cdn + '.dzcdn.net/mobile/1/' + step3; + return 'https://e-cdns-proxy-' + cdn + '.dzcdn.net/mobile/1/' + encryptionService.getSongFileName(trackInfos, trackQuality); } /** @@ -2072,62 +2068,6 @@ function getValidTrackQuality(trackInfos) { return false; } -/** - * Calculate the blowfish key to decrypt the track - * - * @param {Object} trackInfos - */ -function getBlowfishKey(trackInfos) { - const SECRET = 'g4el58wc0zvf9na1'; - - const idMd5 = crypto.createHash('md5').update(trackInfos.SNG_ID.toString(), 'ascii').digest('hex'); - let bfKey = ''; - - for (let i = 0; i < 16; i++) { - bfKey += String.fromCharCode(idMd5.charCodeAt(i) ^ idMd5.charCodeAt(i + 16) ^ SECRET.charCodeAt(i)); - } - - return bfKey; -} - -/** - * Decrypt a deezer track. - * - * @param {Buffer} trackBuffer - * @param {Object} trackInfos - * - * @return {Buffer} - */ -function decryptTrack(trackBuffer, trackInfos) { - const blowFishKey = getBlowfishKey(trackInfos); - - let decryptedBuffer = Buffer.alloc(trackBuffer.length); - let chunkSize = 2048; - let progress = 0; - - while (progress < trackBuffer.length) { - if ((trackBuffer.length - progress) < 2048) { - chunkSize = trackBuffer.length - progress; - } - - let encryptedChunk = trackBuffer.slice(progress, progress + chunkSize); - - // Only decrypt every third chunk and only if not at the end - if (progress % (chunkSize * 3) === 0 && chunkSize === 2048) { - let cipher = crypto.createDecipheriv('bf-cbc', blowFishKey, Buffer.from([0, 1, 2, 3, 4, 5, 6, 7])); - cipher.setAutoPadding(false); - - encryptedChunk = Buffer.concat([cipher.update(encryptedChunk), cipher.final()]); - } - - decryptedBuffer.write(encryptedChunk.toString('binary'), progress, encryptedChunk.length, 'binary'); - - progress += chunkSize; - } - - return decryptedBuffer; -} - /** * Download the track, decrypt it and write it to a file. * @@ -2150,7 +2090,7 @@ function downloadTrack(trackInfos, trackQualityId, saveFilePath, numberRetry = 0 }).then((response) => { log.debug('Got download response for "track/' + trackInfos.SNG_ID + '"'); - const decryptedTrackBuffer = decryptTrack(response, trackInfos); + const decryptedTrackBuffer = encryptionService.decryptTrack(response, trackInfos); resolve(decryptedTrackBuffer); }).catch((err) => { diff --git a/src/service/EncryptionService.js b/src/service/EncryptionService.js new file mode 100644 index 0000000..0f02408 --- /dev/null +++ b/src/service/EncryptionService.js @@ -0,0 +1,70 @@ +const crypto = require('crypto'); + +module.exports = class EncryptionService { + + getSongFileName(trackInfos, trackQuality) { + const step1 = [trackInfos.MD5_ORIGIN, trackQuality, trackInfos.SNG_ID, trackInfos.MEDIA_VERSION].join('¤'); + + let step2 = crypto.createHash('md5').update(step1, 'ascii').digest('hex') + '¤' + step1 + '¤'; + while (step2.length % 16 > 0) step2 += ' '; + + return crypto.createCipheriv('aes-128-ecb', 'jo6aey6haid2Teih', '').update(step2, 'ascii', 'hex'); + } + + + /** + * Calculate the blowfish key to decrypt the track + * + * @param {Object} trackInfos + */ + getBlowfishKey(trackInfos) { + const SECRET = 'g4el58wc0zvf9na1'; + + const idMd5 = crypto.createHash('md5').update(trackInfos.SNG_ID.toString(), 'ascii').digest('hex'); + let bfKey = ''; + + for (let i = 0; i < 16; i++) { + bfKey += String.fromCharCode(idMd5.charCodeAt(i) ^ idMd5.charCodeAt(i + 16) ^ SECRET.charCodeAt(i)); + } + + return bfKey; + } + + /** + * Decrypt a deezer track. + * + * @param {Buffer} trackBuffer + * @param {Object} trackInfos + * + * @return {Buffer} + */ + decryptTrack(trackBuffer, trackInfos) { + const blowFishKey = this.getBlowfishKey(trackInfos); + + let decryptedBuffer = Buffer.alloc(trackBuffer.length); + let chunkSize = 2048; + let progress = 0; + + while (progress < trackBuffer.length) { + if ((trackBuffer.length - progress) < 2048) { + chunkSize = trackBuffer.length - progress; + } + + let encryptedChunk = trackBuffer.slice(progress, progress + chunkSize); + + // Only decrypt every third chunk and only if not at the end + if (progress % (chunkSize * 3) === 0 && chunkSize === 2048) { + let cipher = crypto.createDecipheriv('bf-cbc', blowFishKey, Buffer.from([0, 1, 2, 3, 4, 5, 6, 7])); + cipher.setAutoPadding(false); + + encryptedChunk = Buffer.concat([cipher.update(encryptedChunk), cipher.final()]); + } + + decryptedBuffer.write(encryptedChunk.toString('binary'), progress, encryptedChunk.length, 'binary'); + + progress += chunkSize; + } + + return decryptedBuffer; + } +}; \ No newline at end of file