Python向带有SSL/TSL认证服务器发送网络请求小实践(附并发http请求实现asyncio+aiohttp)

1. 写在前面

最近工作中遇到这样的一个场景:给客户发送文件的时候,为保证整个过程中,文件不会被篡改,需要在发送文件之间, 对发送的文件进行签名, 而整个签名系统是另外一个团队做的, 提供了一个接口服务完成签名,但访问这个接口需要提供他们团队提供的证书链先进行认证,所以需要和该服务端建立安全的链路,这里是用ssl双向认证的方式实现。

本篇文章主要是记录下如果是用Python给这种ssl双向认证的服务器发送post请求的时候,应该怎样携带证书去双向认证? 整个过程怎么实现的,如果需要处理的文件太多, 请求响应超时的情况下, 又怎么把同步http请求改成并发的http请求以减少响应时间?

ok, let’s go!

2. 基础知识

2.1 SSL/TSL

SSL是安全套接层(secure sockets layer),而TLS是SSL的继任者,叫传输层安全(transport layer security),是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密。比如HTTP协议是明文传输,加上SSL层之后,就有了雅称HTTPS。

SSL/TLS双向认证,也被叫做客户端证书认证,是一个验证客户端和服务器双方身份的过程,用来确保双向的通信安全性。
在单向SSL/TLS认证中,只有服务器需要向客户端提供证书用以证明自己的身份。而在双向认证中,客户端也需要使用证书来证明自己的身份,服务器才会允许连接和交换信息。

这种认证流程增加了安全性,确保通信双方都可信。

整个握手认证流程大概是这样:
在这里插入图片描述
双向认证的流程通常如下:

  1. 握手开始:客户端向服务器发送一个连接请求,告知服务器它支持的SSL/TLS版本和加密算法等。
  2. 服务器证书:服务器响应客户端的请求,并发送它的SSL/TLS证书给客户端。这个证书中包含了服务器的公钥和一些身份信息,并且由可信的证书颁发机构(CA)签发。
  3. 证书验证:客户端验证服务器证书的合法性,它会检查证书是否过期、是否被CA真实签发、域名是否匹配等。
  4. 客户端密钥交换:客户端根据服务器提供的公钥加密一个随机生成的对称加密密钥,并发送给服务器。
  5. 客户端证书请求:服务器向客户端发送一条请求消息,要求客户端也提供一份证书。
  6. 客户端证书:客户端发送它的SSL/TLS证书给服务器。如同服务器证书一样,客户端证书也包含了客户端的公钥和身份信息,并且由CA签发。
  7. 服务器验证客户端证书:服务端将验证客户端提供的证书,检查是否由信任的CA签发、是否过期、是否被吊销等。
  8. 预主密钥生成:客户端根据之前的握手过程生成一个预主密钥(Pre-Master Secret)。
  9. 安全参数生成:客户端和服务器都使用预主密钥和一些已经交换的信息计算出会话密钥。
  10. 完成握手:双方利用生成的会话密钥进行加密通讯。

在整个过程中,SSL/TLS的加密算法保证了传输内容的隐私和完整性。双向认证增加了一层安全性,因为不仅要验证服务器的身份,客户端的身份也同样需要验证。这在需要高安全性的场合,比如金融交易、企业系统访问中非常有用。

当然这个过程简单了解下即可,主要是能在实际场景中会使用。

2.2 证书

CA: 证书授权中心( certificate authority)。类似于国家出入境管理处一样,给别人颁发护照;也类似于国家工商管理局一样,给公司企业颁发营业执照。 它有两大主要性质:

  1. CA本身是受信任的 // 国际认可的
  2. 给他受信任的申请对象颁发证书

