infomankit 0.3.23__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.
- infoman/__init__.py +1 -0
- infoman/cli/README.md +378 -0
- infoman/cli/__init__.py +7 -0
- infoman/cli/commands/__init__.py +3 -0
- infoman/cli/commands/init.py +312 -0
- infoman/cli/scaffold.py +634 -0
- infoman/cli/templates/Makefile.template +132 -0
- infoman/cli/templates/app/__init__.py.template +3 -0
- infoman/cli/templates/app/app.py.template +4 -0
- infoman/cli/templates/app/models_base.py.template +18 -0
- infoman/cli/templates/app/models_entity_init.py.template +11 -0
- infoman/cli/templates/app/models_schemas_init.py.template +11 -0
- infoman/cli/templates/app/repository_init.py.template +11 -0
- infoman/cli/templates/app/routers_init.py.template +15 -0
- infoman/cli/templates/app/services_init.py.template +11 -0
- infoman/cli/templates/app/static_index.html.template +39 -0
- infoman/cli/templates/app/static_main.js.template +31 -0
- infoman/cli/templates/app/static_style.css.template +111 -0
- infoman/cli/templates/app/utils_init.py.template +11 -0
- infoman/cli/templates/config/.env.dev.template +43 -0
- infoman/cli/templates/config/.env.prod.template +43 -0
- infoman/cli/templates/config/README.md.template +28 -0
- infoman/cli/templates/docker/.dockerignore.template +60 -0
- infoman/cli/templates/docker/Dockerfile.template +47 -0
- infoman/cli/templates/docker/README.md.template +240 -0
- infoman/cli/templates/docker/docker-compose.yml.template +81 -0
- infoman/cli/templates/docker/mysql_custom.cnf.template +42 -0
- infoman/cli/templates/docker/mysql_init.sql.template +15 -0
- infoman/cli/templates/project/.env.example.template +1 -0
- infoman/cli/templates/project/.gitignore.template +60 -0
- infoman/cli/templates/project/Makefile.template +38 -0
- infoman/cli/templates/project/README.md.template +137 -0
- infoman/cli/templates/project/deploy.sh.template +97 -0
- infoman/cli/templates/project/main.py.template +10 -0
- infoman/cli/templates/project/manage.sh.template +97 -0
- infoman/cli/templates/project/pyproject.toml.template +47 -0
- infoman/cli/templates/project/service.sh.template +203 -0
- infoman/config/__init__.py +25 -0
- infoman/config/base.py +67 -0
- infoman/config/db_cache.py +237 -0
- infoman/config/db_relation.py +181 -0
- infoman/config/db_vector.py +39 -0
- infoman/config/jwt.py +16 -0
- infoman/config/llm.py +16 -0
- infoman/config/log.py +627 -0
- infoman/config/mq.py +26 -0
- infoman/config/settings.py +65 -0
- infoman/llm/__init__.py +0 -0
- infoman/llm/llm.py +297 -0
- infoman/logger/__init__.py +57 -0
- infoman/logger/context.py +191 -0
- infoman/logger/core.py +358 -0
- infoman/logger/filters.py +157 -0
- infoman/logger/formatters.py +138 -0
- infoman/logger/handlers.py +276 -0
- infoman/logger/metrics.py +160 -0
- infoman/performance/README.md +583 -0
- infoman/performance/__init__.py +19 -0
- infoman/performance/cli.py +215 -0
- infoman/performance/config.py +166 -0
- infoman/performance/reporter.py +519 -0
- infoman/performance/runner.py +303 -0
- infoman/performance/standards.py +222 -0
- infoman/service/__init__.py +8 -0
- infoman/service/app.py +67 -0
- infoman/service/core/__init__.py +0 -0
- infoman/service/core/auth.py +105 -0
- infoman/service/core/lifespan.py +132 -0
- infoman/service/core/monitor.py +57 -0
- infoman/service/core/response.py +37 -0
- infoman/service/exception/__init__.py +7 -0
- infoman/service/exception/error.py +274 -0
- infoman/service/exception/exception.py +25 -0
- infoman/service/exception/handler.py +238 -0
- infoman/service/infrastructure/__init__.py +8 -0
- infoman/service/infrastructure/base.py +212 -0
- infoman/service/infrastructure/db_cache/__init__.py +8 -0
- infoman/service/infrastructure/db_cache/manager.py +194 -0
- infoman/service/infrastructure/db_relation/__init__.py +41 -0
- infoman/service/infrastructure/db_relation/manager.py +300 -0
- infoman/service/infrastructure/db_relation/manager_pro.py +408 -0
- infoman/service/infrastructure/db_relation/mysql.py +52 -0
- infoman/service/infrastructure/db_relation/pgsql.py +54 -0
- infoman/service/infrastructure/db_relation/sqllite.py +25 -0
- infoman/service/infrastructure/db_vector/__init__.py +40 -0
- infoman/service/infrastructure/db_vector/manager.py +201 -0
- infoman/service/infrastructure/db_vector/qdrant.py +322 -0
- infoman/service/infrastructure/mq/__init__.py +15 -0
- infoman/service/infrastructure/mq/manager.py +178 -0
- infoman/service/infrastructure/mq/nats/__init__.py +0 -0
- infoman/service/infrastructure/mq/nats/nats_client.py +57 -0
- infoman/service/infrastructure/mq/nats/nats_event_router.py +25 -0
- infoman/service/launch.py +284 -0
- infoman/service/middleware/__init__.py +7 -0
- infoman/service/middleware/base.py +41 -0
- infoman/service/middleware/logging.py +51 -0
- infoman/service/middleware/rate_limit.py +301 -0
- infoman/service/middleware/request_id.py +21 -0
- infoman/service/middleware/white_list.py +24 -0
- infoman/service/models/__init__.py +8 -0
- infoman/service/models/base.py +441 -0
- infoman/service/models/type/embed.py +70 -0
- infoman/service/routers/__init__.py +18 -0
- infoman/service/routers/health_router.py +311 -0
- infoman/service/routers/monitor_router.py +44 -0
- infoman/service/utils/__init__.py +8 -0
- infoman/service/utils/cache/__init__.py +0 -0
- infoman/service/utils/cache/cache.py +192 -0
- infoman/service/utils/module_loader.py +10 -0
- infoman/service/utils/parse.py +10 -0
- infoman/service/utils/resolver/__init__.py +8 -0
- infoman/service/utils/resolver/base.py +47 -0
- infoman/service/utils/resolver/resp.py +102 -0
- infoman/service/vector/__init__.py +20 -0
- infoman/service/vector/base.py +56 -0
- infoman/service/vector/qdrant.py +125 -0
- infoman/service/vector/service.py +67 -0
- infoman/utils/__init__.py +2 -0
- infoman/utils/decorators/__init__.py +8 -0
- infoman/utils/decorators/cache.py +137 -0
- infoman/utils/decorators/retry.py +99 -0
- infoman/utils/decorators/safe_execute.py +99 -0
- infoman/utils/decorators/timing.py +99 -0
- infoman/utils/encryption/__init__.py +8 -0
- infoman/utils/encryption/aes.py +66 -0
- infoman/utils/encryption/ecc.py +108 -0
- infoman/utils/encryption/rsa.py +112 -0
- infoman/utils/file/__init__.py +0 -0
- infoman/utils/file/handler.py +22 -0
- infoman/utils/hash/__init__.py +0 -0
- infoman/utils/hash/hash.py +61 -0
- infoman/utils/http/__init__.py +8 -0
- infoman/utils/http/client.py +62 -0
- infoman/utils/http/info.py +94 -0
- infoman/utils/http/result.py +19 -0
- infoman/utils/notification/__init__.py +8 -0
- infoman/utils/notification/feishu.py +35 -0
- infoman/utils/text/__init__.py +8 -0
- infoman/utils/text/extractor.py +111 -0
- infomankit-0.3.23.dist-info/METADATA +632 -0
- infomankit-0.3.23.dist-info/RECORD +143 -0
- infomankit-0.3.23.dist-info/WHEEL +4 -0
- infomankit-0.3.23.dist-info/entry_points.txt +5 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# !/usr/bin/env python
|
|
2
|
+
# -*-coding:utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
# Time :2025/6/22 12:07
|
|
6
|
+
# Author :Maxwell
|
|
7
|
+
# Description:
|
|
8
|
+
"""
|
|
9
|
+
import time
|
|
10
|
+
import functools
|
|
11
|
+
from typing import Callable, TypeVar, Any, Optional, Union, Dict, List, Tuple
|
|
12
|
+
from inspect import iscoroutinefunction
|
|
13
|
+
from infoman.logger import logger
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
T = TypeVar("T")
|
|
17
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def safe_execute(
|
|
21
|
+
func: F, default_return: Any = None, log_error: bool = True, reraise: bool = False
|
|
22
|
+
) -> F:
|
|
23
|
+
"""
|
|
24
|
+
安全执行装饰器 - 捕获函数执行过程中的异常,防止程序崩溃
|
|
25
|
+
|
|
26
|
+
参数:
|
|
27
|
+
default_return: 发生异常时的默认返回值
|
|
28
|
+
log_error: 是否记录错误日志
|
|
29
|
+
reraise: 是否重新抛出异常
|
|
30
|
+
|
|
31
|
+
示例:
|
|
32
|
+
@safe_execute
|
|
33
|
+
def my_function():
|
|
34
|
+
# 可能抛出异常的代码
|
|
35
|
+
|
|
36
|
+
@safe_execute(default_return=[], log_error=True)
|
|
37
|
+
async def my_async_function():
|
|
38
|
+
# 可能抛出异常的异步代码
|
|
39
|
+
"""
|
|
40
|
+
# 处理直接使用不带参数的装饰器情况
|
|
41
|
+
if callable(func) and not isinstance(func, type):
|
|
42
|
+
|
|
43
|
+
@functools.wraps(func)
|
|
44
|
+
async def async_wrapper(*args, **kwargs):
|
|
45
|
+
try:
|
|
46
|
+
return await func(*args, **kwargs)
|
|
47
|
+
except Exception as e:
|
|
48
|
+
if log_error:
|
|
49
|
+
logger.error(f"Error in {func.__name__}: {e}", exc_info=True)
|
|
50
|
+
if reraise:
|
|
51
|
+
raise
|
|
52
|
+
return default_return
|
|
53
|
+
|
|
54
|
+
@functools.wraps(func)
|
|
55
|
+
def sync_wrapper(*args, **kwargs):
|
|
56
|
+
try:
|
|
57
|
+
return func(*args, **kwargs)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
if log_error:
|
|
60
|
+
logger.error(f"Error in {func.__name__}: {e}", exc_info=True)
|
|
61
|
+
if reraise:
|
|
62
|
+
raise
|
|
63
|
+
return default_return
|
|
64
|
+
|
|
65
|
+
if iscoroutinefunction(func):
|
|
66
|
+
return async_wrapper
|
|
67
|
+
else:
|
|
68
|
+
return sync_wrapper
|
|
69
|
+
|
|
70
|
+
# 处理带参数的装饰器情况
|
|
71
|
+
def decorator(fn: F) -> F:
|
|
72
|
+
@functools.wraps(fn)
|
|
73
|
+
async def async_wrapper(*args, **kwargs):
|
|
74
|
+
try:
|
|
75
|
+
return await fn(*args, **kwargs)
|
|
76
|
+
except Exception as e:
|
|
77
|
+
if log_error:
|
|
78
|
+
logger.error(f"Error in {fn.__name__}: {e}", exc_info=True)
|
|
79
|
+
if reraise:
|
|
80
|
+
raise
|
|
81
|
+
return default_return
|
|
82
|
+
|
|
83
|
+
@functools.wraps(fn)
|
|
84
|
+
def sync_wrapper(*args, **kwargs):
|
|
85
|
+
try:
|
|
86
|
+
return fn(*args, **kwargs)
|
|
87
|
+
except Exception as e:
|
|
88
|
+
if log_error:
|
|
89
|
+
logger.error(f"Error in {fn.__name__}: {e}", exc_info=True)
|
|
90
|
+
if reraise:
|
|
91
|
+
raise
|
|
92
|
+
return default_return
|
|
93
|
+
|
|
94
|
+
if iscoroutinefunction(fn):
|
|
95
|
+
return async_wrapper
|
|
96
|
+
else:
|
|
97
|
+
return sync_wrapper
|
|
98
|
+
|
|
99
|
+
return decorator
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# !/usr/bin/env python
|
|
2
|
+
# -*-coding:utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
# Time :2025/6/22 12:08
|
|
6
|
+
# Author :Maxwell
|
|
7
|
+
# Description:
|
|
8
|
+
"""
|
|
9
|
+
import time
|
|
10
|
+
import functools
|
|
11
|
+
from contextlib import contextmanager
|
|
12
|
+
from typing import Callable, TypeVar, Any, Optional, Union, Dict, List, Tuple
|
|
13
|
+
from infoman.logger import logger
|
|
14
|
+
from inspect import iscoroutinefunction
|
|
15
|
+
|
|
16
|
+
T = TypeVar("T")
|
|
17
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@contextmanager
|
|
21
|
+
def timing_context(mark):
|
|
22
|
+
start_time = time.time()
|
|
23
|
+
try:
|
|
24
|
+
yield
|
|
25
|
+
finally:
|
|
26
|
+
elapsed = time.time() - start_time
|
|
27
|
+
logger.info(f"{mark}: {elapsed:.4f}s")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def timing(
|
|
31
|
+
label: str = "执行时间",
|
|
32
|
+
threshold_ms: Optional[float] = None,
|
|
33
|
+
log_level: str = "info",
|
|
34
|
+
) -> Callable[[F], F]:
|
|
35
|
+
"""
|
|
36
|
+
计时装饰器 - 测量函数执行时间并记录日志
|
|
37
|
+
|
|
38
|
+
参数:
|
|
39
|
+
label: 日志标签
|
|
40
|
+
threshold_ms: 时间阈值(毫秒),超过此值才记录日志
|
|
41
|
+
log_level: 日志级别 (debug, info, warning, error, critical)
|
|
42
|
+
|
|
43
|
+
示例:
|
|
44
|
+
@timing("数据处理")
|
|
45
|
+
def process_data():
|
|
46
|
+
# 处理数据的代码
|
|
47
|
+
|
|
48
|
+
@timing(threshold_ms=100) # 只记录执行时间超过100ms的调用
|
|
49
|
+
async def fetch_data():
|
|
50
|
+
# 获取数据的异步代码
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def get_logger_method(level_name: str) -> Callable:
|
|
54
|
+
"""获取对应日志级别的方法"""
|
|
55
|
+
level_map = {
|
|
56
|
+
"debug": logger.debug,
|
|
57
|
+
"info": logger.info,
|
|
58
|
+
"warning": logger.warning,
|
|
59
|
+
"error": logger.error,
|
|
60
|
+
}
|
|
61
|
+
return level_map.get(level_name.lower(), logger.info)
|
|
62
|
+
|
|
63
|
+
log_method = get_logger_method(log_level)
|
|
64
|
+
|
|
65
|
+
def decorator(func: F) -> F:
|
|
66
|
+
@functools.wraps(func)
|
|
67
|
+
async def async_wrapper(*args, **kwargs):
|
|
68
|
+
start_time = time.perf_counter()
|
|
69
|
+
result = await func(*args, **kwargs)
|
|
70
|
+
elapsed_time = time.perf_counter() - start_time
|
|
71
|
+
elapsed_ms = elapsed_time * 1000
|
|
72
|
+
|
|
73
|
+
if threshold_ms is None or elapsed_ms > threshold_ms:
|
|
74
|
+
log_method(
|
|
75
|
+
f"{label}: {func.__name__}, {elapsed_time:.4f}s ({elapsed_ms:.2f}ms)"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return result
|
|
79
|
+
|
|
80
|
+
@functools.wraps(func)
|
|
81
|
+
def sync_wrapper(*args, **kwargs):
|
|
82
|
+
start_time = time.perf_counter()
|
|
83
|
+
result = func(*args, **kwargs)
|
|
84
|
+
elapsed_time = time.perf_counter() - start_time
|
|
85
|
+
elapsed_ms = elapsed_time * 1000
|
|
86
|
+
|
|
87
|
+
if threshold_ms is None or elapsed_ms > threshold_ms:
|
|
88
|
+
log_method(
|
|
89
|
+
f"{label}: {func.__name__}, {elapsed_time:.4f}s ({elapsed_ms:.2f}ms)"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return result
|
|
93
|
+
|
|
94
|
+
if iscoroutinefunction(func):
|
|
95
|
+
return async_wrapper
|
|
96
|
+
else:
|
|
97
|
+
return sync_wrapper
|
|
98
|
+
|
|
99
|
+
return decorator
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import base64
|
|
3
|
+
|
|
4
|
+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
5
|
+
from cryptography.hazmat.primitives import padding
|
|
6
|
+
from cryptography.exceptions import InvalidKey, UnsupportedAlgorithm
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AESCipher:
|
|
10
|
+
BLOCK_SIZE = algorithms.AES.block_size
|
|
11
|
+
KEY_SIZE = 32
|
|
12
|
+
IV_SIZE = int(BLOCK_SIZE / 8)
|
|
13
|
+
|
|
14
|
+
def __init__(self, key: bytes = None):
|
|
15
|
+
if key:
|
|
16
|
+
if len(key) != self.KEY_SIZE:
|
|
17
|
+
raise ValueError(f"Key must be {self.KEY_SIZE} bytes long.")
|
|
18
|
+
self.key = key
|
|
19
|
+
else:
|
|
20
|
+
self.key = os.urandom(self.KEY_SIZE)
|
|
21
|
+
|
|
22
|
+
def encrypt(self, plaintext: str) -> str:
|
|
23
|
+
try:
|
|
24
|
+
iv = os.urandom(self.IV_SIZE)
|
|
25
|
+
padder = padding.PKCS7(self.BLOCK_SIZE).padder()
|
|
26
|
+
padded_data = padder.update(plaintext.encode("utf-8")) + padder.finalize()
|
|
27
|
+
cipher = Cipher(algorithms.AES(self.key), modes.CBC(iv))
|
|
28
|
+
encryptor = cipher.encryptor()
|
|
29
|
+
ciphertext = encryptor.update(padded_data) + encryptor.finalize()
|
|
30
|
+
return base64.b64encode(iv + ciphertext).decode("utf-8")
|
|
31
|
+
except ValueError as e:
|
|
32
|
+
raise ValueError(f"Encryption failed: {str(e)}")
|
|
33
|
+
except InvalidKey:
|
|
34
|
+
raise ValueError("Encryption failed: Invalid key.")
|
|
35
|
+
except UnsupportedAlgorithm:
|
|
36
|
+
raise ValueError("Encryption failed: Unsupported algorithm.")
|
|
37
|
+
except Exception as e:
|
|
38
|
+
raise ValueError(f"Encryption failed: {str(e)}")
|
|
39
|
+
|
|
40
|
+
def decrypt(self, ciphertext: str) -> str:
|
|
41
|
+
try:
|
|
42
|
+
encrypted_data = base64.b64decode(ciphertext.encode("utf-8"))
|
|
43
|
+
iv = encrypted_data[: self.IV_SIZE]
|
|
44
|
+
ciphertext = encrypted_data[self.IV_SIZE:]
|
|
45
|
+
cipher = Cipher(algorithms.AES(self.key), modes.CBC(iv))
|
|
46
|
+
decryptor = cipher.decryptor()
|
|
47
|
+
padded_data = decryptor.update(ciphertext) + decryptor.finalize()
|
|
48
|
+
unpadder = padding.PKCS7(self.BLOCK_SIZE).unpadder()
|
|
49
|
+
plaintext = unpadder.update(padded_data) + unpadder.finalize()
|
|
50
|
+
return plaintext.decode("utf-8")
|
|
51
|
+
except ValueError as e:
|
|
52
|
+
raise ValueError(f"Decryption failed: {str(e)}")
|
|
53
|
+
except InvalidKey:
|
|
54
|
+
raise ValueError("Decryption failed: Invalid key.")
|
|
55
|
+
except UnsupportedAlgorithm:
|
|
56
|
+
raise ValueError("Decryption failed: Unsupported algorithm.")
|
|
57
|
+
except Exception as e:
|
|
58
|
+
raise ValueError(f"Decryption failed: {str(e)}")
|
|
59
|
+
|
|
60
|
+
def get_key(self) -> str:
|
|
61
|
+
return base64.b64encode(self.key).decode("utf-8")
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def get_key_bytes(cls, base64_bytes: bytes) -> bytes:
|
|
65
|
+
base64_str = base64_bytes.decode("utf-8")
|
|
66
|
+
return base64.b64decode(base64_str)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
|
3
|
+
from cryptography.hazmat.primitives import serialization, hashes
|
|
4
|
+
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
|
5
|
+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
6
|
+
from cryptography.hazmat.primitives import padding
|
|
7
|
+
from cryptography.exceptions import InvalidKey, UnsupportedAlgorithm
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ECCCipher:
|
|
11
|
+
CURVE = ec.SECP521R1()
|
|
12
|
+
HASH_ALGORITHM = hashes.SHA512()
|
|
13
|
+
HASH_LENGTH = 64
|
|
14
|
+
DERIVED_KEY_LENGTH = 64
|
|
15
|
+
AES_KEY_LENGTH = 32
|
|
16
|
+
IV_LENGTH = 16
|
|
17
|
+
INFO = b"ecc-encryption"
|
|
18
|
+
|
|
19
|
+
def __init__(self, private_key=None, public_key=None):
|
|
20
|
+
if private_key and public_key:
|
|
21
|
+
self.private_key = private_key
|
|
22
|
+
self.public_key = public_key
|
|
23
|
+
else:
|
|
24
|
+
self.private_key, self.public_key = self.generate_key_pair()
|
|
25
|
+
|
|
26
|
+
def generate_key_pair(self):
|
|
27
|
+
private_key = ec.generate_private_key(self.CURVE)
|
|
28
|
+
public_key = private_key.public_key()
|
|
29
|
+
return private_key, public_key
|
|
30
|
+
|
|
31
|
+
def encrypt(self, plaintext: bytes, peer_public_key) -> bytes:
|
|
32
|
+
try:
|
|
33
|
+
shared_key = self.private_key.exchange(ec.ECDH(), peer_public_key)
|
|
34
|
+
derived_key = HKDF(
|
|
35
|
+
algorithm=self.HASH_ALGORITHM,
|
|
36
|
+
length=self.DERIVED_KEY_LENGTH,
|
|
37
|
+
salt=None,
|
|
38
|
+
info=self.INFO,
|
|
39
|
+
).derive(shared_key)
|
|
40
|
+
iv = os.urandom(self.IV_LENGTH)
|
|
41
|
+
cipher = Cipher(
|
|
42
|
+
algorithms.AES(derived_key[: self.AES_KEY_LENGTH]), modes.CBC(iv)
|
|
43
|
+
)
|
|
44
|
+
encryptor = cipher.encryptor()
|
|
45
|
+
padder = padding.PKCS7(algorithms.AES.block_size).padder()
|
|
46
|
+
padded_data = padder.update(plaintext) + padder.finalize()
|
|
47
|
+
ciphertext = encryptor.update(padded_data) + encryptor.finalize()
|
|
48
|
+
return iv + ciphertext
|
|
49
|
+
except ValueError as e:
|
|
50
|
+
raise ValueError(f"Encryption failed: {str(e)}")
|
|
51
|
+
except InvalidKey:
|
|
52
|
+
raise ValueError("Encryption failed: Invalid public key.")
|
|
53
|
+
except UnsupportedAlgorithm:
|
|
54
|
+
raise ValueError("Encryption failed: Unsupported algorithm.")
|
|
55
|
+
except Exception as e:
|
|
56
|
+
raise ValueError(f"Encryption failed: {str(e)}")
|
|
57
|
+
|
|
58
|
+
def decrypt(self, ciphertext: bytes, peer_public_key) -> bytes:
|
|
59
|
+
try:
|
|
60
|
+
iv = ciphertext[: self.IV_LENGTH]
|
|
61
|
+
ciphertext = ciphertext[self.IV_LENGTH :]
|
|
62
|
+
shared_key = self.private_key.exchange(ec.ECDH(), peer_public_key)
|
|
63
|
+
derived_key = HKDF(
|
|
64
|
+
algorithm=self.HASH_ALGORITHM,
|
|
65
|
+
length=self.DERIVED_KEY_LENGTH,
|
|
66
|
+
salt=None,
|
|
67
|
+
info=self.INFO,
|
|
68
|
+
).derive(shared_key)
|
|
69
|
+
cipher = Cipher(
|
|
70
|
+
algorithms.AES(derived_key[: self.AES_KEY_LENGTH]), modes.CBC(iv)
|
|
71
|
+
)
|
|
72
|
+
decryptor = cipher.decryptor()
|
|
73
|
+
padded_data = decryptor.update(ciphertext) + decryptor.finalize()
|
|
74
|
+
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
|
|
75
|
+
plaintext = unpadder.update(padded_data) + unpadder.finalize()
|
|
76
|
+
return plaintext
|
|
77
|
+
except ValueError as e:
|
|
78
|
+
raise ValueError(f"Decryption failed: {str(e)}")
|
|
79
|
+
except InvalidKey:
|
|
80
|
+
raise ValueError("Decryption failed: Invalid private key.")
|
|
81
|
+
except UnsupportedAlgorithm:
|
|
82
|
+
raise ValueError("Decryption failed: Unsupported algorithm.")
|
|
83
|
+
except Exception as e:
|
|
84
|
+
raise ValueError(f"Decryption failed: {str(e)}")
|
|
85
|
+
|
|
86
|
+
def serialize_private_key(self) -> bytes:
|
|
87
|
+
return self.private_key.private_bytes(
|
|
88
|
+
encoding=serialization.Encoding.PEM,
|
|
89
|
+
format=serialization.PrivateFormat.PKCS8,
|
|
90
|
+
encryption_algorithm=serialization.NoEncryption(),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def serialize_public_key(self) -> bytes:
|
|
94
|
+
return self.public_key.public_bytes(
|
|
95
|
+
encoding=serialization.Encoding.PEM,
|
|
96
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def deserialize_private_key(private_key_pem: bytes):
|
|
101
|
+
return serialization.load_pem_private_key(
|
|
102
|
+
private_key_pem,
|
|
103
|
+
password=None,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def deserialize_public_key(public_key_pem: bytes):
|
|
108
|
+
return serialization.load_pem_public_key(public_key_pem)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
from cryptography.hazmat.primitives.asymmetric import rsa, padding
|
|
4
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
5
|
+
from cryptography.exceptions import InvalidKey, UnsupportedAlgorithm
|
|
6
|
+
from cryptography.hazmat.backends import default_backend
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RSACipher:
|
|
10
|
+
HASH_ALGORITHM = hashes.SHA512()
|
|
11
|
+
HASH_LENGTH = 64
|
|
12
|
+
PADDING_OVERHEAD = 2 * HASH_LENGTH + 2
|
|
13
|
+
KEY_SIZE = 4096
|
|
14
|
+
MAX_ENCRYPT_LENGTH = (KEY_SIZE // 8) - PADDING_OVERHEAD
|
|
15
|
+
|
|
16
|
+
def __init__(self, private_key=None, public_key=None):
|
|
17
|
+
if private_key:
|
|
18
|
+
self.private_key = private_key
|
|
19
|
+
if public_key:
|
|
20
|
+
self.public_key = public_key
|
|
21
|
+
if not private_key and not public_key:
|
|
22
|
+
self.private_key, self.public_key = self.generate_key_pair()
|
|
23
|
+
|
|
24
|
+
def generate_key_pair(self):
|
|
25
|
+
private_key = rsa.generate_private_key(
|
|
26
|
+
public_exponent=65537,
|
|
27
|
+
key_size=self.KEY_SIZE,
|
|
28
|
+
)
|
|
29
|
+
public_key = private_key.public_key()
|
|
30
|
+
return private_key, public_key
|
|
31
|
+
|
|
32
|
+
def encrypt(self, plaintext: bytes) -> bytes:
|
|
33
|
+
try:
|
|
34
|
+
if len(plaintext) > self.MAX_ENCRYPT_LENGTH:
|
|
35
|
+
raise ValueError(
|
|
36
|
+
f"Plaintext too long. Max length is {self.MAX_ENCRYPT_LENGTH} bytes."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return self.public_key.encrypt(
|
|
40
|
+
plaintext,
|
|
41
|
+
padding.OAEP(
|
|
42
|
+
mgf=padding.MGF1(algorithm=self.HASH_ALGORITHM),
|
|
43
|
+
algorithm=self.HASH_ALGORITHM,
|
|
44
|
+
label=None,
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
except ValueError as e:
|
|
48
|
+
raise ValueError(f"Encryption failed: {str(e)}")
|
|
49
|
+
except InvalidKey:
|
|
50
|
+
raise ValueError("Encryption failed: Invalid public key.")
|
|
51
|
+
except UnsupportedAlgorithm:
|
|
52
|
+
raise ValueError("Encryption failed: Unsupported algorithm.")
|
|
53
|
+
except Exception as e:
|
|
54
|
+
raise ValueError(f"Encryption failed: {str(e)}")
|
|
55
|
+
|
|
56
|
+
def decrypt(self, ciphertext: bytes) -> bytes:
|
|
57
|
+
try:
|
|
58
|
+
return self.private_key.decrypt(
|
|
59
|
+
ciphertext,
|
|
60
|
+
padding.OAEP(
|
|
61
|
+
mgf=padding.MGF1(algorithm=self.HASH_ALGORITHM),
|
|
62
|
+
algorithm=self.HASH_ALGORITHM,
|
|
63
|
+
label=None,
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
except ValueError as e:
|
|
67
|
+
raise ValueError(f"Decryption failed: {str(e)}")
|
|
68
|
+
except InvalidKey:
|
|
69
|
+
raise ValueError("Decryption failed: Invalid private key.")
|
|
70
|
+
except UnsupportedAlgorithm:
|
|
71
|
+
raise ValueError("Decryption failed: Unsupported algorithm.")
|
|
72
|
+
except Exception as e:
|
|
73
|
+
raise ValueError(f"Decryption failed: {str(e)}")
|
|
74
|
+
|
|
75
|
+
def serialize_private_key(self) -> bytes:
|
|
76
|
+
return self.private_key.private_bytes(
|
|
77
|
+
encoding=serialization.Encoding.PEM,
|
|
78
|
+
format=serialization.PrivateFormat.PKCS8,
|
|
79
|
+
encryption_algorithm=serialization.NoEncryption(),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def serialize_public_key(self) -> bytes:
|
|
83
|
+
return self.public_key.public_bytes(
|
|
84
|
+
encoding=serialization.Encoding.PEM,
|
|
85
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def deserialize_private_key(private_key_pem: bytes):
|
|
90
|
+
try:
|
|
91
|
+
return serialization.load_pem_private_key(
|
|
92
|
+
private_key_pem, password=None, backend=default_backend()
|
|
93
|
+
)
|
|
94
|
+
except ValueError as e:
|
|
95
|
+
print(f"Error deserializing private key: {e}")
|
|
96
|
+
raise
|
|
97
|
+
except Exception as e:
|
|
98
|
+
print(f"Unexpected error deserializing private key: {e}")
|
|
99
|
+
raise
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def deserialize_public_key(public_key_pem: bytes):
|
|
103
|
+
try:
|
|
104
|
+
return serialization.load_pem_public_key(
|
|
105
|
+
public_key_pem, backend=default_backend()
|
|
106
|
+
)
|
|
107
|
+
except ValueError as e:
|
|
108
|
+
print(f"Error deserializing public key: {e}")
|
|
109
|
+
raise
|
|
110
|
+
except Exception as e:
|
|
111
|
+
print(f"Unexpected error deserializing public key: {e}")
|
|
112
|
+
raise
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import aiofiles
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AsyncFileHandler:
|
|
5
|
+
def __init__(self, file_path: str):
|
|
6
|
+
self.file_path = file_path
|
|
7
|
+
|
|
8
|
+
async def read_file(self, binary=False):
|
|
9
|
+
mode = "rb" if binary else "r"
|
|
10
|
+
async with aiofiles.open(self.file_path, mode=mode) as file:
|
|
11
|
+
content = await file.read()
|
|
12
|
+
return content
|
|
13
|
+
|
|
14
|
+
async def write_file(self, content, binary=False):
|
|
15
|
+
mode = "wb" if binary else "w"
|
|
16
|
+
async with aiofiles.open(self.file_path, mode=mode) as file:
|
|
17
|
+
await file.write(content)
|
|
18
|
+
|
|
19
|
+
async def append_file(self, content, binary=False):
|
|
20
|
+
mode = "ab" if binary else "a"
|
|
21
|
+
async with aiofiles.open(self.file_path, mode=mode) as file:
|
|
22
|
+
await file.write(content)
|
|
File without changes
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# !/usr/bin/env python
|
|
2
|
+
# -*-coding:utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
# Time :2025/6/21 19:36
|
|
6
|
+
# Author :Maxwell
|
|
7
|
+
# Description:
|
|
8
|
+
"""
|
|
9
|
+
import os
|
|
10
|
+
import hashlib
|
|
11
|
+
import uuid
|
|
12
|
+
import time
|
|
13
|
+
import random
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class HashManager(object):
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def md5(cls, content: str) -> str:
|
|
20
|
+
if not content:
|
|
21
|
+
content = ""
|
|
22
|
+
md5_hash = hashlib.md5()
|
|
23
|
+
md5_hash.update(content.encode("utf-8"))
|
|
24
|
+
return md5_hash.hexdigest()
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def sha256(cls, content: str) -> str:
|
|
28
|
+
if not content:
|
|
29
|
+
content = ""
|
|
30
|
+
sha256_hash = hashlib.sha256()
|
|
31
|
+
sha256_hash.update(content.encode("utf-8"))
|
|
32
|
+
return sha256_hash.hexdigest().upper()
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def is_sha256(cls, string):
|
|
36
|
+
if len(string) != 64:
|
|
37
|
+
return False
|
|
38
|
+
try:
|
|
39
|
+
hashlib.sha256(string.encode()).hexdigest()
|
|
40
|
+
return True
|
|
41
|
+
except ValueError:
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def uuid(cls) -> str:
|
|
46
|
+
random_part = random.getrandbits(128)
|
|
47
|
+
unique_str = f"{uuid.uuid1().hex}_{int(time.time())}_{random_part}"
|
|
48
|
+
return hashlib.sha256(unique_str.encode()).hexdigest().upper()
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def time_hash(cls) -> str:
|
|
52
|
+
pid = os.getpid()
|
|
53
|
+
time_ns = time.time_ns()
|
|
54
|
+
random_id = random.randint(0, 100_0000_0000)
|
|
55
|
+
return f"{pid}-{time_ns}-{random_id}"
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def time_and_random(cls) -> int:
|
|
59
|
+
time_ns = time.time_ns()
|
|
60
|
+
random_id = str(random.randint(0, 99999999)).zfill(8)
|
|
61
|
+
return int(f"{random_id}{time_ns}")
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# !/usr/bin/env python
|
|
2
|
+
# -*-coding:utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
# Time :2024/7/29 14:25
|
|
6
|
+
# Author :Maxwell
|
|
7
|
+
# Description:
|
|
8
|
+
"""
|
|
9
|
+
import aiohttp
|
|
10
|
+
from infoman.utils.http.result import HttpResult
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HttpAsyncClient:
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
async def post(url, headers=None, data=None, json=None, files=None) -> HttpResult | None:
|
|
17
|
+
if data:
|
|
18
|
+
form_data = aiohttp.FormData()
|
|
19
|
+
for key, value in data.items():
|
|
20
|
+
form_data.add_field(key, value)
|
|
21
|
+
|
|
22
|
+
if files:
|
|
23
|
+
for key, file in files.items():
|
|
24
|
+
form_data.add_field(
|
|
25
|
+
key,
|
|
26
|
+
file,
|
|
27
|
+
filename="file",
|
|
28
|
+
content_type="application/octet-stream",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
async with aiohttp.ClientSession() as session:
|
|
32
|
+
async with session.post(
|
|
33
|
+
url, headers=headers, data=form_data
|
|
34
|
+
) as response:
|
|
35
|
+
succeed = response.status == 200
|
|
36
|
+
content = await response.read()
|
|
37
|
+
text = await response.text()
|
|
38
|
+
message = f"{response.status}:{response.reason}"
|
|
39
|
+
return HttpResult(
|
|
40
|
+
succeed=succeed, content=content, text=text, message=message
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if json:
|
|
44
|
+
async with aiohttp.ClientSession() as session:
|
|
45
|
+
async with session.post(url, headers=headers, json=json) as response:
|
|
46
|
+
succeed = response.status == 200
|
|
47
|
+
content = await response.read()
|
|
48
|
+
text = await response.text()
|
|
49
|
+
message = f"{response.status}:{response.reason}"
|
|
50
|
+
return HttpResult(
|
|
51
|
+
succeed=succeed, content=content, text=text, message=message
|
|
52
|
+
)
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
async def get_content(url) -> HttpResult:
|
|
57
|
+
async with aiohttp.ClientSession() as session:
|
|
58
|
+
async with session.get(url) as response:
|
|
59
|
+
succeed = response.status == 200
|
|
60
|
+
content = await response.read()
|
|
61
|
+
message = f"{response.status}:{response.reason}"
|
|
62
|
+
return HttpResult(succeed=succeed, content=content, message=message)
|