Django开发总结-(六)分析网易云音乐接口修改用户Model

分析网易云音乐接口修改用户Model

1.背景

​ 由**Binaryify/NeteaseCloudMusicApi**项目获得启发,想要通过django做一个网易云音乐API作为联系项目。想要实现这个目的,首先要坐的就是要分析网易云音乐的接口规则。

2.网易云音乐登记接口分析

2.1.手机号登陆接口分析

​ 经过分析,其中的https://music.163.com/weapi/login/cellphone?csrf_token=请求URL即为手机号登陆接口URL。

在这里插入图片描述

在这里插入图片描述

​ 其中该接口请求方式为POST方式,返回报文格式为json格式,同时会设置三个cookie。初步设想可以通过requests库模拟HTTPSPOST请求,获取到返回结果后,将获取到的cookie信息设置到浏览器的本地cookie中,或者保存到本地数据库中,可以实现不同接口之间的cookies数据的持续化存储。

在这里插入图片描述

在这里插入图片描述

​ 而对于请求,则是From表单方式发送,总共有两个参数:paramsencSecKey,可以看到均是加密数据,所以需要继续分析请求数据的加密方式。

在这里插入图片描述

通过Network请求信息中的Initiator参数可以看出,该请求的处理逻辑在core*.js的js文件中,所以可以查看其源码。

在这里插入图片描述

​ 源码为压缩版的js,可以通过编辑器进行代码格式化(这里使用vscode进行格式化),格式化后找到相关代码段。

在这里插入图片描述

​ 可以看出两个参数都是通过window.asrsea函数进行处理,因此通过该函数可以查询到该函数具体的实现过程。

在这里插入图片描述

在这里插入图片描述

​ 通过抓包工具fiddler,将本地的core*.js文件替代网页加载的js文件。

在这里插入图片描述

​ 将传递的四个参数打印出来

在这里插入图片描述

​ 发起手机登陆交易

在这里插入图片描述

​ 查看浏览器调试工具中后台打印日志

在这里插入图片描述

​ 与其他请求对比,发现其中第一个参数为请求数据,后三个参数均为固定参数,然后再去观察js代码处理函数。

function d(d, e, f, g) {
	var h = {},
        i = a(16);
    return h.encText = b(d, g), h.encText = b(h.encText, i), h.encSecKey = c(i, e, f), h
}

​ 其中d为请求参数,e010001f00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7g0CoJUm6Qyw8W8jud

​ 参数i为函数a返回值,函数a处理逻辑为以abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789为种子的16位随机字符串。

function a(a) {
    var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
        c = "";
    for (d = 0; a > d; d += 1) e = Math.random() * b.length, e = Math.floor(e), c += b.charAt(e);
    return c
}

h.encText参数为函数b的返回值,其中a=d=请求参数b=g='0CoJUm6Qyw8W8jud',即对请求参数a进行加密,加密密钥为b='0CoJUm6Qyw8W8jud',密钥偏移量IV为0102030405060708,加密算法为CBC模式。之后h.encText = b(h.encText, i)对已加密的字符串进行二次加密,加密密钥为16为随机数i,密钥偏移量IV、加密算法均不变。

function b(a, b) {
    var c = CryptoJS.enc.Utf8.parse(b),
        d = CryptoJS.enc.Utf8.parse("0102030405060708"),
        e = CryptoJS.enc.Utf8.parse(a),
        f = CryptoJS.AES.encrypt(e, c, {
            iv: d,
            mode: CryptoJS.mode.CBC
        });
    return f.toString()
}

h.encSecKey参数为函数c的返回值,其中a=i=16位随机数b=e='010001'c=f='00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7',即对参数第二次加密时使用的加密密钥“16位随机数”进行RSA加密,通过16进制e值b='010001'、公钥c生成RSA加密密钥。

function c(a, b, c) {
    var d, e;
    return setMaxDigits(131), 
        d = new RSAKeyPair(b, "", c), 
        e = encryptedString(d, a)
}

2.2. 根据分析结果编写python版加密处理

​ 根据以上分析,编写python版的加密代码apps/utils/encrypt.py,对于RSA算法的加密算法公式如下:
c = m e ( m o d n ) c = m^e (mod n) c=me(modn)
​ 解密公式如下:
m = c d ( m o d n ) m = c^d (mod n) m=cd(modn)
​ 其中,加密时的公钥中,n为两个素数p和q的乘积(p和q必须保密),e与(p-1)(q-1)互质。

注:

  1. 代码中用到加密三方库pycryptodome,对于pycrypto库已停止更新,所以使用pycryptodome作为替代。
