本帖最后由 aiai 于 2020-7-19 22:27 编辑
参考https://github.com/ix64/unlock-music发布的js代码,改写为python代码,并做少量修改
运行结果解密py目录下所有ncm文件
[Python] 纯文本查看 复制代码 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.当解密文件过多时,时间偏长 |