生成和管理SSL/TLS证书通常涉及以下几个步骤:

  • 创建密钥对(公钥和私钥)
    • 使用密码学工具(例如OpenSSL)创建一对密钥。
    • 私钥必须安全保管,确保只有授权个体才能访问。
  • 创建证书签名请求(CSR):
    • 使用私钥生成CSR。
    • CSR中包括公钥和关于个人或组织身份的信息(如组织名称、常用名称(CN)、组织单位、城市、国家等)。
  • 提交CSR到证书颁发机构(CA):
    • 将CSR提交给CA。
    • CA验证请求者提供的信息并决定是否发放证书。
  • 获取证书:
    • CA签发证书后,它通常会提供一个X.509格式的证书文件(也就是SSL/TLS证书)。
  • 安装证书:
    • 将证书安装在服务器或者需要用到的设备上。
    • 一些服务可能还需要你安装中间证书,以建立证书链至根CA。
  • 配置使用SSL/TLS的服务:
    • 配置服务器使用安装的证书和私钥。
    • 调整服务器设置以启用SSL/TLS加密。
  • 证书的相关维护等

那假设有了这个证书,实际过程中应该怎么用呢? 从服务器的角度:

  1. SSL Server生成一个公钥/私钥对(server.key/server.pub), 私钥加密, 公钥解密。
  2. server.pub 生成一个请求文件 server.req. 请求文件中包含有 server 的一些信息,如域名/申请者/公钥等
  3. server 将请求文件 server.req 递交给 CA,CA验明正身后,将用 ca.key和请求文件加密生成 server.crt
  4. 由于 ca.key 和 ca.crt 是一对, 于是 ca.crt 可以解密 server.crt.


在实际应用中:如果 SSL Client 想要校验 SSL server.那么 SSL server 必须要将他的证书 server.crt 传给 client.然后 client 用 ca.crt 去校验 server.crt 的合法性。如果是一个钓鱼网站,那么CA是不会给他颁发合法server.crt证书的,这样client 用ca.crt去校验,就会失败。比如浏览器作为一个 client,你想访问合法的淘宝网站https://www.taobao.com, 结果不慎访问到 https://wwww.jiataobao.com ,那么浏览器将会检验到这个假淘宝钓鱼网站的非法性,提醒用户不要继续访问!这样就可以保证了client的所有https访问都是安全的

好了,上面的这些内容主要是先对SSL/TSL以及证书等有个大致印象, 下面看看在实际业务中是怎么使用的。

3. 业务落地实践

场景: 我有一个目录需要打成压缩包发给客户, 但是打包之前,为了防止网络传输过程中被篡改, 需要对里面的每个文件进行签名, 这样客户收到文件后再验签就知道是否被改动了。

签名是调用一个签名服务的接口完成,但这个签名服务是采用TSL认证的,如果想给它发请求去签名,需要申请client的证书,携带着证书去发送post请求(把文件内容发给服务器,服务器返回一个签名好的字符串)

这个过程用流程图表示如下:
在这里插入图片描述
流程就是:

  1. 客户端这边, 先生成一个CSR,可以理解成一个公钥一个私钥, 可以用openssl来实现

    # 生成私钥
    openssl ecparam -out private.key -name prime256v1 -genkey# 生成P8格式的pem私钥
    openssl pkcs8 -topk8 -inform PEM -in  private.key -outform pem -nocrypt -out  private.key.pem# 生成CSR文件
    openssl  req -new -out pki-tls-ADD.csr -key  private.key.pem -subj /CN=pki-tls-ADD
    
  2. 拿着这个pki-client-ADD.csr文件, 去申请证书

  3. 证书完成后, 会发回来一个server_chain.pem的证书链, 一个pki-tls-ADD.crt.pem的证书文件(PKI根据CSR颁发的客户端pem证书文件)

  4. 发送请求的时候,要在上下文里面带着3个文件去认证:

    cert_file_path = "/home/wuzhongqiang/csr_file/pki-tls-ADD.crt.pem"
    key_file_path = "/home/wuzhongqiang/csr_file/private.key.pem"
    ca_file_path = "/home/wuzhongqiang/csr_file/server_chain.pem"
    

下面是一个同步请求的代码:

import base64
import hashlib
import json
import os
import random
import ssl
import time
import urllib
import aiohttp
import asyncio
from urllib.parse import quoteimport requests
from requests.adapters import HTTPAdapter
from urllib3 import PoolManagerclass SSLAdapter(HTTPAdapter):def __init__(self, certificate, private_key, ca_file, *args, **kwargs):self._certificate = certificateself._private_key = private_keyself._ca_file = ca_filesuper().__init__(*args, **kwargs)def init_poolmanager(self, *args, **kwargs):context = ssl.create_default_context()context.load_cert_chain(certfile=self._certificate, keyfile=self._private_key)context.load_verify_locations(cafile=self._ca_file)self.poolmanager = PoolManager(*args, ssl_context=context, **kwargs)class SignClient(object):IP = "https://wuzhongqiang.net"SIGN_URL = "/api/v1/sign_file"APP_KEY = os.environ.get("APP_KEY", "xxx")APP_SECRET = os.environ.get("APP_SECRET", "xxx")@staticmethoddef gen_app_key_sign(method: str, url: str, header: dict, security_key: str, body: str):http_str = (f"{method}{url}"f"es-auth-appkey:{header['es-auth-appkey']}"f"es-auth-nonce:{header['es-auth-nonce']}"f"es-auth-timestamp:{header['es-auth-timestamp']}"f"json={body}"f"{security_key}")try:urlCodeStr = urllib.parse.quote(http_str, safe='')hashHex = hashlib.sha256(urlCodeStr.encode('utf-8')).hexdigest()except Exception as e:print(e)hashHex = ""return hashHex@staticmethoddef sign_file(data: str, cert_file_path: str, key_file_path: str, ca_file_path: str) -> str:target_data = {"data": data}es_auth_noce = ''.join(random.choices('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=16))es_auth_timestamp = str(int(time.time() * 1000))auth_info = {"es-auth-appkey": SignClient.APP_KEY,"es-auth-nonce": es_auth_noce,"es-auth-timestamp": es_auth_timestamp}payload = json.dumps(target_data, separators=(',', ':'))signature_value = SignClient.gen_app_key_sign(method="POST",url=SignClient.SIGN_URL,header=auth_info,security_key=SignClient.APP_SECRET,body=payload)# print(f"target_data: {target_data}\n"#       f"es-auth-appkey:{SignClient.APP_KEY}\n"#       f"es-auth-nonce:{es_auth_noce}\n"#       f"es-auth-timestamp:{es_auth_timestamp}\n"#       f"es-auth-sign: {signature_value}")headers = {"Content-Type": "application/json","es-auth-appkey": SignClient.APP_KEY,"es-auth-nonce": es_auth_noce,"es-auth-timestamp": es_auth_timestamp,"es-auth-sign": signature_value}session = requests.Session()session.mount('https://', SSLAdapter(certificate=cert_file_path,private_key=key_file_path,ca_file=ca_file_path))response = session.post(url=f"{SignClient.IP}{SignClient.SIGN_URL}", data=payload, headers=headers)# requests method# response = requests.request("POST",#                             url=f"{SignClient.IP}{SignClient.SIGN_URL}",#                             headers=headers,#                             data=payload,#                             cert=(cert_file_path, key_file_path),#                             verify=ca_file_path)try:response_body = response.textjson_object = json.loads(response_body)sign_data = json_object["signData"]return sign_dataexcept Exception as e:print(e)return ""if __name__ == '__main__':base_dir = "/home/wuzhongqiang/Downloads/test_dir"cert_file_path = "/home/wuzhongqiang/csr_file/pki-tls-ADD.crt.pem"key_file_path = "/home/wuzhongqiang/csr_file/private.key.pem"ca_file_path = "/home/wuzhongqiang/csr_file/server_chain.pem"start = time.time()for root, _, files in os.walk(base_dir):for file in files:file_path = os.path.join(root, file)with open(file_path, 'rb') as f:base64_data = base64.b64encode(f.read())base64_data_str = base64_data.decode()sign_data = SignClient.sign_file(data=base64_data_str,cert_file_path=cert_file_path,key_file_path=key_file_path,ca_file_path=ca_file_path)dir_name = os.path.dirname(file_path)file_name = os.path.basename(file_path)sign_file_name = f"{dir_name}/{file_name}.sign"print(f"target file: {file_path}, sign file: {sign_file_name}")with open(sign_file_name, 'wb') as f:f.write(sign_data.encode())print(f"use time: {time.time() - start} s")   # 46s

requests库是同步阻塞的,必须等到结果才会发第二个请求。 这个代码如果部署成服务的方式,是会发生接口访问请求超时,所以下面通过asyncio+aiohttp实现并发http请求, 来减少请求响应时间, 下面是一个异步并发http请求的代码实现。

class SignClient(object):IP = "https://wuzhongqiang.net"SIGN_URL = "/api/v1/sign_file"APP_KEY = os.environ.get("APP_KEY", "xxx")APP_SECRET = os.environ.get("APP_SECRET", "xxx")@staticmethoddef gen_app_key_sign(method: str, url: str, header: dict, security_key: str, body: str):http_str = (f"{method}{url}"f"es-auth-appkey:{header['es-auth-appkey']}"f"es-auth-nonce:{header['es-auth-nonce']}"f"es-auth-timestamp:{header['es-auth-timestamp']}"f"json={body}"f"{security_key}")try:urlCodeStr = urllib.parse.quote(http_str, safe='')hashHex = hashlib.sha256(urlCodeStr.encode('utf-8')).hexdigest()except Exception as e:print(e)hashHex = ""return hashHex@staticmethodasync def sign_file(session: aiohttp.ClientSession, data: str, url: str, file_path: str, ssl_context) -> typing.Tuple[str, str]:target_data = {"data": data}es_auth_noce = ''.join(random.choices('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=16))es_auth_timestamp = str(int(time.time() * 1000))auth_info = {"es-auth-appkey": SignClient.APP_KEY,"es-auth-nonce": es_auth_noce,"es-auth-timestamp": es_auth_timestamp}payload = json.dumps(target_data, separators=(',', ':'))signature_value = SignClient.gen_app_key_sign(method="POST",url=SignClient.SIGN_URL,header=auth_info,security_key=SignClient.APP_SECRET,body=payload)headers = {"Content-Type": "application/json","es-auth-appkey": SignClient.APP_KEY,"es-auth-nonce": es_auth_noce,"es-auth-timestamp": es_auth_timestamp,"es-auth-sign": signature_value}dir_name = os.path.dirname(file_path)file_name = os.path.basename(file_path)sign_file_name = f"{dir_name}/{file_name}.sign"async with session.post(url=f"{SignClient.IP}{SignClient.SIGN_URL}", data=payload, headers=headers, ssl_context=ssl_context) as response:try:response_body = await response.text()json_object = json.loads(response_body)sign_data = json_object["signData"]return sign_file_name, sign_dataexcept Exception as e:print(e)return "", ""@staticmethodasync def sign_files(base_dir: str,cert_file_path: str,key_file_path: str,ca_file_path):ssl_context = ssl.create_default_context()# 加载私钥及对应证书ssl_context.load_cert_chain(certfile=cert_file_path, keyfile=key_file_path)# 加载一组用于验证其他对等方证书的 "证书颁发机构" (CA) 证书ssl_context.load_verify_locations(cafile=ca_file_path)async with aiohttp.ClientSession() as session:tasks = []for root, _, files in os.walk(base_dir):for file in files:file_path = os.path.join(root, file)with open(file_path, 'rb') as f:base64_data = base64.b64encode(f.read())base64_data_str = base64_data.decode()# 创建一个签名的任务并添加到任务列表task = SignClient.sign_file(session=session,data=base64_data_str,url=f"{SignClient.IP}{SignClient.SIGN_URL}",file_path=file_path,ssl_context=ssl_context)tasks.append(task)results = await asyncio.gather(*tasks)return resultsif __name__ == '__main__':base_dir = "/home/wuzhongqiang/Downloads/test_dir"cert_file_path = "/home/wuzhongqiang/csr_file/pki-tls-ADD.crt.pem"key_file_path = "/home/wuzhongqiang/csr_file/private.key.pem"ca_file_path = "/home/wuzhongqiang/csr_file/server_chain.pem"start = time.time()# 如果已经开启了event, 也就是在服务里面调用的话,这里直接await 后面的签名函数即可results = asyncio.run(SignClient.sign_files(base_dir, cert_file_path, key_file_path, ca_file_path))for sign_file_name, sign_data in results:if sign_data:print(f"sign file: {sign_file_name}")with open(sign_file_name, 'wb') as f:f.write(sign_data.encode())else:print(f"Signed filed written to: {sign_file_name}")print(f"use time: {time.time() - start} s")   # 0.58s

改成并发http请求之后, 处理速度从之前的46s降到了0.58s, 效果还是非常可观的。

参考:

  • python关于SSL/TLS认证的实现
  • SSL接口文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1323670.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

RC滤波电路

RC滤波电路 综述:本文简单讲述了RC低通滤波电路和RC高通滤波电路。 滤波电路是指过滤输入信号中不需要的信号,保留需要的信号。 一、RC低通滤波电路 1.定义:RC低通滤波电路:保留低频信号,衰减高频信号。 2.截止频率…

GBK文件批量转UTF-8,python脚本

import os import codecsdef convert_encoding(file_path):try:# 尝试以gb18030编码打开文件并读取内容with codecs.open(file_path, r, gb18030) as f:content f.read()except UnicodeDecodeError:# 如果出现解码错误,尝试使用utf-8编码打开文件with codecs.open(…

Beaver Builder Pro v2.8.0.6:最佳的WordPress页面构建器插件

如果你正在寻找一个能帮助你轻松创建具有专业外观的网站的工具,那么Beaver Builder Pro v2.8.0.6就是你的最佳选择。这个高级WordPress插件提供了一个直观的前端可视化页面构建器,让你可以通过拖放元素来快速构建无限的自定义帖子和页面。 Beaver Buil…

重磅发布!2024中国快消企业数字化转型:增长密码与实战解析

目 录 一、导语 二、2024快消行业趋势:深刻的供应链变革 三、快消企业如何构建全渠道数字化营销能力? 四、快消企业数字化转型案优秀案例 案例一:杨国福用CRM构建招商加盟数字化管理 案例二:德庄集团:CRM系统助…

认识什么是Git

目录 1. 认识Git 1.1. 问题引入 1.2. 概念 1.3. 作用 1.4. 如何学 1.5. Git 安装 1.6. Git配置用户信息 2. Git仓库 2.1. Git 仓库(repository) 2.2. 创建 2.3. 需求 3. Git的三个区域 3.1. Git 使用时的三个区域 3.2. 工作区的内容&#…

备战蓝桥杯---DP刷题2

1.树形DP: 即问那几个点在树的直径上,类似ROAD那题,我们先求一下每一个子树根的子树的最大值与次大值用d1,d2表示,直径就是d1d2的最大值,那么我们如何判断是否在最大路径上,其实就是看一下从某一点出发的所…

聊一聊单点登录

互联网工程师 一、单点登录的概念 单点登录(Single Sign-On,简称SSO)是一种身份认证和授权技术,旨在解决用户在访问多个应用系统时需要重复登录的问题。该技术允许用户在一个应用系统中完成登录后,就可以访问其他相互信…

地表径流分布数据/水文站点分布/降雨量分布/辐射分布数据

引言 大气降水落到地面后,一部分蒸发变成水蒸气返回大气,一部分下渗到土壤成为地下水,其余的水沿着斜坡形成漫流,通过冲沟,溪涧,注入河流,汇入海洋。这种水流称为地表径流。 正文 数据简介 来自…

Linux简单介绍

Linux简单介绍 编译器VMware虚拟机Ubuntu——LinuxOS为什么使用LinuxOS? 目录结构Windows目录结构Linux操作系统home是不是家目录? Linux常用命令终端命令行提示符与权限切换命令tab 作用:自动补全上下箭头pwd命令ls命令mkdir命令touch命令rm…

基于SSM的师生交流平台

目录 背景 技术简介 系统简介 界面预览 背景 传统的师生互动平台主要依赖于面对面的线下交流,用户必须亲自到场以获取和交流相关信息。然而,随着信息技术的广泛传播,众多教育机构开始转向线上发展,寻求更多样化的发展途径。线…

QML嵌套页面的实现学习记录

StackView是一个QML组件,用于管理和显示多个页面。它提供了向前和向后导航的功能,可以在堆栈中推入新页面,并在不需要时将页面弹出。 ApplicationWindow {id:rootvisible: truewidth: 340height: 480title: qsTr("Stack")// 抽屉:…

计算机的发展历程

本文 我们来说说计算机的发展历程 世界上第一台计算机 1946年2月世界上第一台计算机埃尼阿克ENIAC (Electronic Numerical Integrator AndComputer 电子数字积分式计算机) 在美国诞生 是由宾夕法尼亚大学物理学家约翰.莫克利(J.Mauchly) 和工程师普雷斯伯.埃克特(J.P.Eckert…

redis事务(redis features)

redis支持事务,也就是可以在一次请求中执行多个命令。redis中的事务主要是通过MULTI和EXEC这两个命令来实现的。 MULTI命令用来开启一个事务,事务开启之后,所有的命令就都会被放入到一个队列中,最后通过一个EXEC命令来执行事务中…

windows@软件显示模糊@屏幕显示器分辨率和精细度

文章目录 refsDPIPPIPPI (Pixels Per Inch)DPI (Dots Per Inch) 屏幕尺寸数windows中DPI设置对单个应用设置DPI兼容性设置使用系统全局设置 获取屏幕(监视器)信息👺获取监视器的型号pnp 监视器windows 获取屏幕分辨率 高分辨率屏幕高分辨率和高精细度屏幕&#x1f4…

数字化赋能农业创新:数字乡村促进农村产业融合

随着信息技术的迅猛发展和广泛应用,数字化已经成为推动农业创新发展的重要引擎。数字乡村建设通过引入现代信息技术,为农业产业带来了前所未有的发展机遇,促进了农村产业的深度融合与升级。本文将从数字化赋能农业创新的角度,探讨…

ubuntu安装sublime3并设置中文

安装Sublime Text 3 在Ubuntu上安装Sublime Text 3可以通过以下步骤进行: 打开终端。 导入Sublime Text 3的GPG密钥: wget -qO- https://download.sublimetext.com/sublimehq-pub.gpg | sudo apt-key add - 添加Sublime Text 3的存储库: …

2024最新AI创作系统ChatGPT源码+Ai绘画网站源码,GPTs应用、AI换脸、插件系统、GPT文档分析、GPT语音对话一站式解决方案

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统,支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美,那么如何搭建部署AI创作ChatGPT?小编这里写一个详细图文教程吧。已支持GPT…

vivado适用于 UltraScale 和 UltraScale+ 器件的 eFUSE 寄存器访问和编程

FUSE_DNA : 唯一的器件 DNA 每个 UltraScale 器件都有唯一的器件 ID , 称为器件 DNA , 且赛灵思已将此 DNA 编程到器件中。用户无法对 FUSE_DNA 进行编程。 UltraScale 器件具有 96 位 DNA 。您可在 Vivado Design Suite Tcl 控制台中…

Qt项目通过.pri文件将众多文件按功能模块分类显示,开发大型项目必备

Chapter1 Qt项目通过.pri文件将众多文件按功能模块分类显示,开发大型项目必备 Chapter2 在Qt项目中添加pri文件 原文链接:在Qt项目中添加pri文件_qtpri-CSDN博客 前言 一般我们创建Qt项目工程的时候,都是直接把所有的项目,头文…

代码随想录阅读笔记-二叉树【合并二叉树】

题目 给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。 你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节…