pip install pycryptodome
  1. 代码中以__双下划线开头的参数,均为私有属性,不能在类的外部被使用或直接访问。

  2. 代码中__双下划线开头的方法,均为私有方法,不能在类的外部被调用。

apps/utils/common.py代码如下:

import random


def random_string(length=6):
    """获取特定长度的随机字符串"""
    seed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    return ''.join(random.sample(seed, int(length)))

apps/utils/encrypt.py代码如下:

import json
import base64
import binascii
from Crypto.Cipher import AES

from apps.utils.common import random_string


class APIEncrypt(object):
    """API接口加密类"""
    def __init__(self):
        """初始化函数
            __AES_MODE_CBC:AES加密CBC模式
            __AES_MODE_ECB:AES加密CEB模式
            __AES_IV:AES加密偏移量
            __AES_NONCE:AES加密一次性密钥
            __AES_SECRET_KEY:AES加密密钥
            __AES_LINUX_API_KEY:linux相关API的AES加密密钥
            __RSA_PUBLIC_KEY:RSA加密公钥
            __RSA_MODULUS:RSA加密模量
        """
        self.__AES_MODE_CBC = AES.MODE_CBC
        self.__AES_MODE_ECB = AES.MODE_ECB
        self.__AES_IV = '0102030405060708'
        self.__AES_NONCE = '0CoJUm6Qyw8W8jud'
        self.__AES_SECRET_KEY = random_string(16)
        self.__AES_LINUX_API_KEY = 'rFgB&h#%2?^eDg:Q'
        self.__RSA_PUBLIC_KEY = '010001'
        self.__RSA_MODULUS = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'

    def __encrypt_aes(self, params, secret_key, aes_mode=AES.MODE_CBC, iv='', result_coding='base64'):
        """AES加密处理
        """
        # ① 请求参数补位处理
        pad = 16 - len(params) % 16
        params = params + chr(pad) * pad
        # ② 创建加密器,其中加密模式默认为CBC
        encryptor = AES.new(secret_key.encode('utf8'), aes_mode, iv.encode('utf8')) if iv else \
            AES.new(secret_key.encode('utf8'), self.__AES_MODE_ECB)
        # ③ 对参数进行加密处理
        cipher_params = encryptor.encrypt(params.encode('utf-8'))
        # ④ 对加密后的结果进行编码处理,默认base64编码,否则为十六进制编码
        if result_coding == 'base64':
            cipher_params = base64.b64encode(cipher_params).decode('utf-8')
        elif result_coding == 'hex':
            cipher_params = binascii.hexlify(cipher_params)
        return cipher_params

    def __encrypt_rsa(self):
        """RSA加密"""
        # ① 将加密密钥倒置
        aes_secret_key = self.__AES_SECRET_KEY[::-1]
        # ② 加密处理
        rs = pow(int(binascii.hexlify(aes_secret_key.encode('utf8')), 16),
                 int(self.__RSA_PUBLIC_KEY, 16),
                 int(self.__RSA_MODULUS, 16))
        # ③ 将加密结果转成十六进制编码,并右对齐前补零,补齐256位
        return format(rs, 'x').zfill(256)

    def encrypt_weapi(self, params):
        """web版API加密处理"""
        # ① 两次加密处理:
        # ①-1 对请求参数进行AES加密,加密密钥为__AES_NONCE,加密模式为CBC,加密偏移量为__AES_IV
        # ①-2 对机密结果再次加密,加密密钥为__AES_SECRET_KEY(十六位随机字符串),加密模式为CBC,加密偏移量为__AES_IV
        params = json.dumps(params)
        encrypt_params = self.__encrypt_aes(
            self.__encrypt_aes(params, self.__AES_NONCE, aes_mode=self.__AES_MODE_CBC, iv=self.__AES_IV),
            self.__AES_SECRET_KEY,
            aes_mode=self.__AES_MODE_CBC,
            iv=self.__AES_IV
        )
        # ② 对AES加密使用到的密钥__AES_SECRET_KEY(十六位随机字符串)进行RSA加密处理
        encrypt_secret_key = self.__encrypt_rsa()
        return {'params': encrypt_params, 'encSecKey': encrypt_secret_key}

    def encrypt_linuxapi(self, params):
        """linux版API加密处理"""
        # ① 直接对请求参数进行AES加密,加密密钥为__AES_LINUX_API_KEY,加密模式为ECB,最终编码为十六进制
        params = json.dumps(params)
        encrypt_params = self.__encrypt_aes(
            params, self.__AES_LINUX_API_KEY, aes_mode=self.__AES_MODE_ECB, result_coding='hex')
        return {'eparams': encrypt_params.upper()}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值