网易云音乐客户端在下载歌曲时会向歌曲写入标签(元数据),其中在 Comment 字段中有一串字符串,开头是
163 key(Don't modify):
。
以 RoughSketch - 666 为例:
1
| 163 key(Don't modify):L64FU3W4YxX3ZFTmbZ+8/T2pe247TRKgRJL+sO6ZF+Zsdjuf2gdMH+AKchsDMApnzxU9HrjFayL9cOo+RaExJcM8X9Kv4coonZRypWZb0hCG6F7ZLDKVV3EtH1yIIjMlb9Q5KzLaVMBv3bHtH+epb6Gq8SHRgFivFGMz2mOStud/ngWVTxjr2TKC7Sanr7WW18YeHHC2z7dmVn/UUGSiQwUW83BDLZQfL0AXMpNVXSbHPCQx1kCy7M6nsCc3/J+BQNrrOgMbvnReWAfUnv2Agi3u64FmduXuDcHdoP0eEFBDJFJbc5Mm7zJZq5vBnFs5OmAMPZQSO8PBqvyaXeO6i4pjyM8VOmt+EQdwcFHcFYg+MNc1qij2PyrrUazcIHW1ijysHhnBTrx9t0Ml2j4KoRu6hXtDlzeig57gzp3g2Xlf2gpnK8EqNcxuA2BruMfJtl3uVFa46oIxURC5/UT4zTz57QgDmcNmGkZd5r2zl5Y=
|
若要跳过分析,请转到实例。
分析
去掉开头的 163 key(Don't modify):
以后,剩下的部分是 base64 编码。解码后得到一段二进制数据。
经查找得知这是用 aes-128-ecb 算法加密的密文,密钥是 #14ljk_!\]&0U<'(
。
(参考地址:163 key 使用指南)
解密后得到如下字符串:
1
| music:{"musicId":1821507605,"musicName":"666","artist":[["RoughSketch",161837]],"albumId":0,"album":"","albumPicDocId":"109951165741459370","albumPic":"https://p4.music.126.net/icjUarfrUvQPfJZ9MX7GXA==/109951165741459370.jpg","bitrate":320000,"mp3DocId":"afff55760e0dd5d95e3caa00f8a397ff","duration":119823,"mvId":0,"alias":[],"transNames":[],"format":"mp3"}
|
可以很明显看出去掉头就是包含信息的 json 了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| { "musicId": 1821507605, "musicName": "666", "artist": [ [ "RoughSketch", 161837 ] ], "albumId": 0, "album": "", "albumPicDocId": "109951165741459370", "albumPic": "https://p4.music.126.net/icjUarfrUvQPfJZ9MX7GXA==/109951165741459370.jpg", "bitrate": 320000, "mp3DocId": "afff55760e0dd5d95e3caa00f8a397ff", "duration": 119823, "mvId": 0, "alias": [], "transNames": [], "format": "mp3" }
|
操作实现
这里使用 Python 演示,因为比较方便。
提取歌曲里的 Comment 字段可以使用库 eyed3
(需要从 pip 安装),它可以增加、删除、修改 mp3 文件中 ID3 元数据(即歌曲信息)。这里我们只用它获取数据。
1 2 3 4 5 6 7 8
| import eyed3
song = eyed3.load('RoughSketch - 666.mp3')
comment = song.tag.comments[0].text
b64str = key[22:]
|
这样我们就得到了 base64 处理过的字符串。
Python 自带处理 base64 的库。使用该库获取加密后的二进制串:
1 2 3
| import base64
enc = base64.b64decode(b64str)
|
随后我们对数据进行解密,这里用到 pycryptodome
库(需要从 pip 安装)。
1 2 3 4 5 6 7
| from Crypto.Cipher import AES
aes = AES.new(b"#14ljk_!\\]&0U<'(", AES.MODE_ECB)
dec = aes.decrypt(enc)
dec_str = dec.decode('utf-8')
|
实际操作时会得到这样一个字符串:
1
| 'music:{"musicId":1821507605,"musicName":"666","artist":[["RoughSketch",161837]],"albumId":0,"album":"","albumPicDocId":"109951165741459370","albumPic":"https://p4.music.126.net/icjUarfrUvQPfJZ9MX7GXA==/109951165741459370.jpg","bitrate":320000,"mp3DocId":"afff55760e0dd5d95e3caa00f8a397ff","duration":119823,"mvId":0,"alias":[],"transNames":[],"format":"mp3"}\n\n\n\n\n\n\n\n\n\n'
|
需要注意最后的一堆换行符,它们也可能是莫名其妙的非空白字符(比如 \x01
),导致 json 解析失败。应当额外处理,把 }
后的内容截断。
笔者技术力不佳,还不清楚这些字符出现的原因。
1 2
| json_str = dec_str[6:dec_str.rfind('}') + 1]
|
最后使用 Python 自带的 json 库解析字符串:
1 2 3
| import json
obj = json.loads(json_str)
|
这样我们就得到了一个包含 163key 中信息的字典。可以使用这个字典中的信息进行一些其他操作,例如获取歌词或下载专辑封面。
实例
将刚刚分析的内容综合起来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import eyed3 from base64 import b64decode from Crypto.Cipher import AES import json
def get_key_info(path: str): """ 从一个文件中读取 163key 并解密。 :param path: 要读取的文件路径 :return: 目标文件 163key 的内容。如果没有或者无法解析返回 None """ song = eyed3.load(path) key = song.tag.comments[0].text if type(key) == str and key.startswith("163 key(Don't modify):"): b64str: str = key[22:] enc_str: bytes = b64decode(b64str) aes = AES.new(b"#14ljk_!\\]&0U<'(", AES.MODE_ECB) dec_str: str = aes.decrypt(enc_str).decode('utf-8') if dec_str.startswith("music:"): return json.loads(dec_str[6:dec_str.rfind('}') + 1]) else: return None else: return None
|
使用函数 get_key_info
即可获得一个包含 163key 中信息的字典。