From a4c76c576de7f8b9f5af812819db28a4dea07802 Mon Sep 17 00:00:00 2001 From: liubiren Date: Tue, 19 May 2026 22:01:52 +0800 Subject: [PATCH] 20260519 --- utils/mysql.py | 1 - utils/request.py | 55 ++++++++-------- 安全加密请求/main.py | 110 +++++++++++++++++++++++++++++++- 安全加密请求/rsa_public_key.pem | 3 + 4 files changed, 138 insertions(+), 31 deletions(-) create mode 100644 安全加密请求/rsa_public_key.pem diff --git a/utils/mysql.py b/utils/mysql.py index 81f0bb2..f200777 100644 --- a/utils/mysql.py +++ b/utils/mysql.py @@ -56,7 +56,6 @@ class MySQL: text(sql), connection, coerce_float=False ) # 不尝试将非整数数值转为浮点(维持DECIMAL) return dataframe - except: connection.rollback() raise RuntimeError("执行SQL查询并返回DATAFRAME发生其它异常") diff --git a/utils/request.py b/utils/request.py index aea5ab8..a77463c 100644 --- a/utils/request.py +++ b/utils/request.py @@ -5,9 +5,8 @@ import json from pathlib import Path -import sys import time -from typing import Any, Callable, Dict, Generator, Literal, Optional, Tuple, Union +from typing import Any, Dict, Generator, Literal, Optional, Tuple, Union from xml.etree import ElementTree from pydantic import BaseModel, Field, HttpUrl, model_validator @@ -15,6 +14,9 @@ from requests import Response, Session from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry +import sys + +sys.path.append(Path(__file__).parent.as_posix()) from restrict import restrict from sqlite import SQLite @@ -24,7 +26,9 @@ class Parameters(BaseModel): 请求参数模型 """ - url: HttpUrl = Field(default=..., description="统一资源定位符,基于HttpUrl自动校验") + url: HttpUrl = Field( + default=..., description="统一资源定位符,基于 HttpUrl 自动校验" + ) params: Optional[Dict[str, Any]] = Field(default=None, description="查询参数") headers: Optional[Dict[str, Any]] = Field(default=None, description="请求头") data: Optional[Union[str, bytes, Dict[str, Any]]] = Field( @@ -46,25 +50,28 @@ class Parameters(BaseModel): default=None, description="上传文件", ) - encryption_strategy: Optional[Literal["rsa-aes-gcm"]] = Field( - default=None, description="加密策略,默认为不加密" - ) - stream: Optional[bool] = Field( - default=None, description="开启流式传输,默认为不开启" - ) - guid: Optional[str] = Field(default=None, description="缓存全局唯一标识") + guid: Optional[str] = Field(default=None, description="缓存唯一标识") + stream: bool = Field(default=False, description="开启流式传输,默认为不开启") @model_validator(mode="after") - def validate_data(self): - """校验:data 数据和JSON数据互斥""" + def validate_data_json(self): + """校验:data 数据和 JSON 数据不能同时传入""" if self.data and self.json_: - raise ValueError("data 数据和 JSON 数据不能同时使用") + raise ValueError("data 数据和 JSON 数据不能同时传入") return self @model_validator(mode="after") - def validate_files(self): + def validate_files_stream(self): + """校验:上传文件和开启流式传输不能同时传入""" if self.files and self.stream: - raise ValueError("上传文件和开启流式传输不能同时使用") + raise ValueError("上传文件和开启流式传输不能同时传入") + return self + + @model_validator(mode="after") + def validate_guid_stream(self): + """校验:缓存唯一标识和开启流式传输不能同时传入""" + if self.guid and self.stream: + raise ValueError("缓存唯一标识和开启流式传输不能同时传入") return self @@ -130,7 +137,7 @@ class Caches(SQLite): def query(self, guid: str) -> Optional[Dict[str, Any]]: """ - 查询并返回单条缓存 + 查询并返回缓存 :param guid: 缓存唯一标识 :return: 缓存 """ @@ -147,12 +154,12 @@ class Caches(SQLite): return None if result is None else json.loads(result["cache"]) except Exception as exception: raise RuntimeError( - f"查询并获取单条缓存发生异常:{str(exception)}" + f"查询并获取缓存发生异常:{str(exception)}" ) from exception def update(self, guid: str, cache: Dict) -> Optional[bool]: """ - 新增或更新单条缓存(若无则新增缓存,若有则更新缓存) + 新增或更新缓存(若无则新增缓存,若有则更新缓存) :param guid: 缓存唯一标识 :param cache: 缓存 :return: 成功返回True,失败返回False @@ -186,7 +193,6 @@ class Request: total: int = 3, backoff_factor: float = 0.5, timeout: int = 60, - encryption_strategy: Optional[Literal["rsa-aes-gcm"]] = None, cache_enabled: bool = False, cache_ttl: int = 360, ): @@ -195,7 +201,6 @@ class Request: :param total: 最大重试次数,默认 3 :param backoff_factor: 重试间隔退避因子,默认 0.5 :param timeout: 超时时间(单位为秒),默认为 60 - :param encryption_strategy: 加密策略,默认为不加密 :param cache_enabled: 使用缓存,默认 False :param cache_ttl: 缓存生存时间(单位为天),默认为 360 """ @@ -206,9 +211,6 @@ class Request: # 初始化超时时间 self.timeout = timeout - # 初始化加密策略 - self.encryption_strategy = encryption_strategy - # 实例化缓存 self.caches = Caches(cache_ttl=cache_ttl * 86400) if cache_enabled else None @@ -327,9 +329,9 @@ class Request: if kwargs.get("json"): kwargs["json"] = {k: v for k, v in kwargs["json"].items() if v} - # 缓存全局唯一标识 + # 缓存唯一标识 guid = kwargs.pop("guid", None) - # 若缓存非空且缓存全局唯一标识非空则查询并获取单条缓存 + # 若缓存非空且缓存唯一标识非空则查询并获取单条缓存 if self.caches and guid: cache = self.caches.query(guid) if cache: @@ -348,12 +350,11 @@ class Request: # 处理响应对象 response = self._process_response(response=response) - # 若使用缓存且缓存全局唯一标识非空则新增或更新缓存 + # 若使用缓存且缓存唯一标识非空则新增或更新缓存 if self.caches and guid: self.caches.update(guid, response) return response - # 重构异常信息 except Exception as exception: response = getattr( exception, "response", None diff --git a/安全加密请求/main.py b/安全加密请求/main.py index cfc81bd..89736e8 100644 --- a/安全加密请求/main.py +++ b/安全加密请求/main.py @@ -4,7 +4,111 @@ """ # 列举导入模块 -import uvicorn +from base64 import b64encode +from json import dumps +from pathlib import Path +from secrets import token_bytes +from time import time +from typing import cast +from typing import Any, Dict +from uuid import uuid4 -def request(): - pass \ No newline at end of file +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives.serialization import load_pem_public_key + +import sys +sys.path.append(Path(__file__).parent.parent.as_posix()) +from utils.request import Request + + +def encrypt(payload: Dict[str, Any]) -> Dict[str, Any]: + """ + 加密载荷 + :param payload: 载荷 + :return: 加密后载荷 + """ + pem_path = Path(__file__).parent / "rsa_public_key.pem" + if not pem_path.exists(): + raise FileNotFoundError("RSA 公钥 PEM 文件不存在") + # RSA 公钥 + rsa_public_key = pem_path.read_text(encoding="utf-8") + + # 实例 RSA 加密器 + rsa_encryptor = cast( + rsa.RSAPublicKey, + load_pem_public_key( + data=rsa_public_key.encode("utf-8"), backend=default_backend() + ), + ) + # 生成 AES-256 密钥 + aes_key = token_bytes(32) + # 使用 RSA 公钥加密 AES-256 密钥 + aes_key_encrypted = rsa_encryptor.encrypt( + plaintext=aes_key, + padding=padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None, + ), + ) + aes_key_encoded = b64encode(aes_key_encrypted).decode() # BASE64 编码 + + # 初始向量 + iv = token_bytes(12) + iv_encoded = b64encode(iv).decode() # BASE64 编码 + + # 实例 AES-GCM 加密器 + aes_gcm_encryptor = Cipher( + algorithm=algorithms.AES(aes_key), mode=modes.GCM(iv), backend=default_backend() + ).encryptor() + + # 时间戳和随机码 + timestamp, nonce = int(time()), uuid4().hex.lower() + # 附加认证 + aes_gcm_encryptor.authenticate_additional_data( + data=f"timestamp={timestamp}&nonce={nonce}".encode("utf-8") + ) + + ciphertext = ( + aes_gcm_encryptor.update( + data=dumps(payload, ensure_ascii=False).encode("utf-8") + ) + + aes_gcm_encryptor.finalize() + ) + ciphertext_encoded = b64encode(ciphertext).decode() # BASE64 编码 + + tag = aes_gcm_encryptor.tag + tag_encoded = b64encode(tag).decode() # BASE64 编码 + + return { + "encryptedAesKey": aes_key_encoded, + "iv": iv_encoded, + "timestamp": timestamp, + "nonce": nonce, + "encryptedPayload": ciphertext_encoded, + "tag": tag_encoded, + } + + +request = Request() # 不使用缓存 +response = request.post( + url="http://192.168.3.103:30380/", + headers={ + "Authorization": "Bearer C52FB4D10BC424D9F", + "Content-Type": "application/json;charset=utf-8", + }, + json=encrypt( + payload={ + "productId": "BANK_CARD_4", + "name": "刘弼仁", + "idNumber": "131002198705020000", + "bankCard": "1234567890123456", + "phone": "18058798752", + } + ), +) +print(response) \ No newline at end of file diff --git a/安全加密请求/rsa_public_key.pem b/安全加密请求/rsa_public_key.pem new file mode 100644 index 0000000..491f5fc --- /dev/null +++ b/安全加密请求/rsa_public_key.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApafHKXu1IwGJ1iQVT0susJ20MRl/6unJ+YMX6Lx9l43/+THA4VAJ8YQaZMH9+1eFeaFjwM9pT0ee+okBpKRuDWVkH8NxiKza4ohNHEtSUU37N3h8BEUBJHx74/6pDK8DwPsp4LzGhnUPof5/ZMqW2phqTrEYRwYOKGLYNnScNKkOWkQMSrUxGBkcMs1VsePChe0cJqrC03CTHJ/NRSyc62HqDZLp6G+DaR3VgWkhvhT4sN/3j/n9Ml5io/8I5ooPfBrYPNCfOaVqFWtqHqoG0uMLeROa8edqk6ZBBv2mERPllOG43f91FNmg/kF8XHij1LBNcf1WbO7rHg0Vo71j+wIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file