mdbq 4.0.124__py3-none-any.whl → 4.0.125__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mdbq might be problematic. Click here for more details.

mdbq/__version__.py CHANGED
@@ -1 +1 @@
1
- VERSION = '4.0.124'
1
+ VERSION = '4.0.125'
mdbq/auth/crypto.py CHANGED
@@ -1,26 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """
3
- 通用加密解密管理器模块
4
-
5
- 功能特性:
6
- - RSA-OAEP + AES-GCM 混合加密
7
- - dpflask风格设备指纹验证
8
- - 时间窗口验证
9
- - PEM格式密钥处理
10
- - Redis防重放攻击(可选)
11
-
12
- 使用场景:
13
- - 敏感数据传输加密
14
- - 用户认证系统
15
- - API安全通信
16
- - 设备身份验证
17
-
18
- 依赖说明:
19
- - cryptography: RSA和AES加密算法
20
- - hashlib: 哈希计算(Python标准库)
21
- - json: JSON处理(Python标准库)
22
- - base64: Base64编码(Python标准库)
23
- - redis: 防重放攻击(可选)
3
+ 加密解密管理模块
24
4
  """
25
5
 
26
6
  import os
@@ -30,11 +10,10 @@ import time
30
10
  import hashlib
31
11
  import hmac
32
12
  import threading
13
+ from abc import ABC, abstractmethod
33
14
  from dataclasses import dataclass, field
34
15
  from enum import Enum
35
- from typing import Dict, Any, Optional, Union, List, Protocol, runtime_checkable
36
-
37
- # 加密相关导入
16
+ from typing import Dict, Any, Optional, Union, List, Protocol, runtime_checkable, Tuple
38
17
  from cryptography.hazmat.primitives import serialization, hashes
39
18
  from cryptography.hazmat.primitives.asymmetric import padding
40
19
  from cryptography.hazmat.primitives.ciphers.aead import AESGCM
@@ -42,697 +21,418 @@ from cryptography.hazmat.backends import default_backend
42
21
  from mdbq.myconf import myconf # type: ignore
43
22
  from mdbq.log import mylogger
44
23
 
45
- dir_path = os.path.expanduser("~")
46
- config_file = os.path.join(dir_path, 'spd.txt')
47
- parser = myconf.ConfigParser()
48
- logger = mylogger.MyLogger(
49
- logging_mode='file',
50
- log_level='info',
51
- log_format='json',
52
- max_log_size=50,
53
- backup_count=5,
54
- enable_async=False, # 是否启用异步日志
55
- sample_rate=1, # 采样DEBUG/INFO日志
56
- sensitive_fields=[], # 敏感字段过滤
57
- enable_metrics=False, # 是否启用性能指标
58
- )
59
-
60
-
61
- # ==================== 枚举和常量定义 ====================
62
-
63
- class ValidationResult(Enum):
64
- """验证结果枚举"""
65
- SUCCESS = "success"
66
- DECRYPT_FAILED = "decrypt_failed"
67
- TIMESTAMP_INVALID = "timestamp_invalid"
68
- FINGERPRINT_MISMATCH = "fingerprint_mismatch"
69
- NONCE_REUSED = "nonce_reused"
70
- INVALID_PAYLOAD = "invalid_payload"
71
- KEY_LOAD_FAILED = "key_load_failed"
72
- REDIS_ERROR = "redis_error"
73
24
 
74
- # ==================== 配置类 ====================
25
+ # ==================== 配置和常量 ====================
75
26
 
76
27
  @dataclass
77
28
  class CryptoConfig:
78
- """加密配置类"""
29
+ """
30
+ 加密配置类
31
+ """
79
32
  # 密钥配置
80
- key_dir_path: str = field(default_factory=lambda: os.path.expanduser("~"))
81
- public_key_filename: str = 'public_key' # 公钥文件名
82
- private_key_filename: str = 'private_key' # 私钥文件名
83
- keys_subdir: str = 'dpsk_keys' # 密钥文件夹名
84
-
85
- # 验证配置
86
- time_window_seconds: int = 300 # 时间窗口,300秒
87
- enable_nonce_check: bool = True # 是否启用nonce防重放攻击
88
- nonce_expire_seconds: int = 600 # 600秒后过期
89
- nonce_redis_prefix: str = "api_identity_check" # 存储的键名
33
+ key_dir_path: str = os.path.expanduser("~")
34
+ keys_subdir: str = 'dpsk_keys'
35
+ public_key_filename: str = 'public_key'
36
+ private_key_filename: str = 'private_key'
90
37
 
91
38
  # 缓存配置
92
- enable_key_cache: bool = True # 是否启用密钥缓存
93
- key_cache_ttl_seconds: int = 3600 # 密钥缓存过期时间
39
+ enable_key_cache: bool = True
40
+ key_cache_ttl_seconds: int = 3600
94
41
 
95
- def __post_init__(self):
96
- """配置验证"""
97
- if self.time_window_seconds <= 0:
98
- raise ValueError("time_window_seconds must be positive")
99
- if self.nonce_expire_seconds <= 0:
100
- raise ValueError("nonce_expire_seconds must be positive")
42
+ # 验证配置
43
+ time_window_seconds: int = 300
44
+ enable_nonce_check: bool = False
45
+ nonce_expire_seconds: int = 600
46
+ nonce_redis_prefix: str = "crypto_nonce"
101
47
 
102
48
 
103
49
  # ==================== 结果类 ====================
104
50
 
105
51
  @dataclass
106
- class AuthResult:
107
- """认证结果类"""
52
+ class AuthenticationResult:
53
+ """认证结果"""
108
54
  success: bool
