Skip to content

网易云音乐网页端 API 的加密算法代码的分析 #30

@cosven

Description

@cosven
Owner

参考文章:

下面这段代码来自上面这篇文章

modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
nonce = '0CoJUm6Qyw8W8jud'
pubKey = '010001'

def createSecretKey(size):
    return (''.join(map(lambda xx: (hex(ord(xx))[2:]), os.urandom(size))))[0:16]

def aesEncrypt(text, secKey):
    pad = 16 - len(text) % 16
    text = text + pad * chr(pad)
    encryptor = AES.new(secKey, 2, '0102030405060708')
    ciphertext = encryptor.encrypt(text)
    ciphertext = base64.b64encode(ciphertext)
    return ciphertext

def rsaEncrypt(text, pubKey, modulus):
    text = text[::-1]
    rs = int(text.encode('hex'), 16) ** int(pubKey, 16) % int(modulus, 16)
    return format(rs, 'x').zfill(256)

def encrypted_request(text):
    text = json.dumps(text)
    secKey = createSecretKey(16)
    encText = aesEncrypt(aesEncrypt(text, nonce), secKey)
    encSecKey = rsaEncrypt(secKey, pubKey, modulus)
    data = {
        'params': encText,
        'encSecKey': encSecKey
    }
    return data

这段代码最难懂的应该是 rs = int(text.encode('hex'), 16) ** int(pubKey, 16) % int(modulus, 16) 这句话。所以我就分析分析这句话,以及这些变量的作用。

解释一下一些东西:
AES 加密算法 是一种对称加密算法

RSA 加密算法是一种非对称加密算法

上面的modulus是个16进制的数,这个数由两个质数相乘得到,转换为2进制之后的长度为1024。它的长度代表了RSA加密算法的密钥的长度。目前技术,1024位长度的密钥基本不能被破解。
pubKey应该是一个小于φ(modulus) 的一个随机整数。moduluspubKey组合起来就是RSA加密的公钥。(所以,这个pubkey变量命名其实不是很妥)

rs = int(text.encode('hex'), 16) ** int(pubKey, 16) % int(modulus, 16),这段代码的根据是这个公式:m^e ≡ c (mod n)。m 相当于 int(text.encode('hex'), 16), e 相当于 int(pubKey, 16), n 相当于int(modulus, 16),rs 相当于 c,也就是加密后的内容。所以,这句话的意思就是:使用公钥对text进行加密,得到rs。

encrypted_request 这个函数比较好理解。
secKey = createSecretKey(16)随机生成一个密钥。
代码中的 nonce = '0CoJUm6Qyw8W8jud'的 nonce 变量也是一个密钥,是AES加密的密钥。
encText = aesEncrypt(aesEncrypt(text, nonce), secKey)就是先使用nonce作为AES密钥对text加密一次,然后使用随机生成的密钥seckey对加密后的文字再加密一次得到 encText。

到这里为止,我再次把所知道的东西列出来:

  1. 我们知道了 RSA 加密算法的公钥。服务器有私钥,用来解密消息。
  2. 我们知道了网页端对 内容 进行了两次AES加密,才把内容发给后台,而第一次AES加密的密钥我们已经知道(服务器也知道)。但是第二次AES加密的密钥是随机生成的,程序知道,我们不知道,服务器也不知道。

所以程序对这个 随机生成的密钥 使用 RSA 加密,发给后端,后端就知道了这个随机密钥到底是多少。

基本完了。


问题来了:int(text.encode('hex'), 16) ** int(pubKey, 16) % int(modulus, 16) 这不就是 RSA 加密算法么,为啥要自己写,难道没有现成的函数么。答案是有的,所以 rsa_encrypt 这个函数可以改写为:

from Crypto.PublicKey import RSA

def rsa_encrypt(self, text):
    e = '010001'
    n = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615'\
        'bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf'\
        '695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46'\
        'bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b'\
        '8e289dc6935b3ece0462db0a22b8e7'
    reverse_text = text[::-1]
    pub_key = RSA.contruct([int(n, 16), int(e, 16)])
    encrypt_text = pub_key.encrypt(reverse_text)[0]
    return encrypt_text

不过,如果当初作者写成这个样子,我也就不去研究 RSA 算法的原理了。(一把泪 =.= )不过也好,学了点数学。

另外,一个有趣的地方:
运算 int(text.encode('hex'), 16) ** int(pubKey, 16) 需要几秒的时间,但是运行这整句代码,却可以秒出结果。(表示不懂啊)

使用 python3 改写之后

    def _create_aes_key(self, size):
        return (''.join([hex(b)[2:] for b in os.urandom(size)]))[0:16]

    def _aes_encrypt(self, text, key):
        pad = 16 - len(text) % 16
        text = text + pad * chr(pad)
        encryptor = AES.new(key, 2, '0102030405060708')
        enc_text = encryptor.encrypt(text)
        enc_text_encode = base64.b64encode(enc_text)
        return enc_text_encode

    def _rsa_encrypt(self, text):
        e = '010001'
        n = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615'\
            'bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf'\
            '695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46'\
            'bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b'\
            '8e289dc6935b3ece0462db0a22b8e7'
        reverse_text = text[::-1]
        pub_key = RSA.construct([int(n, 16), int(e, 16)])
        encrypt_text = pub_key.encrypt(int(binascii.hexlify(reverse_text), 16),
                                       None)[0]
        return format(encrypt_text, 'x').zfill(256)

    def encrypt_request(self, data):
        text = json.dumps(data)
        first_aes_key = '0CoJUm6Qyw8W8jud'
        second_aes_key = self._create_aes_key(16)
        enc_text = self._aes_encrypt(
            self._aes_encrypt(text, first_aes_key).decode('ascii'),
            second_aes_key).decode('ascii')
        enc_aes_key = self._rsa_encrypt(second_aes_key.encode('ascii'))
        payload = {
            'params': enc_text,
            'encSecKey': enc_aes_key,
        }
        return payload

Activity

changed the title [-]网易云音乐网页端 API 的加密算法详细分析[/-] [+]网易云音乐网页端 API 的加密算法代码的分析[/+] on May 4, 2016
jixunmoe

jixunmoe commented on Sep 21, 2016

@jixunmoe

运算整句快应该是 python 做了优化吧。

https://en.wikipedia.org/wiki/Modular_exponentiation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @cosven@jixunmoe

        Issue actions

          网易云音乐网页端 API 的加密算法代码的分析 · Issue #30 · cosven/cosven.github.io