API认证及数据加密

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: API认证API认证加密过程:客户端和服务端保留一个相同的随机字符串,如:key = 'uiakjsdfasjdf898';将随机字符串key与当前时间戳进行拼接,如:uiakjsdfasjdf898|1540192216171,将拼接生成的新...

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)

相关文章
|
4月前
|
SQL 安全 API
数字堡垒之下:网络安全漏洞、加密技术与安全意识的博弈探索RESTful API设计的最佳实践
【8月更文挑战第27天】在数字化浪潮中,网络安全成为守护个人隐私与企业资产的关键防线。本文深入探讨了网络漏洞的成因与影响,分析了加密技术如何为数据保驾护航,并强调了提升公众的安全意识对于构建坚固的信息防御系统的重要性。文章旨在为读者提供一场思维的盛宴,启发更多关于如何在日益复杂的网络世界中保护自己的思考。
|
4月前
|
中间件 API 网络架构
Django后端架构开发:从匿名用户API节流到REST自定义认证
Django后端架构开发:从匿名用户API节流到REST自定义认证
46 0
|
4月前
|
API
【Azure Developer】调用Microsoft Graph API获取Authorization Token,使用的认证主体为 Azure中的Managed Identity(托管标识)
【Azure Developer】调用Microsoft Graph API获取Authorization Token,使用的认证主体为 Azure中的Managed Identity(托管标识)
|
4月前
|
JavaScript 前端开发 Linux
【Azure 应用服务】NodeJS Express + MSAL 实现API应用Token认证(AAD OAuth2 idToken)的认证实验 -- passport.authenticate()
【Azure 应用服务】NodeJS Express + MSAL 实现API应用Token认证(AAD OAuth2 idToken)的认证实验 -- passport.authenticate()
|
4月前
|
JSON 算法 API
【Azure API 管理】APIM 配置Validate-JWT策略,验证RS256非对称(公钥/私钥)加密的Token
【Azure API 管理】APIM 配置Validate-JWT策略,验证RS256非对称(公钥/私钥)加密的Token
|
4月前
|
API 数据安全/隐私保护 网络架构
【Azure API 管理】解决调用REST API操作APIM(API Management)需要认证问题(Authentication failed, The 'Authorization' header is missing)
【Azure API 管理】解决调用REST API操作APIM(API Management)需要认证问题(Authentication failed, The 'Authorization' header is missing)
|
5月前
|
安全 API 网络安全
​邮箱OTP认证验证API发送邮件接口
**摘要 (Markdown格式):** OTP认证增强在线服务安全,尤其适用于邮箱验证。AOKSend提供邮箱OTP验证API,实现安全的邮件发送和用户身份验证。关键优势包括提高安全性、简化用户体验、实时发送、可扩展性和多层安全。配置涉及生成API密钥、设置SMTP、实现OTP逻辑、发送邮件及验证。AOKSend的分析工具帮助优化策略,适合各规模企业。
|
7月前
|
API 数据安全/隐私保护
单页源码加密屋zip文件加密API源码
单页源码加密屋zip文件加密API源码 api源码里面的参数已改好,往服务器或主机一丢就行,出现不能加密了就是加密次数达到上限了,告诉我在到后台修改加密次数
46 1
|
7月前
|
JSON 安全 API
【专栏】四种REST API身份验证方法:基本认证、OAuth、JSON Web Token(JWT)和API密钥
【4月更文挑战第28天】本文探讨了四种REST API身份验证方法:基本认证、OAuth、JSON Web Token(JWT)和API密钥。基本认证简单但不安全;OAuth适用于授权第三方应用;JWT提供安全的身份验证信息传递;API密钥适合内部使用。选择方法时需平衡安全性、用户体验和开发复杂性。
837 0
|
7月前
|
监控 安全 API
短信服务的API秘钥认证如何防止滥用
短信服务的API秘钥认证如何防止滥用