109
- result_code: ValidationResult
110
55
  payload: Optional[Dict[str, Any]] = None
111
- error_details: Optional[str] = None
112
- execution_time_ms: Optional[float] = None
113
- debug_info: Optional[Dict[str, Any]] = None
56
+ error_message: Optional[str] = None
57
+ execution_time_ms: float = 0.0
114
58
 
115
59
  @classmethod
116
- def success_result(cls, payload: Dict[str, Any], execution_time_ms: float = None) -> 'AuthResult':
117
- """创建成功结果"""
118
- return cls(
119
- success=True,
120
- result_code=ValidationResult.SUCCESS,
121
- payload=payload,
122
- execution_time_ms=execution_time_ms
123
- )
60
+ def success_result(cls, payload: Dict[str, Any], execution_time: float = 0.0):
61
+ return cls(success=True, payload=payload, execution_time_ms=execution_time)
124
62
 
125
63
  @classmethod
126
- def failure_result(cls, result_code: ValidationResult, error_details: str = None,
127
- execution_time_ms: float = None, debug_info: Dict[str, Any] = None) -> 'AuthResult':
128
- """创建失败结果"""
129
- return cls(
130
- success=False,
131
- result_code=result_code,
132
- error_details=error_details,
133
- execution_time_ms=execution_time_ms,
134
- debug_info=debug_info
135
- )
64
+ def failure_result(cls, error_message: str, execution_time: float = 0.0):
65
+ return cls(success=False, error_message=error_message, execution_time_ms=execution_time)
136
66
 
137
67
 
138
- # ==================== 插件接口 ====================
68
+ # ==================== 密钥管理 ====================
139
69
 
140
- @runtime_checkable
141
- class FingerprintValidator(Protocol):
142
- """设备指纹验证器接口"""
70
+ class KeyManager:
71
+ """密钥管理器 - 单例模式"""
143
72
 
144
- def validate(self, payload: Dict[str, Any], logger_func: callable = None) -> bool:
145
- """验证设备指纹"""
146
- ...
147
-
148
-
149
- class DpflaskFingerprintValidator:
150
- """dpflask风格设备指纹验证器"""
73
+ _instance = None
74
+ _lock = threading.Lock()
75
+ _cache = {}
76
+ _cache_time = {}
151
77
 
152
- def validate(self, payload: Dict[str, Any], logger_func: callable = None) -> bool:
153
- """验证设备指纹"""
154
- try:
155
- client_data = payload.get('deviceData', {})
156
- client_hash = payload.get('deviceHash', '')
157
-
158
- if not client_hash:
159
- if logger_func:
160
- logger_func("设备指纹哈希为空")
161
- return False
162
-
163
- def deep_sort(obj):
164
- if isinstance(obj, dict):
165
- return {k: deep_sort(v) for k, v in sorted(obj.items(), key=lambda x: x)}
166
- if isinstance(obj, list):
167
- return sorted((deep_sort(x) for x in obj),
168
- key=lambda x: (isinstance(x, (int, float)), x))
169
- return obj
170
-
171
- # 严格类型转换保证与前端一致
172
- standardized_data = deep_sort({
173
- "userAgent": str(client_data.get("userAgent", "")),
174
- "platform": str(client_data.get("platform", "")),
175
- "languages": sorted([str(x) for x in client_data.get("languages", [])]),
176
- "hardwareConcurrency": int(client_data.get("hardwareConcurrency", 0)),
177
- "screenProps": {
178
- "width": int(client_data.get("screenProps", {}).get("width", 0)),
179
- "height": int(client_data.get("screenProps", {}).get("height", 0)),
180
- "colorDepth": int(client_data.get("screenProps", {}).get("colorDepth", 0))
181
- }
182
- })
183
-
184
- # JSON序列化
185
- class CompactJSONEncoder(json.JSONEncoder):
186
- def encode(self, obj):
187
- return json.dumps(obj, separators=(',', ':'), sort_keys=False)
188
-
189
- data_str = CompactJSONEncoder().encode(standardized_data)
190
-
191
- # SHA512哈希
192
- sha512 = hashlib.sha512()
193
- sha512.update(data_str.encode('utf-8'))
194
- server_hash_str = sha512.hexdigest()
195
-
196
- # 常量时间比较
197
- if not hmac.compare_digest(server_hash_str.encode(), client_hash.encode()):
198
- if logger_func:
199
- logger_func(f"设备指纹不匹配: 期望长度={len(server_hash_str)}, 实际长度={len(client_hash)}")
200
- return False
201
-
202
- # 硬件验证
203
- hw_concurrency = standardized_data["hardwareConcurrency"]
204
- if not (1 <= hw_concurrency <= 128 and isinstance(hw_concurrency, int)):
205
- if logger_func:
206
- logger_func(f"硬件并发数异常: {hw_concurrency}")
207
- return False
208
-
209
- screen = standardized_data["screenProps"]
210
- if not all(isinstance(v, int) and v > 0 for v in [screen["width"], screen["height"], screen["colorDepth"]]):
211
- if logger_func:
212
- logger_func(f"屏幕属性异常: {screen}")
213
- return False
214
-
215
- return True
216
-
217
- except Exception as e:
218
- if logger_func:
219
- logger_func(f"设备指纹验证异常: {str(e)}")
220
- return False
221
-
222
-
223
- class CryptoManager:
224
- """
225
- 通用加密解密管理器
226
- """
78
+ def __new__(cls, *args, **kwargs):
79
+ if not cls._instance:
80
+ with cls._lock:
81
+ if not cls._instance:
82
+ cls._instance = super().__new__(cls)
83
+ return cls._instance
227
84
 
