volcengine-python-sdk-aicc 0.0.41__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.
- bytedance/__init__.py +4 -0
- bytedance/jeddak_secure_channel/__init__.py +28 -0
- bytedance/jeddak_secure_channel/client.py +305 -0
- bytedance/jeddak_secure_channel/config.py +171 -0
- bytedance/jeddak_secure_channel/consts.py +5 -0
- bytedance/jeddak_secure_channel/crypto/__init__.py +14 -0
- bytedance/jeddak_secure_channel/crypto/aes.py +174 -0
- bytedance/jeddak_secure_channel/crypto/envelope.py +395 -0
- bytedance/jeddak_secure_channel/crypto/rsa.py +164 -0
- bytedance/jeddak_secure_channel/error.py +131 -0
- bytedance/jeddak_secure_channel/log/__init__.py +23 -0
- bytedance/jeddak_secure_channel/log/config.py +31 -0
- bytedance/jeddak_secure_channel/log/logger.py +88 -0
- bytedance/jeddak_secure_channel/logger_factory.py +134 -0
- bytedance/jeddak_secure_channel/ra.py +772 -0
- bytedance/jeddak_secure_channel/server.py +745 -0
- bytedance/jeddak_secure_channel/service/__init__.py +16 -0
- bytedance/jeddak_secure_channel/service/eps.py +133 -0
- bytedance/jeddak_secure_channel/service/eps_models.py +47 -0
- bytedance/jeddak_secure_channel/service/ras.py +88 -0
- bytedance/jeddak_secure_channel/service/tks.py +51 -0
- bytedance/jeddak_secure_channel/service/tks_models.py +334 -0
- bytedance/jeddak_secure_channel/utils.py +469 -0
- bytedance/jeddak_secure_model/model_automation_tool.py +181 -0
- bytedance/jeddak_secure_model/model_encryption.py +416 -0
- bytedance/jeddak_secure_model/model_hub_admin.py +116 -0
- bytedance/jeddak_secure_model/pcc_utils.py +279 -0
- bytedance/jeddak_secure_model/rds_utils.py +63 -0
- bytedance/tks/attester.py +41 -0
- bytedance/tks/client.py +629 -0
- bytedance/tks/consts.py +122 -0
- bytedance/tks/crypto/aes.py +47 -0
- bytedance/tks/crypto/ec.py +39 -0
- bytedance/tks/exceptions.py +22 -0
- bytedance/volcengine_aicc_sdk/__init__.py +14 -0
- bytedance/volcengine_aicc_sdk/aicc_client.py +277 -0
- bytedance/volcengine_aicc_sdk/aicc_inference.py +64 -0
- bytedance/volcengine_aicc_sdk/ark_inference_client.py +343 -0
- bytedance/volcengine_aicc_sdk/asr_inference_client.py +158 -0
- bytedance/volcengine_aicc_sdk/asr_websocket_client.py +705 -0
- bytedance/volcengine_aicc_sdk/auc_inference_client.py +305 -0
- bytedance/volcengine_aicc_sdk/common/__init__.py +0 -0
- bytedance/volcengine_aicc_sdk/common/aes.py +136 -0
- bytedance/volcengine_aicc_sdk/common/constant.py +4 -0
- bytedance/volcengine_aicc_sdk/common/error.py +21 -0
- bytedance/volcengine_aicc_sdk/common/log/__init__.py +24 -0
- bytedance/volcengine_aicc_sdk/common/log/config.py +31 -0
- bytedance/volcengine_aicc_sdk/common/log/logger.py +88 -0
- bytedance/volcengine_aicc_sdk/common/tos_utils.py +341 -0
- bytedance/volcengine_aicc_sdk/common/utils.py +45 -0
- bytedance/volcengine_aicc_sdk/cu_inference_client.py +292 -0
- bytedance/volcengine_aicc_sdk/deepseek_inference_client.py +501 -0
- bytedance/volcengine_aicc_sdk/secure_http_client.py +406 -0
- bytedance/volcengine_aicc_sdk/seedance_inference_client.py +500 -0
- bytedance/volcengine_aicc_sdk/seedream_inference_client.py +213 -0
- volcengine_python_sdk_aicc-0.0.41.dist-info/METADATA +79 -0
- volcengine_python_sdk_aicc-0.0.41.dist-info/RECORD +60 -0
- volcengine_python_sdk_aicc-0.0.41.dist-info/WHEEL +5 -0
- volcengine_python_sdk_aicc-0.0.41.dist-info/licenses/LICENSE +202 -0
- volcengine_python_sdk_aicc-0.0.41.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
"""
|
|
3
|
+
seedance机密推理客户端
|
|
4
|
+
"""
|
|
5
|
+
__all__ = ["AUCClient"]
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import base64
|
|
9
|
+
import requests
|
|
10
|
+
import traceback
|
|
11
|
+
from urllib.parse import unquote
|
|
12
|
+
from cryptography.hazmat.primitives import serialization as crypto_serialization
|
|
13
|
+
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
14
|
+
from cryptography.hazmat.backends import default_backend as crypto_default_backend
|
|
15
|
+
from bytedance.jeddak_secure_channel.crypto import PrivateKey
|
|
16
|
+
from .secure_http_client import SecureHttpClient
|
|
17
|
+
from .common.tos_utils import TOSUtils
|
|
18
|
+
from .common.error import AICCException
|
|
19
|
+
from .common.log import logger
|
|
20
|
+
from .secure_http_client import AICCConfig
|
|
21
|
+
from .common.aes import decrypt_file
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AUCClient:
|
|
25
|
+
"""
|
|
26
|
+
AUC机密推理客户端.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self,
|
|
30
|
+
base_url: str = "",
|
|
31
|
+
api_key: str = "",
|
|
32
|
+
endpoint: str = "",
|
|
33
|
+
aicc_config: AICCConfig = None,
|
|
34
|
+
**kwargs):
|
|
35
|
+
self.base_url = base_url
|
|
36
|
+
self.api_key = api_key
|
|
37
|
+
self.endpoint = endpoint
|
|
38
|
+
self.aicc_config = aicc_config
|
|
39
|
+
self.tos_utils = TOSUtils()
|
|
40
|
+
self.kwargs = kwargs
|
|
41
|
+
self.is_secure = self.kwargs.get("is_secure", True)
|
|
42
|
+
|
|
43
|
+
if aicc_config:
|
|
44
|
+
self.secure_http_client = SecureHttpClient(aicc_config=aicc_config)
|
|
45
|
+
else:
|
|
46
|
+
if self.is_secure:
|
|
47
|
+
aicc_config = AICCConfig(ra_url=base_url, auth_token=api_key)
|
|
48
|
+
self.secure_http_client = SecureHttpClient(aicc_config=aicc_config)
|
|
49
|
+
else:
|
|
50
|
+
self.secure_http_client = SecureHttpClient()
|
|
51
|
+
|
|
52
|
+
# 是否开启AUC文件加密, 默认加密
|
|
53
|
+
self.is_enable_auc_encrypt = str(self.kwargs.get("is_enable_auc_encrypt", True)).lower() == "true"
|
|
54
|
+
|
|
55
|
+
# AUC文件的存储位置, 目前仅支持火山TOS
|
|
56
|
+
self.auc_file_storage_location = self.kwargs.get("auc_file_storage_location", "TOS").upper()
|
|
57
|
+
|
|
58
|
+
self.auc_file_encrypt_pub_key_content = None
|
|
59
|
+
self.auc_file_encrypt_priv_key_content = None
|
|
60
|
+
|
|
61
|
+
self.auc_file_encrypt_priv_key: PrivateKey = None
|
|
62
|
+
|
|
63
|
+
self.timeout = self.kwargs.get("timeout", 120.0)
|
|
64
|
+
|
|
65
|
+
# API Endpoints
|
|
66
|
+
self.CREATE_TASK_URL = "/auc/lark/submit"
|
|
67
|
+
self.QUERY_TASK_URL = "/auc/lark/query"
|
|
68
|
+
|
|
69
|
+
def _build_common_headers(self, request_id: str) -> dict[str, str]:
|
|
70
|
+
if not request_id:
|
|
71
|
+
request_id = str(uuid.uuid4())
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
"Content-Type": "application/json",
|
|
75
|
+
"X-Api-App-Key": self.endpoint,
|
|
76
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
77
|
+
"X-Request-Id": request_id,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
def _get_base_url(self):
|
|
81
|
+
"""
|
|
82
|
+
Get base URL with trailing slash removed
|
|
83
|
+
"""
|
|
84
|
+
return self.base_url.rstrip("/")
|
|
85
|
+
|
|
86
|
+
def generate_key_pair(self, pub_key_path: str = "", priv_key_path: str = ""):
|
|
87
|
+
"""
|
|
88
|
+
Generate RSA 4096 key pair and save to files
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
pub_key_path: Path to save public key
|
|
92
|
+
priv_key_path: Path to save private key
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Tuple of (public_key_path, private_key_path)
|
|
96
|
+
"""
|
|
97
|
+
try:
|
|
98
|
+
# Generate RSA key pair
|
|
99
|
+
key = rsa.generate_private_key(
|
|
100
|
+
backend=crypto_default_backend(),
|
|
101
|
+
public_exponent=65537,
|
|
102
|
+
key_size=4096
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Save private key
|
|
106
|
+
private_key = key.private_bytes(
|
|
107
|
+
crypto_serialization.Encoding.PEM,
|
|
108
|
+
crypto_serialization.PrivateFormat.PKCS8,
|
|
109
|
+
crypto_serialization.NoEncryption()
|
|
110
|
+
)
|
|
111
|
+
with open(priv_key_path, 'wb') as f:
|
|
112
|
+
f.write(private_key)
|
|
113
|
+
|
|
114
|
+
# Save public key
|
|
115
|
+
public_key = key.public_key().public_bytes(
|
|
116
|
+
crypto_serialization.Encoding.PEM,
|
|
117
|
+
crypto_serialization.PublicFormat.SubjectPublicKeyInfo
|
|
118
|
+
)
|
|
119
|
+
with open(pub_key_path, 'wb') as f:
|
|
120
|
+
f.write(public_key)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.error(f"[Error] Failed to generate key pair: {e}")
|
|
123
|
+
raise AICCException("Failed to generate RSA key pair") from e
|
|
124
|
+
|
|
125
|
+
def set_file_encrypt_key(self, pub_key_path: str = "", priv_key_path: str = ""):
|
|
126
|
+
"""
|
|
127
|
+
Set auc file encrypt key
|
|
128
|
+
Args:
|
|
129
|
+
pub_key_path: Public key for encrypt
|
|
130
|
+
priv_key_path: Private key for decrypt
|
|
131
|
+
"""
|
|
132
|
+
if os.path.exists(pub_key_path):
|
|
133
|
+
with open(pub_key_path, 'r') as f:
|
|
134
|
+
content = f.read()
|
|
135
|
+
if '-----BEGIN' in content and '-----END' in content:
|
|
136
|
+
self.auc_file_encrypt_pub_key_content = content
|
|
137
|
+
|
|
138
|
+
if os.path.exists(priv_key_path):
|
|
139
|
+
with open(priv_key_path, 'r') as f:
|
|
140
|
+
content = f.read()
|
|
141
|
+
if '-----BEGIN' in content and '-----END' in content:
|
|
142
|
+
self.auc_file_encrypt_priv_key_content = content
|
|
143
|
+
self.auc_file_encrypt_priv_key = \
|
|
144
|
+
PrivateKey.from_private_pem(self.auc_file_encrypt_priv_key_content)
|
|
145
|
+
|
|
146
|
+
def create_task(self, data: dict, custom_header: dict = None, request_id: str = "") -> str:
|
|
147
|
+
"""
|
|
148
|
+
:param data:
|
|
149
|
+
:param custom_header:
|
|
150
|
+
:return:
|
|
151
|
+
"""
|
|
152
|
+
if not data:
|
|
153
|
+
raise AICCException("data must be provided")
|
|
154
|
+
|
|
155
|
+
if "model" not in data:
|
|
156
|
+
data["model"] = self.endpoint
|
|
157
|
+
|
|
158
|
+
base_url = self._get_base_url()
|
|
159
|
+
url = f"{base_url}{self.CREATE_TASK_URL}"
|
|
160
|
+
|
|
161
|
+
headers = self._build_common_headers(request_id)
|
|
162
|
+
if custom_header:
|
|
163
|
+
headers.update(custom_header)
|
|
164
|
+
|
|
165
|
+
# Add encryption headers if provided
|
|
166
|
+
if self.is_enable_auc_encrypt:
|
|
167
|
+
if not self.auc_file_encrypt_pub_key_content or not self.auc_file_encrypt_priv_key_content:
|
|
168
|
+
raise AICCException("pub_key and priv_key must be provided when Is-Enable-Encrypt is 'true'")
|
|
169
|
+
|
|
170
|
+
headers["Enable-TOS-Content-Result-Encryption"] = str(self.is_enable_auc_encrypt).lower()
|
|
171
|
+
headers["X-Encryption-Algorithm"] = "RSA_OAEP_4096_AES_256"
|
|
172
|
+
if self.auc_file_encrypt_pub_key_content:
|
|
173
|
+
# Base64 encode the PEM public key for safe HTTP header transmission
|
|
174
|
+
# This avoids issues with newlines and special characters in headers
|
|
175
|
+
pk_base64 = base64.b64encode(self.auc_file_encrypt_pub_key_content.encode()).decode()
|
|
176
|
+
headers["PK"] = pk_base64
|
|
177
|
+
|
|
178
|
+
# logger.info(f"[Request] url: {url}, headers: {headers}, data: {data}")
|
|
179
|
+
|
|
180
|
+
response = self.secure_http_client.post(url, headers=headers, json=data, timeout=self.timeout)
|
|
181
|
+
if response.status_code == 200:
|
|
182
|
+
result = response.json()
|
|
183
|
+
task_id = result.get("Data", {}).get("TaskID", "")
|
|
184
|
+
return task_id
|
|
185
|
+
else:
|
|
186
|
+
logger.error(f"[Error] url: {url}, response: {response.text}")
|
|
187
|
+
return ""
|
|
188
|
+
|
|
189
|
+
def query_task(self, task_id: str, request_id: str = "") -> dict:
|
|
190
|
+
"""
|
|
191
|
+
Query auc task
|
|
192
|
+
API: POST /auc/lark/query
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
if not task_id:
|
|
196
|
+
raise AICCException("task_id must be provided")
|
|
197
|
+
|
|
198
|
+
base_url = self._get_base_url()
|
|
199
|
+
url = f"{base_url}{self.QUERY_TASK_URL}"
|
|
200
|
+
|
|
201
|
+
headers = self._build_common_headers(request_id)
|
|
202
|
+
payload = {"TaskID": task_id}
|
|
203
|
+
|
|
204
|
+
response = self.secure_http_client.post(url, headers=headers, json=payload, timeout=self.timeout)
|
|
205
|
+
if response.status_code == 200:
|
|
206
|
+
result = response.json()
|
|
207
|
+
return result
|
|
208
|
+
else:
|
|
209
|
+
try:
|
|
210
|
+
result = response.json()
|
|
211
|
+
return result
|
|
212
|
+
except Exception as e:
|
|
213
|
+
logger.error(f"[Error] url: {url}, Exception={e}")
|
|
214
|
+
raise AICCException("Failed to query auc task")
|
|
215
|
+
|
|
216
|
+
def download_file(self, task_id: str, auc_file_path: str) -> bool:
|
|
217
|
+
"""
|
|
218
|
+
Download and decrypt encrypted auc
|
|
219
|
+
This function polls the task status until it succeeds, then downloads
|
|
220
|
+
and decrypts the auc using the provided private key.
|
|
221
|
+
Args:
|
|
222
|
+
task_id: AUC generation task ID
|
|
223
|
+
auc_file_path: Directory to save decrypted auc
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
True if download succeeded, False otherwise
|
|
227
|
+
"""
|
|
228
|
+
if not task_id:
|
|
229
|
+
raise AICCException("task_id must be provided")
|
|
230
|
+
|
|
231
|
+
if not auc_file_path:
|
|
232
|
+
raise AICCException("auc_file_path must be provided")
|
|
233
|
+
|
|
234
|
+
task_info = self.query_task(task_id)
|
|
235
|
+
status = task_info.get("Data", {}).get("Status", "")
|
|
236
|
+
if not status:
|
|
237
|
+
logger.error(f"task status is null, task_id={task_id}, query_task return {task_info}")
|
|
238
|
+
raise AICCException("task status is null")
|
|
239
|
+
if status != "success":
|
|
240
|
+
logger.info(f"task status is not success, task_id={task_id}, status={status}")
|
|
241
|
+
return False
|
|
242
|
+
|
|
243
|
+
result = task_info.get("Data", {}).get("Result") or {}
|
|
244
|
+
for label, key in (
|
|
245
|
+
("AudioTranscriptionFile", "AudioTranscriptionFile"),
|
|
246
|
+
("SummarizationFile", "SummarizationFile"),
|
|
247
|
+
("ChapterFile", "ChapterFile"),
|
|
248
|
+
("InformationExtractionFile", "InformationExtractionFile"),
|
|
249
|
+
("TranslationFile", "TranslationFile"),
|
|
250
|
+
):
|
|
251
|
+
file_url = result.get(key, "")
|
|
252
|
+
if file_url:
|
|
253
|
+
local_file_path = f"./{auc_file_path}_{label}.json"
|
|
254
|
+
self.download_file_by_url(file_url, local_file_path)
|
|
255
|
+
|
|
256
|
+
def download_file_by_url(self, file_url: str, local_file_path: str) -> bool:
|
|
257
|
+
"""
|
|
258
|
+
Download auc file
|
|
259
|
+
Args:
|
|
260
|
+
file_url: File file_url
|
|
261
|
+
auc_file_path: Directory to save auc file
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
Path to auc file, or None if failed
|
|
265
|
+
"""
|
|
266
|
+
if not file_url:
|
|
267
|
+
raise AICCException("file_url must be provided")
|
|
268
|
+
|
|
269
|
+
if not local_file_path:
|
|
270
|
+
raise AICCException("local_file_path must be provided")
|
|
271
|
+
|
|
272
|
+
if os.path.exists(local_file_path) and os.path.getsize(local_file_path) > 0:
|
|
273
|
+
logger.error(f"[Error] File {local_file_path} already exists and is not empty")
|
|
274
|
+
return False
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
response = requests.get(file_url, stream=True)
|
|
278
|
+
with open(local_file_path, "wb") as f:
|
|
279
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
280
|
+
f.write(chunk)
|
|
281
|
+
|
|
282
|
+
if self.is_enable_auc_encrypt:
|
|
283
|
+
if not self.auc_file_encrypt_priv_key:
|
|
284
|
+
logger.error("[Error] No private key set, cannot decrypt encrypted auc")
|
|
285
|
+
return False
|
|
286
|
+
|
|
287
|
+
headers = response.headers
|
|
288
|
+
encrypted_key = headers.get("x-tos-meta-enc-dek")
|
|
289
|
+
if encrypted_key:
|
|
290
|
+
try:
|
|
291
|
+
encrypted_key_bytes = base64.b64decode(unquote(encrypted_key))
|
|
292
|
+
encrypted_key = self.auc_file_encrypt_priv_key.decrypt(encrypted_key_bytes)
|
|
293
|
+
tmp_file_path = local_file_path + ".tmp"
|
|
294
|
+
decrypt_file(encrypted_key, local_file_path, tmp_file_path, 'b')
|
|
295
|
+
os.remove(local_file_path)
|
|
296
|
+
os.rename(tmp_file_path, local_file_path)
|
|
297
|
+
except Exception as e:
|
|
298
|
+
logger.error(f"[Error] Failed to decode encrypted_key: {e}")
|
|
299
|
+
traceback.print_exc()
|
|
300
|
+
return False
|
|
301
|
+
except Exception as e:
|
|
302
|
+
logger.error(f"[Error] Download failed: {e}")
|
|
303
|
+
traceback.print_exc()
|
|
304
|
+
return False
|
|
305
|
+
return True
|
|
File without changes
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"gcm_encrypt",
|
|
3
|
+
"gcm_decrypt",
|
|
4
|
+
"aes_encrypt",
|
|
5
|
+
"aes_decrypt",
|
|
6
|
+
"encrypt_file",
|
|
7
|
+
"decrypt_file"
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
import base64
|
|
11
|
+
from typing_extensions import IO, Union
|
|
12
|
+
|
|
13
|
+
import secrets
|
|
14
|
+
|
|
15
|
+
from cryptography.hazmat.backends import default_backend
|
|
16
|
+
from cryptography.hazmat.primitives import hashes
|
|
17
|
+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
18
|
+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
SALT = "ai-handler-salt"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def gcm_decrypt(key: bytes, ciphertext: bytes) -> bytes:
|
|
25
|
+
iv, ciphertext = ciphertext[:12], ciphertext[12:]
|
|
26
|
+
ciphertext, tag = ciphertext[:-16], ciphertext[-16:]
|
|
27
|
+
decryptor = Cipher(
|
|
28
|
+
algorithms.AES(key),
|
|
29
|
+
modes.GCM(iv, tag),
|
|
30
|
+
).decryptor()
|
|
31
|
+
|
|
32
|
+
return decryptor.update(ciphertext) + decryptor.finalize()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def gcm_encrypt(key: bytes, plaintext: bytes) -> bytes:
|
|
36
|
+
iv = secrets.token_bytes(12)
|
|
37
|
+
encryptor = Cipher(
|
|
38
|
+
algorithms.AES(key),
|
|
39
|
+
modes.GCM(iv),
|
|
40
|
+
).encryptor()
|
|
41
|
+
|
|
42
|
+
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
|
|
43
|
+
return iv + ciphertext + encryptor.tag
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def generate_key_from_string(seed_string: str) -> bytes:
|
|
47
|
+
backend = default_backend()
|
|
48
|
+
# 将字符串种子转换为字节
|
|
49
|
+
password = seed_string.encode()
|
|
50
|
+
salt = SALT.encode()
|
|
51
|
+
kdf = PBKDF2HMAC(
|
|
52
|
+
algorithm=hashes.SHA256(), length=32, salt=salt, iterations=100000, backend=backend
|
|
53
|
+
)
|
|
54
|
+
key = kdf.derive(password)
|
|
55
|
+
return key
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def aes_encrypt(aes_key: Union[bytes, str], data: Union[bytes, str]) -> bytes:
|
|
59
|
+
if isinstance(aes_key, str):
|
|
60
|
+
aes_key = aes_key.encode()
|
|
61
|
+
if isinstance(data, str):
|
|
62
|
+
data = data.encode()
|
|
63
|
+
return gcm_encrypt(aes_key, data)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def aes_decrypt(aes_key: Union[bytes, str], data: Union[bytes, str]) -> bytes:
|
|
67
|
+
if isinstance(aes_key, str):
|
|
68
|
+
aes_key = aes_key.encode()
|
|
69
|
+
if isinstance(data, str):
|
|
70
|
+
data = data.encode()
|
|
71
|
+
return gcm_decrypt(aes_key, data)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def encrypt_file(
|
|
75
|
+
aes_key: Union[bytes, str],
|
|
76
|
+
source_path: str,
|
|
77
|
+
dest_path: str,
|
|
78
|
+
mode: str = "b",
|
|
79
|
+
) -> bool:
|
|
80
|
+
if mode not in ["b", "t"]:
|
|
81
|
+
raise Exception("mode argument error")
|
|
82
|
+
|
|
83
|
+
if isinstance(aes_key, str):
|
|
84
|
+
aes_key = aes_key.encode()
|
|
85
|
+
|
|
86
|
+
with open(source_path, f"r{mode}") as source, open(
|
|
87
|
+
dest_path, f"w{mode}"
|
|
88
|
+
) as dest:
|
|
89
|
+
if mode == "b":
|
|
90
|
+
source_b: IO[bytes] = source
|
|
91
|
+
dest_b: IO[bytes] = dest
|
|
92
|
+
plaintext = source_b.read()
|
|
93
|
+
ciphertext = aes_encrypt(aes_key, plaintext)
|
|
94
|
+
dest_b.write(ciphertext)
|
|
95
|
+
else:
|
|
96
|
+
source_t: IO[str] = source
|
|
97
|
+
dest_t: IO[str] = dest
|
|
98
|
+
for plaintext in source_t:
|
|
99
|
+
plaintext = plaintext.rstrip("\n")
|
|
100
|
+
ciphertext = aes_encrypt(aes_key, plaintext)
|
|
101
|
+
dest_t.write(base64.b64encode(ciphertext).decode() + "\n")
|
|
102
|
+
|
|
103
|
+
return True
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def decrypt_file(
|
|
107
|
+
aes_key: Union[bytes, str],
|
|
108
|
+
source_path: str,
|
|
109
|
+
dest_path: str,
|
|
110
|
+
mode: str = "b",
|
|
111
|
+
) -> bool:
|
|
112
|
+
if mode not in ["b", "t"]:
|
|
113
|
+
raise Exception("mode argument error")
|
|
114
|
+
|
|
115
|
+
if isinstance(aes_key, str):
|
|
116
|
+
aes_key = aes_key.encode()
|
|
117
|
+
|
|
118
|
+
with open(source_path, f"r{mode}") as source, open(
|
|
119
|
+
dest_path, f"w{mode}"
|
|
120
|
+
) as dest:
|
|
121
|
+
if mode == "b":
|
|
122
|
+
source_b: IO[bytes] = source
|
|
123
|
+
dest_b: IO[bytes] = dest
|
|
124
|
+
ciphertext = source_b.read()
|
|
125
|
+
plaintext = aes_decrypt(aes_key, ciphertext)
|
|
126
|
+
dest_b.write(plaintext)
|
|
127
|
+
else:
|
|
128
|
+
source_t: IO[str] = source
|
|
129
|
+
dest_t: IO[str] = dest
|
|
130
|
+
for ciphertext in source_t:
|
|
131
|
+
ciphertext = ciphertext.rstrip("\n")
|
|
132
|
+
ciphertext = base64.b64decode(ciphertext.encode())
|
|
133
|
+
plaintext = aes_decrypt(aes_key, ciphertext)
|
|
134
|
+
dest_t.write(plaintext.decode() + "\n")
|
|
135
|
+
# dest_t.write(base64.b64encode(plaintext).decode() + "\n")
|
|
136
|
+
return True
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
安全通信错误类型.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"AICCException"
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AICCException(Exception):
|
|
11
|
+
"""AICC 异常"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, msg):
|
|
14
|
+
Exception.__init__(self)
|
|
15
|
+
self._msg = msg
|
|
16
|
+
|
|
17
|
+
def get_msg(self):
|
|
18
|
+
return self._msg
|
|
19
|
+
|
|
20
|
+
def __str__(self):
|
|
21
|
+
return self.get_msg()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"DEBUG",
|
|
3
|
+
"INFO",
|
|
4
|
+
"WARNING",
|
|
5
|
+
"ERROR",
|
|
6
|
+
"CRITICAL",
|
|
7
|
+
"FORMAT",
|
|
8
|
+
"LogConfig",
|
|
9
|
+
"debug",
|
|
10
|
+
"info",
|
|
11
|
+
"warning",
|
|
12
|
+
"error",
|
|
13
|
+
"critical",
|
|
14
|
+
"init_log_config"
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
from .config import DEBUG, INFO, WARNING, ERROR, CRITICAL, FORMAT, LogConfig
|
|
18
|
+
from .logger import init_log_config, info, debug, warning, error, critical
|
|
19
|
+
|
|
20
|
+
_initialized = False
|
|
21
|
+
if not _initialized:
|
|
22
|
+
config = LogConfig()
|
|
23
|
+
init_log_config(config)
|
|
24
|
+
_initialized = True
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"DEBUG",
|
|
3
|
+
"INFO",
|
|
4
|
+
"WARNING",
|
|
5
|
+
"ERROR",
|
|
6
|
+
"CRITICAL",
|
|
7
|
+
"FORMAT",
|
|
8
|
+
"LogConfig"
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
# Log level constants
|
|
12
|
+
DEBUG = "DEBUG"
|
|
13
|
+
INFO = "INFO"
|
|
14
|
+
WARNING = "WARNING"
|
|
15
|
+
ERROR = "ERROR"
|
|
16
|
+
CRITICAL = "CRITICAL"
|
|
17
|
+
# Log output format
|
|
18
|
+
FORMAT = "{time:YYYY-MM-DD HH:mm:ss} | {level} | {file.path}:{line} | {message}"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class LogConfig:
|
|
22
|
+
"""Log configuration class."""
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self.rotation = 100 # Size limit for log files in MB
|
|
26
|
+
self.retention = 7 # Days to retain log files
|
|
27
|
+
self.compression = "zip"
|
|
28
|
+
self.dir = "aicc_log"
|
|
29
|
+
self.filename = "aicc.log"
|
|
30
|
+
self.format = FORMAT
|
|
31
|
+
self.level = INFO
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"debug",
|
|
3
|
+
"info",
|
|
4
|
+
"warning",
|
|
5
|
+
"error",
|
|
6
|
+
"critical",
|
|
7
|
+
"init_log_config"
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
import os.path
|
|
11
|
+
import sys
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from loguru import logger
|
|
15
|
+
|
|
16
|
+
from . import LogConfig, FORMAT
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def init_log_config(*configs: LogConfig):
|
|
20
|
+
"""Initialize logging configuration with given configs."""
|
|
21
|
+
logger.remove()
|
|
22
|
+
|
|
23
|
+
log_level = "INFO"
|
|
24
|
+
if os.environ.get("log_level"):
|
|
25
|
+
log_level = os.environ.get("log_level")
|
|
26
|
+
if os.environ.get("LOG_LEVEL"):
|
|
27
|
+
log_level = os.environ.get("LOG_LEVEL")
|
|
28
|
+
|
|
29
|
+
logger.add(sink=sys.stdout, format=FORMAT, level=log_level)
|
|
30
|
+
|
|
31
|
+
for config in configs:
|
|
32
|
+
if not config.dir:
|
|
33
|
+
config.dir = os.environ.get("LOG_DIR", "aicc_log")
|
|
34
|
+
|
|
35
|
+
if not config.filename:
|
|
36
|
+
config.filename = "aicc.log"
|
|
37
|
+
|
|
38
|
+
os.makedirs(config.dir, exist_ok=True)
|
|
39
|
+
fullpath = os.path.join(config.dir, config.filename)
|
|
40
|
+
|
|
41
|
+
if config.rotation <= 0:
|
|
42
|
+
config.rotation = 100
|
|
43
|
+
rotation = f"{config.rotation} MB"
|
|
44
|
+
|
|
45
|
+
if config.retention <= 0:
|
|
46
|
+
config.retention = 7
|
|
47
|
+
retention = f"{config.retention} days"
|
|
48
|
+
|
|
49
|
+
logger.add(
|
|
50
|
+
sink=fullpath,
|
|
51
|
+
rotation=rotation,
|
|
52
|
+
retention=retention,
|
|
53
|
+
compression=config.compression,
|
|
54
|
+
format=config.format,
|
|
55
|
+
level=config.level
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def msg_proc(message: str) -> str:
|
|
60
|
+
if message:
|
|
61
|
+
return f"[aicc]:{message}"
|
|
62
|
+
else:
|
|
63
|
+
return message
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def debug(message: str, *args: Any, **kwargs: Any) -> None:
|
|
67
|
+
"""Log debug message."""
|
|
68
|
+
logger.opt(depth=1).debug(msg_proc(message), *args, **kwargs)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def info(message: str, *args: Any, **kwargs: Any) -> None:
|
|
72
|
+
"""Log info message."""
|
|
73
|
+
logger.opt(depth=1).info(msg_proc(message), *args, **kwargs)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def warning(message: str, *args: Any, **kwargs: Any) -> None:
|
|
77
|
+
"""Log warning message."""
|
|
78
|
+
logger.opt(depth=1).warning(msg_proc(message), *args, **kwargs)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def error(message: str, *args: Any, **kwargs: Any) -> None:
|
|
82
|
+
"""Log error message."""
|
|
83
|
+
logger.opt(depth=1).error(msg_proc(message), *args, **kwargs)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def critical(message: str, *args: Any, **kwargs: Any) -> None:
|
|
87
|
+
"""Log critical message."""
|
|
88
|
+
logger.opt(depth=1).critical(msg_proc(message), *args, **kwargs)
|