cosven / cosven.github.io

个人零碎笔记,博客草稿,阅读笔记
10 stars 0 forks source link

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

Closed cosven closed 3 years ago

cosven commented 8 years ago

参考文章:

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

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
jixunmoe commented 7 years ago

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

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