228
- def __init__(self,
229
- config: CryptoConfig,
230
- redis_client=None,
231
- fingerprint_validator: Optional[FingerprintValidator] = None):
232
- """
233
- 初始化加密管理器
234
-
235
- Args:
236
- config: 加密配置对象(必需)
237
- logger: 日志记录器,None时不使用日志
238
- redis_client: Redis客户端,用于nonce防重放攻击
239
- fingerprint_validator: 设备指纹验证器,None时使用默认dpflask验证器
240
- """
85
+ def __init__(self, config: CryptoConfig, logger: Any):
86
+ if hasattr(self, '_initialized'):
87
+ return
241
88
  self.config = config
242
- self.redis_client = redis_client
243
- self.enable_nonce_check = self.config.enable_nonce_check and redis_client is not None
244
- self.fingerprint_validator = fingerprint_validator or DpflaskFingerprintValidator()
245
-
246
- self._init_redis()
247
- # 密钥缓存初始化
248
- self._init_cache()
249
-
250
- # 验证环境
251
- self._validate_environment()
252
-
253
- logger.debug(f"CryptoManager初始化完成: keys_dir={self.keys_directory}, nonce_enabled={self.enable_nonce_check}")
89
+ self.logger = logger
90
+ self._initialized = True
254
91
 
255
- def _init_redis(self):
256
- """初始化Redis"""
257
- if not self.redis_client:
258
- redis_password = parser.get_value(file_path=config_file, section='redis', key='password', value_type=str) # redis 使用本地数据,全部机子相同
259
- # Redis连接配置, 创建连接池
260
- import redis
261
- redis_pool = redis.ConnectionPool(
262
- host='127.0.0.1',
263
- port=6379,
264
- db=0,
265
- password=redis_password,
266
- max_connections=3, # 连接池最大连接数
267
- socket_timeout=5, # 操作超时时间(秒)
268
- socket_connect_timeout=3, # 连接超时时间(秒)
269
- health_check_interval=30, # 健康检查间隔(秒)
270
- decode_responses=False, # 保持二进制数据格式
271
- )
272
-
273
- # 通过连接池获取Redis实例,这个实例用于密钥服务
274
- self.redis_client = redis.Redis(connection_pool=redis_pool)
275
-
276
- def _init_cache(self) -> None:
277
- """初始化密钥缓存"""
278
- self._key_cache_lock = threading.RLock()
279
- self._public_key_cache = None
280
- self._private_key_cache = None
281
- self._cache_timestamp = 0
92
+ def get_public_key(self) -> Optional[str]:
93
+ """获取公钥PEM字符串"""
94
+ cache_key = 'public_key'
282
95
 
283
- # 构建密钥目录路径
284
- self.keys_directory = os.path.join(self.config.key_dir_path, self.config.keys_subdir)
285
-
286
- def _validate_environment(self) -> None:
287
- """验证运行环境"""
288
- # 验证密钥目录
289
- if not os.path.exists(self.keys_directory):
290
- error_msg = f"密钥目录不存在: {self.keys_directory}"
291
- logger.error(error_msg)
292
- raise FileNotFoundError(error_msg)
293
-
294
- # ==================== 缓存管理方法 ====================
295
-
296
- def _is_cache_valid(self) -> bool:
297
- """检查缓存是否有效"""
298
- if not self.config.enable_key_cache:
299
- return False
300
- current_time = time.time()
301
- return (self._cache_timestamp > 0 and
302
- current_time - self._cache_timestamp < self.config.key_cache_ttl_seconds)
303
-
304
- def clear_cache(self) -> None:
305
- """清除密钥缓存"""
306
- with self._key_cache_lock:
307
- self._public_key_cache = None
308
- self._private_key_cache = None
309
- self._cache_timestamp = 0
310
- logger.debug("密钥缓存已清除")
311
-
312
- # ==================== 密钥加载方法 ====================
313
-
314
- def _load_public_key_from_file(self) -> Optional[str]:
315
- """从文件加载公钥"""
96
+ # 检查缓存
97
+ if self.config.enable_key_cache and cache_key in self._cache:
98
+ cache_time = self._cache_time.get(cache_key, 0)
99
+ if time.time() - cache_time < self.config.key_cache_ttl_seconds:
100
+ return self._cache[cache_key]
101
+
102
+ # 读取文件
316
103
  try:
317
- key_path = os.path.join(self.keys_directory, f'{self.config.public_key_filename}.pem')
318
-
319
- if not os.path.exists(key_path):
320
- raise FileNotFoundError(f"公钥文件不存在: {key_path}")
104
+ key_path = os.path.join(
105
+ self.config.key_dir_path,
106
+ self.config.keys_subdir,
107
+ self.config.public_key_filename
108
+ )
321
109
 
322
- with open(key_path, "rb") as key_file:
323
- public_key = serialization.load_pem_public_key(
324
- key_file.read(),
325
- backend=default_backend()
326
- )
110
+ with open(key_path, 'r', encoding='utf-8') as f:
111
+ public_key_pem = f.read().strip()
327
112
 
328
- # 转换为 PEM 格式字节
329
- public_pem = public_key.public_bytes(
330
- encoding=serialization.Encoding.PEM,
331
- format=serialization.PublicFormat.SubjectPublicKeyInfo
332
- )
113
+ # 缓存结果
114
+ if self.config.enable_key_cache:
115
+ self._cache[cache_key] = public_key_pem
116
+ self._cache_time[cache_key] = time.time()
333
117
 
334
- return public_pem.decode("utf-8")
118
+ return public_key_pem
335
119
 
