【python】ncm解密

kain
2020-09-01 / 0 评论 / 808 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2020年09月01日,已超过1356天没有更新,文章所提及的内容可能已过时失效,所以请自行测试验证。

X1Z5XV

参考 https://github.com/ix64/unlock-music 发布的js代码,改写为python代码,并做少量修改

运行结果解密py目录下所有ncm文件

Code

import base64
import json
import os
from mutagen.id3 import ID3, TIT2, TPE1, TALB
from Crypto.Cipher import AES
 
def getUint32(dataView, offset):
    dataView = list(dataView[offset:offset+4])[::-1]
    u16 = ''
    for each in dataView:
        u16 += hex(each)[-2:]
    u16 = u16.replace('x', '0')
    return int(u16, 16)
     
def getKeyData(dataView, offset):
    keyLen = getUint32(dataView, offset)
    offset += 4
    cipherText = list(dataView[offset:offset+keyLen])
    for i in range(0, len(cipherText)):
        cipherText[i] = cipherText[i] ^ 0x64
    offset += keyLen
    cryptor = AES.new(key=bytes.fromhex('687a4852416d736f356b496e62617857'), mode=AES.MODE_ECB)
    plainText = cryptor.decrypt(bytes(cipherText))
    return {"offset": offset, "data": list(plainText)[17:-14]}
     
def getKeyBox(keyData):
    box = []
    for i in range(0, 256):
        box.append(i)
    keyDataLen = len(keyData)
    j = 0
    for i in range(0, 256):
        j = (box[i] + j + keyData[i % keyDataLen]) & 0xff
        box[i], box[j] = box[j], box[i]
    boxmap = []
    for i in range(0, 256):
        i = (i + 1) & 0xff;
        si = box[i]
        sj = box[(i + si) & 0xff]
        boxmap.append(box[(si + sj) & 0xff])
    return boxmap 
     
def getMetaData(dataView, offset):
    metaDataLen = getUint32(dataView, offset)
    offset += 4
    cipherText = list(dataView[offset:offset+metaDataLen])
    for i in range(0, len(cipherText)):
        cipherText[i] = cipherText[i] ^ 0x63
    offset += metaDataLen
    cryptor = AES.new(key=bytes.fromhex('2331346C6A6B5F215C5D2630553C2728'), mode=AES.MODE_ECB)
    plainText = cryptor.decrypt(base64.b64decode(bytes(cipherText)[22:])).decode()
    labelIndex = plainText.find(":")
    result = json.loads(plainText[labelIndex+1:].encode().replace(b'\x07', b'').decode())
    return {'data': result, 'offset': offset}
     
def GetFileInfo(artist, title, filenameNoExt, separator):
    newArtist = ""
    newTitle = ""
    filenameArray = filenameNoExt.split(separator)
    if len(filenameArray) > 1:
        newArtist = filenameArray[0].strip()
        newTitle = filenameArray[1].strip()
    elif len(filenameArray) == 1:
        newTitle = filenameArray[0].strip()
    if type(artist) == 'string' and artist:
        newArtist = artist
    if type(title) == 'string' and title:
        newArtist = title
    return {'artist': newArtist, 'title': newTitle}
 
def DetectAudioExt(data, fallbackExt):
    if data[:8] == '664C6143':
        return 'flac'
    elif data[:6] == '494433':
        return 'mp4'
    elif dat[:8] == '4F676753':
        return 'ogg'
    elif data[4:12] == '66747970':
        return 'm4a'
    elif data[:32] == '3026B2758E66CF11A6D900AA0062CE6C':
        return 'wma'
    elif data[:8] == '52494646':
        return 'wav'
    else:
        return 'mp3'
 
def decryptncm(filepath):
    outputpath = './unlockmusic/wangyiyun/'
    if not os.path.exists(outputpath):
        os.makedirs(outputpath)
    ncm = {}
    ncm['fileext'] = filepath.split('.')[-1]
    ncm['filename'] = filepath[:-(len(ncm['fileext'])+1)]
    with open(filepath, 'rb') as f:
        ncm['filebuffer'] = f.read()
    keyDataObj = getKeyData(ncm['filebuffer'], 10)
    keyBox =  getKeyBox(keyDataObj['data'])
    musicMetaObj = getMetaData(ncm['filebuffer'], keyDataObj['offset']);
    musicMeta = musicMetaObj['data']
    audioOffset = musicMetaObj['offset']+getUint32(ncm['filebuffer'], musicMetaObj['offset']+5)+13
    audioData = list(ncm['filebuffer'][audioOffset:])
    lenAudioData = len(audioData)
    for i in range(0, lenAudioData):
        audioData[i] ^= keyBox[i & 0xff];
    if not 'album' in musicMeta.keys():
        musicMeta['album'] = ''
    artists = []
    if musicMeta['artist']:
        for each in musicMeta['artist']:
             artists.append(each[0])
    info = GetFileInfo(' & '.join(artists), musicMeta['musicName'], ncm['filename'], '-')
    if not artists:
        artists.append(info['artist'])
    if not 'format' in musicMeta.keys():
        musicMeta['format'] = DetectAudioExt(audioData[:32].hex().upper(), "mp3");
    outputpath = outputpath+musicMeta['musicName']+"."+musicMeta['format']
    with open(outputpath, 'wb') as f:
        f.write(bytes(audioData))
    if musicMeta['format'] == 'mp3':
        audio = ID3(outputpath)
        audio.update_to_v23()
        audio.delete()
        audio['TIT2'] = TIT2(encoding=3, text=[info['title']])
        audio['TPE1'] = TPE1(encoding=3, text=[artists[0]])
        audio['TALB'] = TALB(encoding=3, text=[musicMeta['album']])
        audio.save()
    print(musicMeta['musicName']+"."+musicMeta['format']+'解密完成')
 
def main():
    for each in os.listdir(os.getcwd()):
        if '.ncm' in each:
            decryptncm(each)
    a = input('所有音乐解密完成')
     
if __name__ == "__main__":
    main()

已知问题

  1. 自身语法写的比较低级,影响运行速度
  2. 当解密文件过多时,时间偏长
0

评论 (0)

取消