天天看點

API認證及資料加密

API認證

API認證加密過程:

  1. 用戶端和服務端保留一個相同的随機字元串,如:

    key = 'uiakjsdfasjdf898'

    ;
  2. 将随機字元串

    key

    與目前時間戳進行拼接,如:

    uiakjsdfasjdf898|1540192216171

    ,将拼接生成的新字元串通過

    md5()

    加密;
  3. 用戶端将第2步生成的md5值和目前時間戳做為URL參數傳給服務端,URL如: http://127.0.0.1:8000/test/?sign=fb7005761539b0d18d130455a9de9914&ctime=1540192216171
  4. 服務端接到用戶端傳遞過來的md5和時間戳後,用伺服器端保留的随機字元串

    key

    (此字元串與用戶端保留的字元串一緻)和用戶端傳過來的時間,并使用與第2步相同的算法生成一個md5值,然後将服務端的md5值與用戶端傳遞過來的md5值做比較,如果相同,則證明此用戶端是受信任的;
注意:不要認為至此認證就結束了,此認證機制還存在以下兩點漏洞:
  1. 如果用戶端在向服務端發起請求時,URL被黑客拿到,黑客也可以利用此URL向服務端發起請求;

    解決思路:在服務端維護一個md5值的字典,将每次認證過的md5值存儲起來,下次請求來時判斷用戶端傳過來的md5值是否已經存在,如果存在就拒絕通路;

  2. 就算解決了第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個字元,是以

128 - 11 = 117

;以此類推:當加密長度為2048位元組時,可加密的字元長度為

2048 / 8 - 11 = 245

但是當我們要發送的資料達到幾KB,甚至幾MB時,怎麼辦呢?看看接下來的,大資料加密!

大字元串資料加密

對于大字元串加密的思路:

  1. 以加密長度為1024位元組為例,将大字元串的資料分割成若幹個小字元串,每段字元串長度為117,然後再循環對小字元串加密,然後再将加密後的小字元串拼接成大字元串傳給服務端;
  2. 服務端将加密的大字元串分割成每段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)