336
120
  except Exception as e:
337
- logger.error(f"公钥加载失败: {str(e)}")
121
+ self.logger.error(f"读取公钥失败: {str(e)}")
338
122
  return None
339
123
 
340
- def _load_private_key_from_file(self):
341
- """从文件加载私钥"""
124
+ def get_private_key(self) -> Optional[Any]:
125
+ """获取私钥对象"""
126
+ cache_key = 'private_key'
127
+
128
+ # 检查缓存
129
+ if self.config.enable_key_cache and cache_key in self._cache:
130
+ cache_time = self._cache_time.get(cache_key, 0)
131
+ if time.time() - cache_time < self.config.key_cache_ttl_seconds:
132
+ return self._cache[cache_key]
133
+
134
+ # 读取文件
342
135
  try:
343
- key_path = os.path.join(self.keys_directory, f'{self.config.private_key_filename}.pem')
344
-
345
- if not os.path.exists(key_path):
346
- raise FileNotFoundError(f"私钥文件不存在: {key_path}")
136
+ key_path = os.path.join(
137
+ self.config.key_dir_path,
138
+ self.config.keys_subdir,
139
+ self.config.private_key_filename
140
+ )
347
141
 
348
- with open(key_path, "rb") as key_file:
142
+ with open(key_path, 'rb') as f:
349
143
  private_key = serialization.load_pem_private_key(
350
- key_file.read(),
144
+ f.read(),
351
145
  password=None,
352
146
  backend=default_backend()
353
147
  )
354
148
 
149
+ # 缓存结果
150
+ if self.config.enable_key_cache:
151
+ self._cache[cache_key] = private_key
152
+ self._cache_time[cache_key] = time.time()
153
+
355
154
  return private_key
356
155
 
357
156
  except Exception as e:
358
- logger.error(f"私钥加载失败: {str(e)}")
157
+ self.logger.error(f"读取私钥失败: {str(e)}")
359
158
  return None
360
159
 
361
- # ==================== 公共API方法 ====================
362
-
363
- def get_public_key(self) -> Optional[str]:
364
- """
365
- 获取PEM格式的公钥字符串(支持缓存)
366
- """
367
- with self._key_cache_lock:
368
- # 检查缓存
369
- if self._is_cache_valid() and self._public_key_cache is not None:
370
- logger.debug("使用缓存的公钥")
371
- return self._public_key_cache
372
-
373
- # 从文件加载
374
- public_key = self._load_public_key_from_file()
375
-
376
- # 更新缓存
377
- if public_key and self.config.enable_key_cache:
378
- self._public_key_cache = public_key
379
- self._cache_timestamp = time.time()
380
- logger.debug("公钥已缓存")
381
-
382
- return public_key
383
-
384
- def get_keys_info(self) -> Dict[str, Any]:
385
- """
386
- 获取密钥文件信息
387
- """
388
- public_key_path = os.path.join(self.keys_directory, f'{self.config.public_key_filename}.pem')
389
- private_key_path = os.path.join(self.keys_directory, f'{self.config.private_key_filename}.pem')
390
-
391
- return {
392
- 'keys_directory': self.keys_directory,
393
- 'public_key_path': public_key_path,
394
- 'private_key_path': private_key_path,
395
- 'public_key_exists': os.path.exists(public_key_path),
396
- 'private_key_exists': os.path.exists(private_key_path),
397
- 'public_key_size': os.path.getsize(public_key_path) if os.path.exists(public_key_path) else 0,
398
- 'private_key_size': os.path.getsize(private_key_path) if os.path.exists(private_key_path) else 0,
399
- 'cache_enabled': self.config.enable_key_cache,
400
- 'cache_ttl_seconds': self.config.key_cache_ttl_seconds,
401
- 'nonce_enabled': self.enable_nonce_check
402
- }
160
+ def clear_cache(self):
161
+ """清除缓存"""
162
+ self._cache.clear()
163
+ self._cache_time.clear()
164
+
165
+
166
+ # ==================== 加密服务 ====================
167
+
168
+ class CryptoService:
169
+ """加密服务"""
403
170
 
404
- # ==================== 核心加解密方法 ====================
171
+ def __init__(self, key_manager: KeyManager, logger: Any):
172
+ self.key_manager = key_manager
173
+ self.logger = logger
405
174
 
406
- def decrypt_payload(self, encrypted_token: str) -> Optional[Dict[str, Any]]:
407
- """
408
- 解密载荷数据
409
- """
175
+ def decrypt_token(self, encrypted_token: str) -> Optional[Dict[str, Any]]:
176
+ """解密令牌"""
410
177
  try:
411
- if not encrypted_token:
412
- logger.warning("加密令牌为空")
413
- return None
414
-
415
- # 获取私钥(使用缓存)
416
- with self._key_cache_lock:
417
- if self._is_cache_valid() and self._private_key_cache is not None:
418
- logger.debug("使用缓存的私钥")
419
- private_key = self._private_key_cache
420
- else:
421
- private_key = self._load_private_key_from_file()
422
- if not private_key:
423
- return None
424
-
425
- # 更新缓存
426
- if self.config.enable_key_cache:
427
- self._private_key_cache = private_key
428
- if self._cache_timestamp == 0: # 首次缓存时间戳
429
- self._cache_timestamp = time.time()
430
- logger.debug("私钥已缓存")
178
+ # 解析加密数据
179
+ encrypted_data = json.loads(base64.b64decode(encrypted_token))
431
180
 
