Requests21--接口加密测试
接口安全机制
用户认证
数字签名
接口加密
用户认证
用户认证的意义
认证是客户端要给服务器出示一些自己的身份证明,来证明自己是谁,一旦服务器知道了客户端的身份,就可以判定客户端是否被允许进行访问,用于辨别访问服务器的用户的合法性
常见的用户认证方式
- Basic基本认证(在一开始访问服务器的时候,服务器会让客户提供账户和密码,如果账户和密码正确,则允许访问服务器)
- Digest摘要认证
- SSL Client(需要证书,收费,导入证书以后,接口地址中使用https)
- 表单认证(首先客户端连接服务器,服务知道了客户端访问服务器,此时服务器会通过一些算法自己产生或让客户机产生一个sessionid或token,服务器会把这个sessionid或token发给客户机,客户机带着服务器发来的session或token,连同其他要发送给服务器的参数一起重新发给服务器,服务器在收到所有数据后,判断客户机新发的session或token是之前自己发给客户机的,则认为认证通过,允许访问接口,在处理其它数据)`
Basic基本认证
客户端请求服务器,服务器返回401状态码来初始化认证质询,浏览器收到质询时,会打开一个对话框,要求用户输入用户名和密码,然后将用户名:密码用Base64编码,再用Authorization请求首部发送给服务器
Base64是一种用64个可打印字符(包括大小写字母、数字、+、/)来表示任意二进制数据的方法。每3个原字符编码为4个Bse64字符,非3倍数的补=
用户输入用户名和密码后,浏览器会保存用户名和密码,用于构造Authorization值。当关闭浏览器后,用户名和密码将不再保存
Base64编码不属于加密范畴,可以被逆向解码,等同于明文,因此Basic传输认证信息是不安全的,这种认证方式是一种相对较弱的认证方式, 安全性较低,为了传输安全,需要配合SSL使用
requests.get(url, auth)
Basic基本认证使用auth参数表示
auth=(用户名,密码)
必须是元组形式
auth在 request 请求头中发送
用户认证测试
'''用户认证测试
http://ip/apitest/auth-login/
post
参数:username、password
认证账号和密码:admin、admin123456
返回值类型:text/html
'''
#不认证
import requests
url='http://192.168.221.128/apitest/auth-login/'
args={'username':'dumb', 'password':'blind'}
# res=requests.post(url, args)
# print(res.text)
# 进行基本认证
author=('admin','admin123456')
res=requests.post(url,data=args,auth=author) #data是发送表单数据,此时args使用urlencode编码,username=dumb&password=blind;auth使用base64编码
print(res.text)
Digest摘要认证
摘要认证的基本原则是绝不通过网络发送明文密码,而是发送一个密码的摘要信息来取代密码,并且这个摘要信息是不可逆的,也就是用非对称加密算法来加密,如md5加密。
如客户端需要加密的信息是hello world,经过md5加密后请求发送的是5eb63bbbe01eeed093cb22bb8f5acdc3这串密文,任何人截获到这段密文是无法反推出hello world的,服务端收到这个密文后,先去数据库中找到存储的密文hello world,然后对hello world进行md5加密,对比加密后的字符串和客户端发过来的字符串,一样的话就验证通过
Digest摘要认证步骤
客户端访问Http资源服务器,Digest认证时,服务器返回nonce(随机数number once)和realm
客户端构造Authorization请求头,值包含realm、nonce、username、uri 等字段信息
其中,realm和nouce就是第一步返回的值,nonce只能被服务端使用一次
服务器验证包含Authorization值的请求,若验证通过则可访问资源
Digest认证可以防止密码泄露和请求重放,但和Basic认证一样,每次都会发送Authorization请求头,也就相当于重新构造此值,故没办法防假冒,所以安全级别也较低
SSL Client认证
SSL Client认证步骤
1)客户端请求服务资源,服务器要求客户端出示数字证书(客户端已购买或下载了证书)
2)客户端发送数字证书
3)服务器通过数字证书机构的公钥验证数字证书的合法性,验证通过后取出证书的公钥
4)服务端随机生成一个随机数即为对称密钥,并使用非对称算法和证书公钥加密,这个加密后的字符串,只有发送的客户端能解
5)客服端使用非对称解密算法和证书私钥获取服务端发送的对称密钥,后续客户端和服务端的请求直接基于对称算法和对称密钥。由于只有客户端和服务端有对称密钥,所以后续发送的请求较安全。
SSL认证的特点
安全级别较高,可以防泄漏、假冒、重放
SSL客户端认证在实际中用得不多,因为需要在客户端中安装证书(升级麻烦)、还需要承担证书费用
最新的requests模块中自带了一个certifi包,requests会使用它里边的证书。这样用户就可以在不修改代码的情况下更新它们的可信任证书
不需要修改请求的代码,只需要将URL替换成HTTPS,就可以发送HTTPS的请求
File-Settings-Project-Python Interpreter,certifi
分析的正文合适编码
#使用requests访问https://www.baidu.com
import requests
url='https://www.baidu.com'
res=requests.get(url)
print(res.apparent_encoding) #requests分析的正文合适编码
print(res.encoding) #响应正文真正使用的编码
res.encoding=res.apparent_encoding
print(res.text)
表单认证
表单认证一般都会配合cookie+sessionId的使用,现在很多Web站点都是使用此认证方式
用户在登录页中填写用户名和密码,服务端认证通过后会将sessionId返回给浏览器端,浏览器会保存sessionId到浏览器的Cookie中
因为Http是无状态的,所以浏览器使用Cookie来保存sessionId。下次客户端发送的请求中会包含sessionId值,服务端发现sessionId存在并认证过则会提供资源访问
数字签名
数字签名(又称公钥数字签名)是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明
数字签名的作用
数字签名用于鉴权
数字签名防数据篡改
数字签名用于鉴权
客户通过注册等方式获得密钥如api_key,同时服务器存储此密钥,鉴权时要判断客户端传来的密钥,与服务端的密钥是否匹配
添加签名
假设一个接口:http://服务器IP/api/?a=1&b=2
假设签名的密钥为:@admin123
加上签名之后的接口:http://服务器IP/api/?a=1&b=2&sign=@admin123
签名sign参数明文传输不安全,一般会通过加密算法进行加密
鉴权时,可以只加密签名密钥
MD5加密算法
MD5 即 Message-Digest Algorithm 5(消息摘要算法第五版),用于确保信息传输是否完整一致
MD5算法不可逆
假设对密钥@admin123按MD5加密发送,当服务器接收到请求后,同样对“@admin123” 进行 MD5 加密,比对服务器的加密串与客户端传来的签名加密串是否一致,从而来鉴别调用者是否有权限访问该接口
MD5加密算法
'''
导入模块:import hashlib #内置模块,无需安装
创建加密器:cryptor=hashlib.md5()
字符串转字节形式:sign_str_bytes='@admin123'.encode('utf-8') #编码要与开发一致
拼接加密串:cryptor.update(sign_str_bytes)
生成16进制加密摘要:sign_md5=cryptor.hexdigest()
MD5加密签名
'''
# MD5加密字符串数据
import hashlib #1、导入模块
cryptor=hashlib.md5() #2、创建md5加密器
s='@admin123' #3、指定要加密的字符串
s_byte=s.encode('utf-8') #4、将字符串转字节形式,编码方式一定要与开发一致
cryptor.update(s_byte) #5、进行加密(拼接加密串)
s_md5=cryptor.hexdigest() #6、转16进制的加密摘要
print(s_md5)
签名加密后的接口
http://服务器IP/sign/?a=1&b=2&sign=sign_md5
编写加密文本
"""
md5加密,测试过程中如果开发加密了,测试也要加密,要问开发加密算法要求 规定 和如何使用
一切由开发告知
"""
import hashlib
cryptor = hashlib.md5() # 创建md5加密器
api_key = '@admin123'#表示签名的原始字符串
api_key_byte = api_key.encode('utf-8')#变成字节形式
cryptor.update(api_key_byte)#拼接加密串,保存加密串
api_key_crypt=cryptor.hexdigest()#十六进制摘要(加密后的文本)
print(api_key_crypt)
数字签名鉴权过程
服务器根据收到的时间戳(客户端当前时间),计算服务器当前时间与收到的时间戳是否小于60s,60s以内的请求允许访问接口,否则进行超时处理
服务器根据收到的时间戳(客户端当前时间)和api_key(密钥字符串) 拼接成新字符串,用MD5加密,将加密后的字符串作为用户签名
服务器比对客户端发来的用户签名和自己生成的用户签名,如果相等说明签名验证通过,否则返回签名验证失败
获得整数时间
#获得整数时间(1970以来经历的秒数,本身是浮点数)
import time
now=time.time()
print(now)
print(int(now))
访问无签名接口
'''
地址:http://ip:8000/sign/add_event/
参数:eid(整数,不重复,发布会id)、name(字符串,不重复,发布会名称)、limit(整数,限制参加人数)、address(字符串,举办地址)、start_time(日期时间型,举办时间,必须提供年月日)
方法:post
数据库表:guest.sign_event
返回:json
'''
import requests
url='http://192.168.237.128:8000/sign/add_event/'
args={'eid':3,'name':'vivo手机发布会','limit':1200,'address':'北京中关村', 'start_time':'2021-10-1 14:10'}
res=requests.post(url, args)
print(res.json())
访问有数字签名的加密接口
'''
地址:http://ip:8000/sign/signature_add_event/
参数:eid(整数,不重复,发布会id)、name(字符串,不重复,发布会名称)、limit(整数,限制参加人数)、address(字符串,举办地址)、start_time(日期时间型,举办时间,必须提供年月日)、time(1970年以来的时间戳整数部分)、sign(md5(time+api_key),api_key:&Guest-Bugmaster)
方法:post
数据库表:guest.sign_event
返回:json
'''
import requests,time,hashlib
url='http://192.168.221.128:8000/sign/signature_add_event/'
now=int(time.time())
apikey='&Guest-Bugmaster'
sign=f'{now}{apikey}'
sign_byte=sign.encode('utf-8') #字符串的字节形式,utf-8编码
cryptor=hashlib.md5()
cryptor.update(sign_byte)
sign_md5=cryptor.hexdigest()
# print(sign_md5)
args={'eid':5,'name':'华为2手机发布会','limit':1200,'address':'北京中关村', 'start_time':'2021-10-1 14:10','time':now, 'sign':sign_md5}
res=requests.post(url, args)
print(res.json())
数字签名鉴权测试
"""
数字签名鉴权测试
接口需求:发布会接口
地址:http://192.168.175.128:8000/sign/signature_add_event/
方法:post
参数:eid、name、limit、address、start_time、time、sign
此处time是客户端的1970年以来秒数的整数部分(需单独传此参数)
sign是数字签名:此处的计算公式为md5(time+api_key),api_key为密钥字符串,此处api_key为&Guest-Bugmaster,md5加密使用utf-8编码
返回值:json字典数据
"""
import time,hashlib,requests
client_time = int(time.time()) # time.time() 从1970年零点到目前的持续秒数,结果为浮点数
# print(client_time)
api_key = '&Guest-Bugmaster'
sign0 =str(client_time)+api_key# 原始的待加密的字符串
sign0_byte = sign0.encode('utf-8')#待加密字符串的二进制形式(字节形式)
cryptor = hashlib.md5()#MD5加密器
cryptor.update(sign0_byte)# 进行加密
sign_crypt = cryptor.hexdigest()#最终加密字符串
# print(sign_crypt)
address = 'http://192.168.175.128:8000/sign/signature_add_event/'
argument ={'eid':101,'name':'vivo手机发布会','limit':200,'address':'北京市朝阳区南沙滩','time':client_time,'sign':sign_crypt,'start_time':'2020-3-1 14:20:00'}
res = requests.post(url=address,data=argument)
print(res.json())
数字签名防数据篡改
加密整个参数串
假设接口是http://服务器IP/sign/?a=1&b=2,签名的密钥是@admin123
签名的明文:a=1&b=2&api_key=@admin123
对整个接口参数值生成 MD5 加密串如:786bfe32ae1d3
签名加密后的接口:http://服务器IP/sign/?a=1&b=2&sign=786bfe32ae1d3
整个接口的参数做了加密,只要任意一个参数发改变,签名验证就会失败,从而起到了鉴权及数据完整性的保护
弊端:因MD5加密不可逆,所以服务器端必须已知客户端的接口参数和值,否则签名的验证就会失败
加密json字符串
加密携带数字签名的整个参数
假设参数a=1、b=2,签名的密钥是@admin123
加密算法:md5
说明
需要使用json.dump(字典)将字典转为json字符串之后,再加密
接口加密
接口加密的含义和算法
用于对要传输的参数进行加密
内置的Hashlib模块是一个加密模块,主要提供SHA1、SHA224、SHA256、SHA384、SHA512、MD5等算法
http请求post的内容其实可以通过抓包获取,极为不安全,所以往往会将post的数据加密后再发送,其用户密码或者比较隐私的信息都会以加密的方式存到数据库
一般最常用的就是通过md5对数据进行加密
AES加密算法
AES(Advanced Encryption Standard,高级加密标准)
参数包括key(签名)、IV(初始化向量)、加密模式(如CBC:Cipher Block Chaining,加密块链模式,若要加密超过块大小的数据,需要填充)
key字符串必须是字节类型,长度必须为16、24或32字节
IV字符串必须是字节类型,长度只能为16字节
接口调用者需要提供key、IV、要加密的文本
要加密的字符串长度必须是16倍数,对字符串的字节形式进行加密
AES加密过程
安装pycryptodome
导入AES:from Crypto.Cipher import AES
获取key和iv
创建加密器:cryptor=AES.new(key, AES.MODE_CBC, iv)
字符串填充总长度到16倍数:s=s+' '*((16-len(s))%16)
加密:cipher=cryptor.encrypt(欲加密字符串.encode('utf-8'))
处理加密串中的+与\:base64.urlsafe_b64encode(cipher) #字符长度非4倍数时补(空格)
求余
print(12%16) #余数与除数同号
print(-12%16) #余数与除数同号
print(12%-16) #余数与除数同号
print(-12%-16) #余数与除数同号
字符串扩充到16倍数
s='@admin123@admin123'
print(len(s))
# while len(s)<16: #只适用要16字符以内
# s=' '+s
# print(s,len(s))
# while len(s)<16: #只适用要16字符以内
# s=s+' '
# print(s,len(s))
# print((16-len(s))%16) #(16-9)%16=7%16=7,(16-18)%16=-2%16
s=' '*((16-len(s))%16)+s
print(s,len(s))
AES加密json数据
#AES加密
from Crypto.Cipher import AES
import base64
#1、指定签名key或api_key、向量iv,要加密的字符串,全变成字节形式
key=b'This is a key123' #签名,b表示二进制/字节
# 1234567890123456
iv=b'This is an IV456' #初始化向量
# 1234567890123456
#key字符个数必须是16/24/32,iv的字符个数必须是16
# print(len(key), len(iv))
s='@admin123' #长度为9,必须是16的倍数
s=s+' '*((16-len(s))%16)
# print(len(s))
s_byte=s.encode('utf-8')
#2、创建加密器
cryptor=AES.new(key, AES.MODE_CBC, iv)
#3、加密
cipher=cryptor.encrypt(s_byte)
print(cipher) #二进制/字节形式
#4、base64编码
cipher=base64.urlsafe_b64encode(cipher)
print(cipher)
字典变字符串
args={'eid':1, 'name':'jhon', 'isA':False, 'isB':None}
print(str(args))
import json
print(json.dumps(args)) #序列化
加密字典
from Crypto.Cipher import AES
import json, base64
#指定签名key、向量iv、待加密字典d
key=b'This is a key123'
iv=b'This is an IV456'
d={'eid':1,'name':'zhsan'}
s=str(d) #写法1
# s=json.dumps(d) #写法2
s=s+' '*((16-len(s))%16)
# print(s,len(s))
s_byte=s.encode('utf-8')
#创建加密器
cryptor=AES.new(key, AES.MODE_CBC, iv)
#加密
cipher=cryptor.encrypt(s_byte)
#base64编码
cipher=base64.urlsafe_b64encode(cipher)
print(cipher)
#rpys2P0E6paKaQUPLMFcfZGsNfsKiCk4N6AJnZ7rJ7Q=
#nWacCvIX_ZCBKd1nUszFXKyNKqX_VPz91N3RSJmI6Ok=
接口加密测试(同时实现:数据签名防数据篡改测试)
"""
接口加密测试:对要发送的数据全部加密 --只有开发加密测试才可以加密
接口需求
地址:http://IP:192.168.175.128/8000/sign/encrypt_get_guest_list/
功能:查询客户信息
方法:post
参数:eid、phone(18011001100)
加密:对所有参数整体加密,此处使用aes算法
app_key=b'W7v4D60fds2Cmk2U'
iv=b"1172311105789011"
加密串中的+和\用urlsafe_b64encode处理
加密数据以form表单形式发送,键名data
返回值:json字典数据
"""
from Crypto.Cipher import AES
import json, requests, base64
app_key = b'W7v4D60fds2Cmk2U'
# 0123456789012345
iv = b"1172311105789011"
# 0123456789012345
cryptor = AES.new(app_key, AES.MODE_CBC, iv) # 创建加密器
argument = {'eid': 1, 'phone': '18011001100'}
argument_str = json.dumps(argument)
argument_str = argument_str + ' ' * (16 - len(argument_str) % 16)
argument_byte = argument_str.encode('utf-8') # 转字节
argument_crypt = cryptor.encrypt(argument_byte)
print(argument_crypt)
argument_crypt = base64.urlsafe_b64decode(argument_crypt)
print(argument_crypt)
argument = {'data': argument_crypt} # data是本项目的规定
address = 'http://192.168.175.128:8000/sign/encrypt_get_guest_list/'
res = requests.post(url=address, data=argument)
print(res.json())
接口加密测试
'''
地址:http://服务器IP:8000/sign/encrypt_get_guest_list/
功能:查询发布会客户信息
方法:post
参数:eid(关联的发布会id)、phone(手机号)
数据库表:guest.sign_guest
加密:对所有参数整体加密,使用AES算法
app_key=b'W7v4D60fds2Cmk2U'
iv=b"1172311105789011"
加密数据以form表单形式发送,键名data
返回值:json字符串'''
#导入模块
from Crypto.Cipher import AES #导入模块
import json, base64, requests
#key签名、iv向量、要加密的参数字典,字典转字符串,扩充字符串长度,转字节
key=b'W7v4D60fds2Cmk2U'
# 1234567890123456
iv=b"1172311105789011"
# 1234567890123456
args={'eid':1,'phone':'18011001100'}
args_s=json.dumps(args)
args_s=args_s+' '*((16-len(args_s))%16)
# print(len(args_s))
args_byte=args_s.encode('utf-8')
#创建加密器
cryptor=AES.new(key, AES.MODE_CBC, iv)
#加密
cipher=cryptor.encrypt(args_byte)
#转base64编码
cipher=base64.urlsafe_b64encode(cipher)
# print(cipher)
#发送请求
url='http://192.168.237.128:8000/sign/encrypt_get_guest_list/'
args={'data':cipher}
res=requests.post(url, args)
#查看响应结果
print(res.json())
接口加密测试小结
1.数字签名事实上属于用户认证,通常是由用户申请的,测试时可以问开发
2.数字签名可以实现鉴权,也就是判断用户是否是合法用户,最简单的实现方法是只对签名等部分数据加密
3.数字签名还可以实现防止数据篡改,这时候就是对接口所有参数进行加密
4.常用的加密算法:md5,aes...
5.md5加密
cyptor = hashlib.md5()创建加密器
cyptor.updata(字节数据)
6.aes加密
cryptor = AES.new(key,AES.MODE_CBC,iv)#key必须是16、24、32位,iv必须是16位
cryptor.encrypt(字节数据)#字节数据之前的字符串必须是长度位16倍数
本文来自博客园,作者:暄总-tester,转载请注明原文链接:https://www.cnblogs.com/sean-test/p/15593227.html

浙公网安备 33010602011771号