• 测试接口遇到APP加密?先来了解一下算法思路~


    在这里插入图片描述

    背景

    服务端与客户端进行http通讯时,为了防止被爬虫,数据安全性等,引入APP通信加密,简单来说,就是引入签名sign,APP的所有请求都会经过加密签名校验流程。常见的加密方案有AES加密,RSA加密,MD5加密等。由于引入签名sign请求头,我们在测APP接口的时候,不填签名数据的话,都会被服务端加密签名校验所拦截,这对我们测接口造成了极大的困扰。

    {
        "msg": "鉴权失败",
        "code": 99999,
        "data": {}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    解决方案

    • 叫开发在测试环境关闭签名验证
    • 通过抓包把APP的请求头和请求参数抓下来,通过postman测试
    • 询问开发具体的加密过程,复写一套加密算法,自己生成加密数据

    方案分析

    测试环境关闭签名验证,可能对代码改动比较大,容易留下坑,有可能上线部署时忘记打开签名验证,这会造成极大的影响;也有可能改动不大,开发代码比较规范,上线比较规范;(每个公司情况不一样~)可以作为备选方案。

    每次抓包时,请求参数都是固定的,而签名数据又是随着请求参数变化而变化,这对于测试接口的多场景比较麻烦,每点一次就抓包一次;有时候前后端还没联调,让你提前介入接口测试,没有页面怎么抓包,抛弃。

    复写过程可能比较复杂,可能比较简单,需要开发协助说明整个加密过程(一杯奶茶就好了)复写过程中又可以锻炼自己的code能力,推荐。

    方案实现过程

    验签说明

    参数名按ASCII码从小到大排序(字典序)如果参数的值为空(null值,空格、空字符串)不参与签名,参数名区分大小写;如参数的类型为复合类型不参与签名(如:List[Map]类型或List[String]类型或List[int]类型)。

    使用URL键值对的格式(key1=value1&key2=value2…)拼接成待签名的字符串plain,在plain最后拼接上key(密钥),plain=plain+”&key=分配的密钥”,再将plain进行签名运算sign=Base64(MD5(plain))。

    sign字符串放在http报文头中X-Sign字段。

    验签步骤拆解

    • 参数名按ASCII码从小到大排序
    • 参数值为空(null值,空格、空字符串)不参与签名
    • 参数值为复合类型(对应Python中的dict、list)不参与签名
    • 拼接键值对,格式为key1=value1&key2=value2…
    • 键值对后面拼接key(密钥)
    • 键值对字符串先经过MD5加密,再进行base64编码

    py实现

    作者:新梦想IT
    链接:https://zhuanlan.zhihu.com/p/531852341
    来源:知乎
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    
    import hashlib
    import base64
    key = '123456'
    class Sign:
        @classmethod
        def get_sign(cls, data):
    
    
            # 不改变传入的data=>直接对字典进行复制
            data = data.copy()
            # 首先判断字典是否空,为空直接加密
            if not data:
                string = ''
            else:
                # 非空字典,过滤value为空和嵌套字典、列表
                for k, v in list(data.items()):
                    if isinstance(v,str):
                        v = v.replace(" ", "").replace("\n", "")
                        data[k] = v
                    if v == '' or v is None or isinstance(v,(dict,list)):
                        data.pop(k)
    
    
                # 对字典进行ASCII码排序
                new_list = sorted(data.items())
                alist = ['&' + str(i[0]) + '=' + str(i[1]) for i in new_list]
                string = ''.join(alist)
            return {"X-Sign": cls.encry(string)}
    
    
        @classmethod
        def encry(cls, string):
            """
            加密算法
            :param string: 要加密的字符串
            :return: 字符串
            """
            if string:
                sign = string[1:] + '&key=' + key
            else:
                sign = 'key=' + key
            m = hashlib.md5()
            m.update(sign.encode("utf8"))
            encodeStr = m.hexdigest()
            base_code = base64.b64encode(encodeStr.encode('utf-8'))
            return base_code.decode(
    • 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
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    因为有用到dict.pop()方法,这会对原来的data进行改变,所以我们传入data的时候,对data进行浅拷贝:

    data = data.copy()

    字符串要去除空格、换行符,实际服务端进行加密生成时,也会对字符串进行去除空格、换行符:

    if isinstance(v,str):
        v = v.replace(" ", "").replace("\n", "")
        data[k] = v
    
    • 1
    • 2
    • 3

    算法验证

    作者:新梦想IT
    链接:https://zhuanlan.zhihu.com/p/531852341
    来源:知乎
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    
    import requests
        data ={
    "mercId": "888000000000001",
    "sysCnl": "IOS",
    "orderTypes": [0, 1, 2, 3, 4, 5, 6, 7, 9, 10],
    "platform": "LXMALL",
    "limit": 20,
    "page": 1,
    "timestamp": "1625881419"
    }
        sign_data = Sign.get_sign(data)
        headers = {
            "X-Token" : "08fa6dad125d30842c58d9bea6059ace",
            "X-APPVer": "1.5",
            "X-SignVer": "v1",
            "Content-Type": "application/json"
        }
        headers |= sign_data
        url = base_url + 'order/list'
        r = requests.post(url,json = data,headers = headers)
        print(r.json())
    
    • 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

    成功打印输出返回参数,大功告成~

    api集成

    你可以将生成加密数据做成一个api提供给小伙伴使用,传入待加密的请求报文,返回加密数据,小伙伴复制粘贴加密数据到请求头,即可完成接口测试。

    jmeter集成

    因为jmeter里面的beanshell用的是Java语法,可以直接喊开发(Java后端或者Android开发)直接帮你打个jar包,直接调用即可。

    //找不到以前的脚本,大家可以去网上搜索一下的源码,大致是下面的import com.xxx.xxxx.Sign;String SamplerData=prev.getSamplerData();String signData = Sign.getSign(SamplerData);http://log.info("签名数据是"+signData);
    
    • 1

    最后: 可以在公众号:程序员一凡 自行领取一份216页软件测试工程师面试宝典文档资料【免费的】。以及相对应的视频学习教程免费分享!,其中包括了有基础知识、Linux必备、Shell、互联网程序原理、Mysql数据库、抓包工具专题、接口测试工具、测试进阶-Python编程、Web自动化测试、APP自动化测试、接口自动化测试、测试高级持续集成、测试架构开发测试框架、性能测试、安全测试等。

    我推荐一个【Python自动化测试交流群:769146372】,大家可以一起探讨交流软件测试,共同学习软件测试技术、面试等软件测试方方面面,助你快速进阶Python自动化测试/测试开发,走向高薪之路。

  • 相关阅读:
    make和cmake命令
    机器学习笔记之隐马尔可夫模型(一)概率模型背景的阶段性介绍
    【论文笔记】—低照度图像增强—Supervised—混合神经网络—2019-TIP
    Linux 学习笔记(yum)
    C++笔记之popen()和std_system()和std_async()执行系统命令比较
    广州蓝景分享—「web前端素材」使用CSS动画效果(下)
    【机器学习算法】机器学习:支持向量机(SVM)
    如何将文字转换成语音?分享两款实用软件
    Python全栈开发【基础-07】与用户交互
    窗口函数-分组排序:row_number()、rank() 、dense_rank()、ntile()
  • 原文地址:https://blog.csdn.net/weixin_56502375/article/details/125626671