代码改变世界

接口加密如何测试?

2020-05-02 09:38  zouhui  阅读(1565)  评论(0编辑  收藏  举报

摘要算法:

  对明文编码生成信息摘要,以防止被篡改。比如MD5使用的是Hash算法,无论多长的输入,MD5都会输出长度为128bits的一个串。

  摘要算法不要秘钥,客户端和服务端采用相同的摘要算法即可针对同一段明文获取一致的密文

对称加密:

  对称加密算法是共享密钥加密算法,在加密解密过程中,使用的密钥只有一个。发送和接收双方事先都知道加密的密钥,均使用这个密钥对数据进行加密和解密。

数据加密:在对称加密算法中,数据发送方将明文 (原始数据) 和 加密密钥一起经过加密处理,生成复杂的密文进行发送。

数据解密:数据接收方收到密文后,使用加密的密钥及相同算法的逆算法对加密的密文进行解密,将使其恢复成可读明文。

非对称加密

非对称加密算法,有两个密钥,一个称为公开密钥 (publickey),另一个称为 私有密钥 (private key),加密和解密使用的是两个不同的密钥,所以这种算法称为非对称加密算法。

如果使用公钥对数据进行加密,只有用对应的私钥才能进行解密。

如果使用私钥对数据进行加密,只有用对应的公钥才能进行解密。

常见加密算法的处理方式

根据上述常见的加密算法,测试人员在测试不同的加密接口可采用下述的方法处理加密接口

摘要算法(MD5.SHA1 ):造接口数据前调用MD5,SHA1进行编码,服务端对比编码后的字符串是否一致。

对称加密算法(AES,DES ):造接口数据前从开发获取对称公钥,基于对称公钥可以加密请求数据,解密响应报文。

非对称加密算法(RSA):造接口数据前从开发获取公钥私钥去加密解密接口数据

用户认证

一般的接口测试工具都会提供一个User Auth/Authorization的选项,以Postman为栗子,你可以看到以下的选项:

基本认证(Basic Auth)

摘要认证(Digest Auth)

OAuth 1.0a

OAuth 2.0(最常见,现在的网站接口多数提供此用户认证方式)

在对应的工具上,你可以选取对应的用户认证选项,这里CC先生主要介绍如果用Python如何实现用户认证。

1 首先安装Requests库,Requests库的get()和post()方法均提供有auth参数,用于设置用户签名。

2 假定我们有一个接口为添加一个新的公告,接口需要认证:auth=("username","password")

nid 或 name两个参数二选一

3 伪代码:

def test_get_notice_list_nid_sucess(self):
    auth_user = ('admin' , 'admin123456')
    r = requests.get(self.base_url, auth = auth_user, params = {'nid' : 1})
    result = r.json()
    self.assertEqual(result['status'], 200)

数字签名

在使用 HTTP/SOAP 协议传输数据的时候,签名作为其中一个参数,可以起到关键作用:

先来一个简单的,通过客户的密钥,服务端的密钥匹配;

这个很有好理解,例如一个接口传参为:

http://127.0.0.1:8000/api/?a=1&b=2

假设签名的密钥为:@signpassword

加上签名之后的接口参数为:

http://127.0.0.1:8000/sign/?a=1&b=2&sign=@signpassword

但是,这样的sign 参数明文传输是不安全的,一般会选择一些加密算法,比如MD5 算法(MD5算法是不可逆向的),比如MD5代码如下:

      

import hashlib  
md5 = hashlib.md5()
sign_str = "@signpassword"
sign_bytes_utf8 = sign_str.encode()
md5.update(sign_bytes_utf8)
sign_md5 = md5.hexdigest()
print(sign_md5)

执行后得到:6648e929329e53e7a91c50ae685a88b5

此时带签名的接口为:

http://127.0.0.1:8000/sign/?a=1&b=2&sign=6648e929329e53e7a91c50ae685a88b5

所以,当服务器接收到请求后,同样需要对“signpassword”进行 MD5 加密,然后,比对与调用者传来的 sign 加密串是否一致,从而来鉴别调用者是否有权限使用该接口。

接着,我们来理解一个复杂一点的:把sign参数传递为api key(申请获取)+timestramp(时间戳)同样需要 用代码来实现,原理和上面这个一致的。(伪代码)

def setUp(self):   
    self.base_url = "http://127.0.0.1:8000/api/sec_add_notice/"
    # app_key
    self.api_key = "&APIkey"
    # 当前时间
    now_time = time()
    self.client_time = str(now_time).split('.')[0]
    # sign
    md5 = hashlib.md5()
    sign_str = self.client_time + self.api_key
    sign_bytes_utf8 = sign_str.encode(encoding="utf-8")
    md5.update(sign_bytes_utf8)
    self.sign_md5 = md5.hexdigest()

接口加密

通常接口会使用更复杂一点的方式来进行加密的操作,常见的是AES的使用,放一张图让大家感受一下AES加解密的过程:

Python里面有一个很好的黑魔法,叫PyCrypto库,支持常见的 DES、AES 加密以及 MD5、SHA 各种 HASH 运算。

官方网站下载最新版本:

https://www.dlitz.net/software/pycrypto/

另外,也可以在 PyPi 仓库中下载安装:

https://pypi.python.org/pypi/pycrypto

对于AES的加密来说,看一下用了PyCrypto库的结果

加密:

   

from Crypto.Cipher import AES
obj = AES.new('This is a key123', AES.MODE_CBC, 'This is an IV456')
message = "The answer is no"
ciphertext = obj.encrypt(message)
print(ciphertext)

程序运行后的结果为:

b'\xd6\x83\x8dd!VT\x92\xaa`A\x05\xe0\x9b\x8b\xf1'

AES加密里面有两个关键,一个是key(必须为16,24,32位),一个是VI(必须为16位)

解密:解谜者必须要同时知道key和VI才可以解密

obj2 = AES.new('This is a key123', AES.MODE_CBC, 'This is an IV456')
s = obj2.decrypt(ciphertext)
print(s)

由于Crypto库的作者已停止维护,现推荐安装:

 pip install pycrytodome

pycryptodome官方文档:

https://www.pycryptodome.org/en/latest/

下面以Jmete为例介绍如何实现加密接口的处理,针对这种接口测试的时候,在发起请求以前,我们需要对接口进入如下特殊处理:

1.和开发沟通接口加密的算法以及需要的信息,如果是SHA加密,和开发沟通获取加密的秘钥,以及加密和编码的关键字符串

2.在发起接口请求以前,对接口明文数据进行预处理,在Jmeter的接口实现中,可以通过添加Beanshell脚本,在Beanshell脚本中基于上一个步骤获取到的信息,采用和服务端相同的加密方法和加密流程对接口明文进行加密和编码

3.基于加密后的密文发起加密请求