432
- # 解析加密令牌
433
- try:
434
- token_data = json.loads(base64.b64decode(encrypted_token).decode())
435
- except (json.JSONDecodeError, ValueError, base64.binascii.Error) as e:
436
- logger.warning(f"令牌格式错误: {type(e).__name__}")
181
+ # 获取私钥
182
+ private_key = self.key_manager.get_private_key()
183
+ if not private_key:
437
184
  return None
438
185
 
439
- # 验证令牌格式
440
- required_fields = ['key', 'iv', 'ciphertext', 'tag']
441
- missing_fields = [field for field in required_fields if field not in token_data]
442
- if missing_fields:
443
- logger.warning(f"令牌缺少必需字段: {missing_fields}")
444
- return None
445
-
446
- # 解码各个组件
447
- try:
448
- encrypted_key = base64.b64decode(token_data['key'])
449
- iv = base64.b64decode(token_data['iv'])
450
- ciphertext = base64.b64decode(token_data['ciphertext'])
451
- tag = base64.b64decode(token_data['tag'])
452
- except (ValueError, base64.binascii.Error) as e:
453
- logger.warning(f"令牌组件Base64解码失败: {type(e).__name__}")
454
- return None
455
-
456
- # 使用RSA-OAEP解密AES密钥
457
- try:
458
- raw_key = private_key.decrypt(
459
- encrypted_key,
460
- padding.OAEP(
461
- mgf=padding.MGF1(algorithm=hashes.SHA512()),
462
- algorithm=hashes.SHA512(),
463
- label=None
464
- )
186
+ # 解密AES密钥
187
+ encrypted_aes_key = base64.b64decode(encrypted_data['key'])
188
+ aes_key = private_key.decrypt(
189
+ encrypted_aes_key,
190
+ padding.OAEP(
191
+ mgf=padding.MGF1(algorithm=hashes.SHA256()),
192
+ algorithm=hashes.SHA256(),
193
+ label=None
465
194
  )
466
- except Exception as e:
467
- logger.warning(f"RSA解密失败: {type(e).__name__}")
468
- return None
195
+ )
469
196
 
470
- # 使用AES-GCM解密载荷数据
471
- try:
472
- aesgcm = AESGCM(raw_key)
473
- decrypted = aesgcm.decrypt(iv, ciphertext + tag, None)
474
- except Exception as e:
475
- logger.warning(f"AES-GCM解密失败: {type(e).__name__}")
476
- return None
197
+ # 解密数据
198
+ iv = base64.b64decode(encrypted_data['iv'])
199
+ ciphertext = base64.b64decode(encrypted_data['ciphertext'])
477
200
 
478
- # 解析JSON载荷
479
- try:
480
- payload = json.loads(decrypted.decode())
481
- logger.debug("载荷解密成功")
482
- return payload
483
- except (json.JSONDecodeError, UnicodeDecodeError) as e:
484
- logger.warning(f"载荷JSON解析失败: {type(e).__name__}")
485
- return None
201
+ aesgcm = AESGCM(aes_key)
202
+ decrypted_data = aesgcm.decrypt(iv, ciphertext, None)
203
+
204
+ # 解析JSON
205
+ payload = json.loads(decrypted_data.decode('utf-8'))
206
+ return payload
486
207
 
487
208
  except Exception as e:
488
- logger.error(f"解密载荷异常: {str(e)}")
209
+ self.logger.error(f"解密失败: {str(e)}")
489
210
  return None
211
+
212
+
213
+ # ==================== 验证服务 ====================
214
+
215
+ class Validator:
216
+ """验证器"""
490
217
 
491
- def validate_payload(self, payload: Dict[str, Any], token: Optional[str] = None) -> bool:
492
- """
493
- 验证载荷数据的完整性和有效性
494
- """
218
+ def __init__(self, config: CryptoConfig, redis_client: Any, logger: Any):
219
+ self.config = config
220
+ self.redis_client = redis_client
221
+ self.logger = logger
222
+
223
+ def validate_timestamp(self, payload: Dict[str, Any]) -> bool:
224
+ """验证时间戳"""
495
225
  try:
496
- if not isinstance(payload, dict):
497
- return False
498
-
499
- # 1. 时间窗口验证
500
226
  current_time = int(time.time())
501
- payload_timestamp = payload.get('timestamp', 0)
227
+ payload_timestamp = payload.get('timestamp')
502
228
 
503
229
  if not isinstance(payload_timestamp, (int, float)):
504
230
  return False
505
231
 
506
232
  time_diff = abs(current_time - int(payload_timestamp))
507
- if time_diff > self.config.time_window_seconds:
233
+ return time_diff <= self.config.time_window_seconds
234
+
235
+ except Exception:
236
+ return False
237
+
238
+ def validate_nonce(self, payload: Dict[str, Any]) -> bool:
239
+ """验证nonce(防重放)"""
240
+ if not self.config.enable_nonce_check or not self.redis_client:
241
+ return True
242
+
243
+ try:
244
+ nonce = payload.get('nonce')
245
+ if not nonce:
508
246
  return False
509
247
 
510
- # 2. 设备指纹验证
511
- def log_fingerprint_error(msg):
512
- logger.warning(f"设备指纹验证: {msg}")
248
+ nonce_key = f"{self.config.nonce_redis_prefix}:{nonce}"
513
249
 
514
- if not self.fingerprint_validator.validate(payload, log_fingerprint_error):
250
+ # 检查nonce是否已存在
251
+ if self.redis_client.exists(nonce_key):
515
252
  return False
516
253
 
517
- # 3. nonce验证
518
- if self.enable_nonce_check and token:
519
- nonce = payload.get('nonce')
520
- if not nonce:
521
- return False
522
-
523
- if self._is_nonce_used(nonce, token):
524
- return False
525
-
526
- logger.debug("载荷验证通过")
254
+ # 设置nonce(防止重复使用)
255
+ self.redis_client.setex(
256
+ nonce_key,
257
+ self.config.nonce_expire_seconds,
258
+ "used"
259
+ )
527
260
  return True
528
261
 
529
262
  except Exception as e:
530
- logger.error(f"验证载荷异常: {str(e)}")
263
+ self.logger.error(f"Nonce验证失败: {str(e)}")
531
264
  return False
532
265
 
533
- def _is_nonce_used(self, nonce: str, token: str) -> bool:
534
- """
535
- 防重放攻击检查
536
- """
537
- if not self.enable_nonce_check or not self.redis_client:
538
- return False
539
-
540
- try:
541
- redis_key = f"{self.config.nonce_redis_prefix}:{nonce}"
542
- # 原子操作:设置键值并设置过期时间
543
- result = self.redis_client.set(
544
- name=redis_key,
545
- value=token,
546
- ex=self.config.nonce_expire_seconds,
547
- nx=True # 仅当键不存在时设置
548
- )
549
- is_used = not bool(result) # False表示设置成功(未使用过)
550
- if is_used:
551
- logger.warning(f"nonce重复使用: {nonce[:8]}...")
552
- return is_used
553
- except Exception as e:
554
- logger.error(f"nonce检查异常: {str(e)}")
555
- return True # 异常时认为已使用,拒绝请求
266
+ def validate(self, payload: Dict[str, Any]) -> bool:
267
+ """执行所有验证"""
268
+ return (self.validate_timestamp(payload) and
269
+ self.validate_nonce(payload))
270
+
271
+
272
+ # ==================== 主要管理器 ====================
273
+
274
+ class OptimizedCryptoManager:
275
+ """
276
+ 加密管理器
556
277
 
557
- # ==================== 高级认证方法 ====================
278
+ 特点:
279
+ 1. 复用外部Redis连接,避免重复创建连接池
280
+ 2. 单例密钥管理,避免重复读取文件
281
+ """
558
282
 
559
- def get_private_key(self, token: str) -> bool:
560
- """
561
- 加载私钥并解密令牌进行校检(dpflask兼容方法)
562
- 这是dpflask风格的一体化验证方法:解密+验证一步完成
563
- """
564
- result = self.authenticate_token_detailed(token)
565
- return result.success
283
+ def __init__(self, config: CryptoConfig, redis_client: Any = None, logger: Any = None):
284
+ self.config = config
285
+ self.redis_client = redis_client
286
+
287
+ # 初始化日志器
288
+ if logger is None:
289
+ self.logger = mylogger.MyLogger(
290
+ logging_mode='file',
291
+ log_level='info',
292
+ log_format='json',
293
+ max_log_size=50,
294
+ backup_count=5,
295
+ enable_async=False,
296
+ sample_rate=1,
297
+ sensitive_fields=[],
298
+ enable_metrics=False,
299
+ )
300
+ else:
301
+ self.logger = logger
302
+
303
+ # 初始化组件
304
+ self.key_manager = KeyManager(config, self.logger)
305
+ self.crypto_service = CryptoService(self.key_manager, self.logger)
306
+ self.validator = Validator(config, redis_client, self.logger)
307
+
308
+ self.logger.debug("OptimizedCryptoManager初始化完成")
566
309
 
567
- def authenticate_token(self, token: str) -> Dict[str, Any]:
568
- """
569
- 认证令牌并返回详细结果(向后兼容)
570
- """
571
- result = self.authenticate_token_detailed(token)
572
- return {
573
- 'success': result.success,
574
- 'error': result.result_code.value if not result.success else None,
575
- 'message': result.error_details or ('认证成功' if result.success else '认证失败'),
576
- 'payload': result.payload
577
- }
310
+ # ==================== 主要API ====================
578
311
 
579
- def authenticate_token_detailed(self, token: str) -> AuthResult:
312
+ def authenticate_token(self, token: str) -> AuthenticationResult:
580
313
  """
581
- 认证令牌并返回标准化结果对象
314
+ 认证令牌 - 主要的认证API
315
+
316
+ Args:
317
+ token: 加密的令牌字符串
318
+
319
+ Returns:
320
+ AuthenticationResult: 标准化的认证结果
582
321
  """
583
322
  start_time = time.time()
584
323
 
585
324
  try:
586
- if not token:
587
- return AuthResult.failure_result(
588
- ValidationResult.INVALID_PAYLOAD,
589
- "令牌为空",
590
- (time.time() - start_time) * 1000
591
- )
592
-
593
- # 1. 解密载荷
594
- payload = self.decrypt_payload(token)
325
+ # 1. 解密令牌
326
+ payload = self.crypto_service.decrypt_token(token)
595
327
  if not payload:
596
- return AuthResult.failure_result(
597
- ValidationResult.DECRYPT_FAILED,
328
+ return AuthenticationResult.failure_result(
598
329
  "令牌解密失败",
599
330
  (time.time() - start_time) * 1000
600
331
  )
601
332
 
602
333
  # 2. 验证载荷
603
- if not self.validate_payload(payload, token):
604
- # 根据验证失败的具体原因返回不同的结果码
605
- current_time = int(time.time())
606
- payload_timestamp = payload.get('timestamp', 0)
607
-
608
- if abs(current_time - int(payload_timestamp)) > self.config.time_window_seconds:
609
- return AuthResult.failure_result(
610
- ValidationResult.TIMESTAMP_INVALID,
611
- "时间窗口验证失败",
612
- (time.time() - start_time) * 1000
613
- )
614
-
615
- # 检查nonce
616
- if self.enable_nonce_check and token:
617
- nonce = payload.get('nonce')
618
- if nonce and self._is_nonce_used(nonce, token):
619
- return AuthResult.failure_result(
620
- ValidationResult.NONCE_REUSED,
621
- "nonce重复使用",
622
- (time.time() - start_time) * 1000
623
- )
624
-
625
- # 其他情况(设备指纹验证失败等)
626
- return AuthResult.failure_result(
627
- ValidationResult.FINGERPRINT_MISMATCH,
628
- "设备指纹验证失败",
334
+ if not self.validator.validate(payload):
335
+ return AuthenticationResult.failure_result(
336
+ "载荷验证失败",
629
337
  (time.time() - start_time) * 1000
630
338
  )
631
339
 
632
- # 3. 验证成功
340
+ # 3. 认证成功
633
341
  execution_time = (time.time() - start_time) * 1000
634
- logger.debug(f"令牌认证成功,耗时: {execution_time:.2f}ms")
342
+ self.logger.debug(f"认证成功,耗时: {execution_time:.2f}ms")
635
343
 
636
- return AuthResult.success_result(payload, execution_time)
344
+ return AuthenticationResult.success_result(payload, execution_time)
637
345
 
638
346
  except Exception as e:
639
347
  execution_time = (time.time() - start_time) * 1000
640
- logger.error(f"令牌认证异常: {str(e)}")
348
+ error_msg = f"认证异常: {str(e)}"
349
+ self.logger.error(error_msg)
641
350
 
642
- return AuthResult.failure_result(
643
- ValidationResult.DECRYPT_FAILED,
644
- f"认证异常: {str(e)}",
645
- execution_time,
646
- {"exception_type": type(e).__name__}
647
- )
351
+ return AuthenticationResult.failure_result(error_msg, execution_time)
648
352
 
649
- def get_keys_info(self) -> Dict[str, Any]:
353
+ def decrypt_payload(self, encrypted_token: str) -> Optional[Dict[str, Any]]:
650
354
  """
651
- 获取密钥文件信息
355
+ 仅解密载荷数据(不进行验证)
356
+
357
+ Args:
358
+ encrypted_token: 加密的令牌字符串
359
+
360
+ Returns:
361
+ 解密后的载荷数据或None
652
362
  """
653
- public_key_path = os.path.join(self.keys_directory, f'{self.config.public_key_filename}.pem')
654
- private_key_path = os.path.join(self.keys_directory, f'{self.config.private_key_filename}.pem')
363
+ return self.crypto_service.decrypt_token(encrypted_token)
364
+
365
+ def get_public_key(self) -> Optional[str]:
366
+ """获取PEM格式的公钥字符串"""
367
+ return self.key_manager.get_public_key()
368
+
369
+ # ==================== 向后兼容API(简化版)====================
370
+
371
+ def get_private_key(self, token: str) -> bool:
372
+ """验证token成功性"""
373
+ result = self.authenticate_token(token)
374
+ return result.success
375
+
376
+ def decrypt_token(self, token: str, time_window: int = None, return_data: bool = True) -> Union[bool, Dict[str, Any]]:
377
+ """向后兼容方法"""
378
+ result = self.authenticate_token(token)
379
+ if return_data:
380
+ return result.payload if result.success else None
381
+ return result.success
382
+
383
+ # ==================== 系统管理API ====================
384
+
385
+ def get_system_status(self) -> Dict[str, Any]:
386
+ """获取系统状态信息"""
387
+ redis_healthy = False
388
+ if self.redis_client:
389
+ try:
390
+ self.redis_client.ping()
391
+ redis_healthy = True
392
+ except Exception:
393
+ redis_healthy = False
655
394
 
656
395
  return {
657
- 'keys_directory': self.keys_directory,
658
- 'public_key_path': public_key_path,
659
- 'private_key_path': private_key_path,
660
- 'public_key_exists': os.path.exists(public_key_path),
661
- 'private_key_exists': os.path.exists(private_key_path),
662
- 'public_key_size': os.path.getsize(public_key_path) if os.path.exists(public_key_path) else 0,
663
- 'private_key_size': os.path.getsize(private_key_path) if os.path.exists(private_key_path) else 0,
396
+ 'redis_healthy': redis_healthy,
397
+ 'keys_available': bool(self.key_manager.get_public_key()),
664
398
  'cache_enabled': self.config.enable_key_cache,
665
- 'cache_ttl_seconds': self.config.key_cache_ttl_seconds,
666
- 'nonce_enabled': self.enable_nonce_check
399
+ 'nonce_check_enabled': self.config.enable_nonce_check,
400
+ 'time_window_seconds': self.config.time_window_seconds,
401
+ 'version': '2.1.0-optimized'
667
402
  }
403
+
404
+ def clear_cache(self) -> None:
405
+ """清除所有缓存"""
406
+ self.key_manager.clear_cache()
407
+ self.logger.debug("所有缓存已清除")
668
408
 
669
409
 
670
- class CryptoHelper:
671
- """
672
- 加密工具辅助类
410
+ # ==================== 便捷的创建函数 ====================
411
+
412
+ def create_crypto_manager(config: Optional[CryptoConfig] = None,
413
+ redis_client: Any = None) -> OptimizedCryptoManager:
673
414
  """
415
+ 创建函数
674
416
 
675
- @staticmethod
676
- def generate_secure_token(length: int = 32) -> str:
677
- """
678
- 生成安全的随机令牌
417
+ Args:
418
+ config: 可选的配置对象,如果不提供则使用默认配置
419
+ redis_client: 可选的Redis客户端,如果提供则复用连接
679
420
 
680
- Args:
681
- length (int): 令牌长度,默认32字节
682
-
683
- Returns:
684
- str: 十六进制格式的随机令牌
685
- """
686
- import secrets
687
- return secrets.token_hex(length)
688
-
689
- @staticmethod
690
- def hash_data(data: Union[str, bytes], algorithm: str = 'sha256') -> str:
691
- """
692
- 对数据进行哈希处理
693
-
694
- Args:
695
- data (str|bytes): 要哈希的数据
696
- algorithm (str): 哈希算法,支持 'md5', 'sha1', 'sha256', 'sha512'
697
-
698
- Returns:
699
- str: 十六进制格式的哈希值
700
- """
701
- if isinstance(data, str):
702
- data = data.encode('utf-8')
703
-
704
- hash_func = getattr(hashlib, algorithm.lower())()
705
- hash_func.update(data)
706
- return hash_func.hexdigest()
707
-
708
- @staticmethod
709
- def encode_base64(data: Union[str, bytes]) -> str:
710
- """
711
- Base64编码
712
-
713
- Args:
714
- data (str|bytes): 要编码的数据
715
-
716
- Returns:
717
- str: Base64编码后的字符串
718
- """
719
- if isinstance(data, str):
720
- data = data.encode('utf-8')
721
- return base64.b64encode(data).decode('utf-8')
421
+ Returns:
422
+ 配置好的OptimizedCryptoManager实例
423
+ """
424
+ if config is None:
425
+ config = CryptoConfig()
722
426
 
723
- @staticmethod
724
- def decode_base64(encoded_data: str) -> bytes:
725
- """
726
- Base64解码
727
-
728
- Args:
729
- encoded_data (str): Base64编码的字符串
730
-
731
- Returns:
732
- bytes: 解码后的字节数据
733
- """
734
- return base64.b64decode(encoded_data)
427
+ return OptimizedCryptoManager(config, redis_client)
428
+
735
429
 
430
+ # ==================== 示例用法 ====================
736
431
 
737
432
  if __name__ == "__main__":
738
- pass
433
+ # 示例:使用默认配置创建加密管理器
434
+ crypto_manager = create_crypto_manager()
435
+
436
+ # 示例:获取系统状态
437
+ status = crypto_manager.get_system_status()
438
+ print(f"系统状态: {status}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mdbq
3
- Version: 4.0.124
3
+ Version: 4.0.125
4
4
  Home-page: https://pypi.org/project/mdbq
5
5
  Author: xigua,
6
6
  Author-email: 2587125111@qq.com
@@ -1,8 +1,8 @@
1
1
  mdbq/__init__.py,sha256=Il5Q9ATdX8yXqVxtP_nYqUhExzxPC_qk_WXQ_4h0exg,16
2
- mdbq/__version__.py,sha256=1vD8GtQu_YBGFlxv0PdpRzvgw9IAwNfA43CwcOg03ZQ,19
2
+ mdbq/__version__.py,sha256=8InfWPXvxAxerWannxoYZduGNW4ULDSvL7ME9Q09f44,19
3
3
  mdbq/auth/__init__.py,sha256=pnPMAt63sh1B6kEvmutUuro46zVf2v2YDAG7q-jV_To,24
4
4
  mdbq/auth/auth_backend.py,sha256=FGyl3EYcVAqHuCd5oojFC9A4Tl88F0YVAfMIKEfforQ,97822
5
- mdbq/auth/crypto.py,sha256=VHQ1Y82nx_8EdJml-GUtK3VhdVexAk28MFRLFrBsaKw,27754
5
+ mdbq/auth/crypto.py,sha256=7KdWWSHuqsPTvycxUbB6y4nfKeaVXAqvqKmvUyQ404I,14279
6
6
  mdbq/auth/rate_limiter.py,sha256=1m_Paxp8pDNpmyoFGRpFMVOJpbmeIvfVcfiQ2oH72qM,32850
7
7
  mdbq/js/__init__.py,sha256=hpMi3_ZKwIWkzc0LnKL-SY9AS-7PYFHq0izYTgEvxjc,30
8
8
  mdbq/js/jc.py,sha256=6Rgf1WqaJJ1oevpn-pt08gXKbX5hjoQaV6uZGCAGbYw,13177
@@ -35,7 +35,7 @@ mdbq/route/routes.py,sha256=QVGfTvDgu0CpcKCvk1ra74H8uojgqTLUav1fnVAqLEA,29433
35
35
  mdbq/selenium/__init__.py,sha256=AKzeEceqZyvqn2dEDoJSzDQnbuENkJSHAlbHAD0u0ZI,10
36
36
  mdbq/selenium/get_driver.py,sha256=1NTlVUE6QsyjTrVVVqTO2LOnYf578ccFWlWnvIXGtic,20903
37
37
  mdbq/spider/__init__.py,sha256=RBMFXGy_jd1HXZhngB2T2XTvJqki8P_Fr-pBcwijnew,18
38
- mdbq-4.0.124.dist-info/METADATA,sha256=k2obIBHWnNyfItv2aIStPdW7juDGlSv6N-O8ZcpzmXY,365
39
- mdbq-4.0.124.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
40
- mdbq-4.0.124.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
41
- mdbq-4.0.124.dist-info/RECORD,,
38
+ mdbq-4.0.125.dist-info/METADATA,sha256=p2oLQCT1qiJmkcU13sQ5h_E_KO3tja4Ij-FsXQ9jno4,365
39
+ mdbq-4.0.125.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
40
+ mdbq-4.0.125.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
41
+ mdbq-4.0.125.dist-info/RECORD,,
File without changes