API認證
API認證加密過程:
- 用戶端和服務端保留一個相同的随機字元串,如:
;key = 'uiakjsdfasjdf898'
- 将随機字元串
與目前時間戳進行拼接,如:key
,将拼接生成的新字元串通過uiakjsdfasjdf898|1540192216171
加密;md5()
- 用戶端将第2步生成的md5值和目前時間戳做為URL參數傳給服務端,URL如: http://127.0.0.1:8000/test/?sign=fb7005761539b0d18d130455a9de9914&ctime=1540192216171 ;
- 服務端接到用戶端傳遞過來的md5和時間戳後,用伺服器端保留的随機字元串
(此字元串與用戶端保留的字元串一緻)和用戶端傳過來的時間,并使用與第2步相同的算法生成一個md5值,然後将服務端的md5值與用戶端傳遞過來的md5值做比較,如果相同,則證明此用戶端是受信任的;key
注意:不要認為至此認證就結束了,此認證機制還存在以下兩點漏洞:
如果用戶端在向服務端發起請求時,URL被黑客拿到,黑客也可以利用此URL向服務端發起請求;
解決思路:在服務端維護一個md5值的字典,将每次認證過的md5值存儲起來,下次請求來時判斷用戶端傳過來的md5值是否已經存在,如果存在就拒絕通路;
就算解決了第1點問題,還有一個問題:用戶端在向服務端發起請求時,URL被黑客拿到,如果黑客的請求比受信用戶端的請求先到達服務端,那麼受信用戶端的請求就會被拒;
解決思路:在服務端接收到請求時也生成一個目前時間戳,并與用戶端傳遞過來的時間戳做比較,如果時間差大與3秒(此時間可根據實際情況自定義),則拒絕通路;
用戶端代碼 :
import requests
import time
import hashlib
# 生成md5值
def gen_sign(ctime):
key = 'uiakjsdfasjdf898'
val = '%s|%s' %(key,ctime,)
obj = hashlib.md5()
obj.update(val.encode('utf-8'))
return obj.hexdigest()
# 用戶端發起post請求
ctime = int(time.time() * 1000)
result = requests.post(
url='http://127.0.0.1:8000/test/',
params={'sign':gen_sign(ctime),'ctime':ctime}, # url參數
data='adfasdfasdfasdfasdf' # 發送給服務端的資料,
)
print(result.text)
print(result.url)
print(result.ok)
輸入結果:
{"status":true,"data":666}
http://127.0.0.1:8000/test/?sign=b5abce59bd5776db5e4a107b403775c5&ctime=1540194227854
True
服務端代碼:
定義一個API認證的類,在
dispatch()
方法中實作API認證,需要經過認證的視圖類需要繼承這個認證類。
import hashlib
import time
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response
from utils.security import decrypt
key = 'uiakjsdfasjdf898'
def gen_sign(ctime):
val = '%s|%s' %(key, ctime,)
obj = hashlib.md5()
obj.update(val.encode('utf-8'))
return obj.hexdigest()
SIGN_RECORD = {}
# 定義一個API認證的類,在`dispatch()`方法中實作API認證,需要經過認證的視圖類需要繼承這個認證類。
class APIAuthView(APIView):
def dispatch(self, request, *args, **kwargs):
client_sign = request.GET.get('sign') # 用戶端簽名
client_ctime = int(request.GET.get('ctime')) # 用戶端時間
server_time = int(time.time() * 1000) # 服務端時間
# 請求時間大于3秒,拒絕通路
if server_time - client_ctime > 3000:
return Response({'status': False, 'error': '你在路上的時間太久了'})
# 用戶端URL攜帶的MD5如果已經驗證過,則拒絕通路
if client_sign in SIGN_RECORD:
return Response({'status': False, 'error': '簽名已經被使用過了'})
# 如果用戶端的md5值與服務端的md5值不相同,則說明說請求url被篡改,拒絕通路
server_sign = gen_sign(client_ctime)
if server_sign != client_sign:
return Response({'status': False, 'error': '簽名錯誤'})
SIGN_RECORD[client_sign] = client_ctime
return super().dispatch(request, *args, **kwargs)
# 需要經過API認證的視圖類需要繼承認證類`APIAuthView`
class TestView(APIAuthView):
def post(self,request):
print(request.data)
print(request.url)
return Response({'status':True,'data':666})
注意:此示例中的服務端代碼是一個Django程式,在驗證示例時,還需要在urls.py檔案是添加如下代碼:from django.contrib import admin from api.views import TestView from django.urls import path urlpatterns = [ path('admin/', admin.site.urls), path('test/',TestView.as_view()), ]
資料加密
資料加密算法這裡我用RSA。
安裝rsa子產品
pip3 install rsa
生成一對公鑰和私鑰
# ######### 1. 生成公鑰私鑰 #########
pub_key_obj, priv_key_obj = rsa.newkeys(1024) # 128 - 11 = 117,隻能對117個字元加密
# 公鑰字元串
pub_key_str = pub_key_obj.save_pkcs1()
pub_key_code = base64.standard_b64encode(pub_key_str) # 對公鑰再次進行base64編碼
print(pub_key_code)
# 私鑰字元串
priv_key_str = priv_key_obj.save_pkcs1()
priv_key_code = base64.standard_b64encode(priv_key_str) # 對私鑰再次進行base64編碼
print(priv_key_code)
注意:公鑰用來對資料進行加密,私鑰用對加密後的資料進行解密;是以用戶端生的私鑰需要發送到服務端并儲存起來;
輸出結果:
# 公鑰字元串 ,位元組類型
b'LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JR0pBb0dCQUtiTGluemJqMkttb1hOUVRvVlBtV2JzVDVqV3F6cm1scjJRU09HR0o0TnVzM0FFMDhiL0RESHgKaW5BTkN1djRVcVB2M3FlWWJiKzRhR3cvMXhZaTJNekVDL2h1cWQwZXdLMk9ha1Y1aWwvZEpMdlB3SHJLN2IrZQpSQnhZaUoyOUh3QWhUemJEaFcvQjBUaGh0M0dmVThPQjVnWVNhS29MTTJndlpxMURIb1kvQWdNQkFBRT0KLS0tLS1FTkQgUlNBIFBVQkxJQyBLRVktLS0tLQo='
# 私鑰字元串 ,位元組類型
b'LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlDWUFJQkFBS0JnUUNteTRwODI0OWlwcUZ6VUU2RlQ1bG03RStZMXFzNjVwYTlrRWpoaGllRGJyTndCTlBHCi93d3g4WXB3RFFycitGS2o3OTZubUcyL3VHaHNQOWNXSXRqTXhBdjRicW5kSHNDdGptcEZlWXBmM1NTN3o4QjYKeXUyL25rUWNXSWlkdlI4QUlVODJ3NFZ2d2RFNFliZHhuMVBEZ2VZR0VtaXFDek5vTDJhdFF4NkdQd0lEQVFBQgpBb0dBWjBuVVVNMkdWWWpxb2dZeEdjelpLaXRjZjBFd2VDRWpaL0Jac1k3cUdUSU1YR29nMnpKRjB3Zkl1dXJZCndKZmVWVGJOb3V0NXl5ZmZRbW1sdkwwNE5WZ2FRZFM5eHZSUmtUbkZ5WFZja2x5eTFFSklNQ1JhdXd4U3JadEgKOGRvUC9RSE93dm1IeXVvNDRaT1A4d3o1T1lwRitvSnpWYlZEWW9EdDlCMkJrd0VDUlFEVWFoSGJqTERzTEVkUApyQW4rWEs1UFQrUEFxQjhuSDcvTTBRZ2s3MURoMlpuZzdpZlZiVWhBWE4zVmxBeU5tcytQZTBWbUdvUDBNc1BUCjdEbVdDQldaUk92MWp3STlBTWtGSWppeGNhMW9aRUJJd1cyaFJXVndlN2grYmhHWVhzMk5BZEloOWpZUW85bGgKcGJJRnJ3YnRVQWg0VkVLenROMWFyeVIydk1rekFZcnNVUUpGQUtUVm52LzV3TDIxYXExSCt3VlpsS2JWZnc3ZApLRGVyS3FMZFAyMnlETmtHaktRQkRBWlNaTFFWbk13RnRHd2F5NkV6YnRwYUR6WHNRd3pzam85L3ZJc1E4ZTYvCkFqd2swUWJpZ1VWRHNFSGtNQzhWQ0J0d3A3aFJJdjYveER0Z3hEbVlKZFkxTXJqL29FMjduQ1RoVE9lQ2xaOUIKRkM3RTk4M3FETUVvekdtMDZ2RUNSQ05UaGFTT05aWHVUZnFDRXVtdm9YYTFJcDg1NTdYaXFQU2ZlbXNXYXZmcApFOWJDZy9URUEwa2dzeFd1c0RjVzQ5S2IyVlhWekJrUUJsWWxiRElqdmhCV3czbVUKLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K'
資料加密:
# # ######### 2. 加密 #########
def encrypt(pub_key_code,value):
key_str = base64.standard_b64decode(pub_key_code)
pk = rsa.PublicKey.load_pkcs1(key_str)
result = rsa.encrypt(value.encode('utf-8'), pk)
return result
data = encrypt(pub_key_code,'zff') # “zff”是要被加密的資料
print(data)
print(len(data))
# 加密後的資料
b'GJy\xfc\x82R\xc6N\xebo_\xdad\xbf\x93\xe8\xb9\xd0y\xee\x0b\x9b\xe6\xad\x9f\x07\xcf4\x7f\x8f\x1b\xb4\xc4f\xa0\x01\xc8z\xf9\xd1\x89\xbd\xc0\x1b\xdfhi\x93\x14\x84\x1d\x15\xaa3y"2,\xeb\x1aP\xda2)\'\x98\x90R\xab\xf3\xfcV:\xf7\x12\xcd\xf2d}\xb4NZ\x021\xd6\xce=\x9e\xdf\x07\x0eD\xbc\xf8\xb9\xcdO\xe1\xb0R\x0e\x1cg\xc0X1"\xab\xcd\x18K\xc2\x9bn\xbb\xda{\xd1\xec\x1d\xbai\'\xef\x86\xa85C'
128
不管多大的資料,加密後的長度都是128個字元;
資料解密
# # ######### 3. 解密 #########
def decrypt(priv_key_code,value):
key_str = base64.standard_b64decode(priv_key_code)
pk = rsa.PrivateKey.load_pkcs1(key_str)
val = rsa.decrypt(value, pk)
return val
origin = decrypt(priv_key_code,data)
print(origin)
# 解密後資料,位元組類型
b'zff'
注意:加密長度為1024位元組時,可加密的字元長度為117?117是這麼計算得來的:8位元組=1字元,1024位元組=128字元,加密算法本身會占用11個字元,是以;以此類推:當加密長度為2048位元組時,可加密的字元長度為
128 - 11 = 117
2048 / 8 - 11 = 245
。
但是當我們要發送的資料達到幾KB,甚至幾MB時,怎麼辦呢?看看接下來的,大資料加密!
大字元串資料加密
對于大字元串加密的思路:
- 以加密長度為1024位元組為例,将大字元串的資料分割成若幹個小字元串,每段字元串長度為117,然後再循環對小字元串加密,然後再将加密後的小字元串拼接成大字元串傳給服務端;
- 服務端将加密的大字元串分割成每段128個字元長度,進行分段解密,然後再将解密後的小字元串拼接成元始資料。
import rsa
import base64
# ######### 1. 生成公鑰私鑰 #########
pub_key_obj, priv_key_obj = rsa.newkeys(1024) # 128 - 11 = 117
# 公鑰字元串
pub_key_str = pub_key_obj.save_pkcs1()
pub_key_code = base64.standard_b64encode(pub_key_str)
print(pub_key_code)
# 私鑰字元串
priv_key_str = priv_key_obj.save_pkcs1()
priv_key_code = base64.standard_b64encode(priv_key_str)
print(priv_key_code)
#
# # # ######### 2. 加密 #########
def encrypt(pub_key_code,value):
key_str = base64.standard_b64decode(pub_key_code)
pk = rsa.PublicKey.load_pkcs1(key_str)
value_bytes = value.encode('utf-8')
data_list = []
for i in range(0,len(value_bytes),117):
chunk = value_bytes[i:i+117]
result = rsa.encrypt(chunk, pk)
data_list.append(result)
return b''.join(data_list)
data = encrypt(pub_key_code,'zff'*1000)
print(len(data),data)
# # # ######### 3. 解密 #########
def decrypt(priv_key_code,bytes_value):
key_str = base64.standard_b64decode(priv_key_code)
pk = rsa.PrivateKey.load_pkcs1(key_str)
result = []
for i in range(0,len(bytes_value),128):
chunk = bytes_value[i:i+128]
val = rsa.decrypt(chunk, pk)
result.append(val)
return b''.join(result)
origin = decrypt(priv_key_code,data)
origin_str = origin.decode('utf-8')
print(origin_str)