xiaoshiai-hub 1.1.1__py3-none-any.whl → 1.1.2__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.
- xiaoshiai_hub/__init__.py +7 -0
- xiaoshiai_hub/cli.py +14 -0
- xiaoshiai_hub/client.py +9 -3
- xiaoshiai_hub/envelope_crypto.py +78 -35
- xiaoshiai_hub/upload.py +30 -18
- {xiaoshiai_hub-1.1.1.dist-info → xiaoshiai_hub-1.1.2.dist-info}/METADATA +31 -5
- xiaoshiai_hub-1.1.2.dist-info/RECORD +15 -0
- xiaoshiai_hub-1.1.1.dist-info/RECORD +0 -15
- {xiaoshiai_hub-1.1.1.dist-info → xiaoshiai_hub-1.1.2.dist-info}/WHEEL +0 -0
- {xiaoshiai_hub-1.1.1.dist-info → xiaoshiai_hub-1.1.2.dist-info}/entry_points.txt +0 -0
- {xiaoshiai_hub-1.1.1.dist-info → xiaoshiai_hub-1.1.2.dist-info}/licenses/LICENSE +0 -0
- {xiaoshiai_hub-1.1.1.dist-info → xiaoshiai_hub-1.1.2.dist-info}/top_level.txt +0 -0
xiaoshiai_hub/__init__.py
CHANGED
|
@@ -28,6 +28,10 @@ from .auth import (
|
|
|
28
28
|
load_token,
|
|
29
29
|
delete_token,
|
|
30
30
|
)
|
|
31
|
+
from .envelope_crypto import (
|
|
32
|
+
Algorithm,
|
|
33
|
+
envelope_enc_file,
|
|
34
|
+
)
|
|
31
35
|
|
|
32
36
|
# Upload functionality (requires GitPython)
|
|
33
37
|
try:
|
|
@@ -58,6 +62,9 @@ __all__ = [
|
|
|
58
62
|
"save_token",
|
|
59
63
|
"load_token",
|
|
60
64
|
"delete_token",
|
|
65
|
+
# Encryption
|
|
66
|
+
"Algorithm",
|
|
67
|
+
"envelope_enc_file",
|
|
61
68
|
# Exceptions
|
|
62
69
|
"HubException",
|
|
63
70
|
"RepositoryNotFoundError",
|
xiaoshiai_hub/cli.py
CHANGED
|
@@ -62,6 +62,9 @@ def cmd_upload_folder(args):
|
|
|
62
62
|
"请设置 MOHA_ENCRYPTION_PASSWORD 环境变量或使用 --encryption-password 参数", file=sys.stderr)
|
|
63
63
|
return 1
|
|
64
64
|
|
|
65
|
+
# 获取加密算法
|
|
66
|
+
algorithm = args.algorithm if encryption_password else None
|
|
67
|
+
|
|
65
68
|
try:
|
|
66
69
|
upload_folder(
|
|
67
70
|
folder_path=args.folder,
|
|
@@ -76,6 +79,7 @@ def cmd_upload_folder(args):
|
|
|
76
79
|
encryption_password=encryption_password,
|
|
77
80
|
ignore_patterns=ignore_patterns,
|
|
78
81
|
temp_dir=args.temp_dir,
|
|
82
|
+
algorithm=algorithm,
|
|
79
83
|
)
|
|
80
84
|
print("上传成功!")
|
|
81
85
|
return 0
|
|
@@ -102,6 +106,9 @@ def cmd_upload_file(args):
|
|
|
102
106
|
# 确定仓库中的路径
|
|
103
107
|
path_in_repo = args.path_in_repo if args.path_in_repo else os.path.basename(args.file)
|
|
104
108
|
|
|
109
|
+
# 获取加密算法
|
|
110
|
+
algorithm = args.algorithm if encryption_password else None
|
|
111
|
+
|
|
105
112
|
try:
|
|
106
113
|
upload_file(
|
|
107
114
|
path_file=args.file,
|
|
@@ -115,6 +122,7 @@ def cmd_upload_file(args):
|
|
|
115
122
|
password=password,
|
|
116
123
|
token=token,
|
|
117
124
|
encryption_password=encryption_password,
|
|
125
|
+
algorithm=algorithm,
|
|
118
126
|
)
|
|
119
127
|
print(f"上传成功: {path_in_repo}")
|
|
120
128
|
return 0
|
|
@@ -286,6 +294,12 @@ def _add_encryption_args(parser):
|
|
|
286
294
|
"--encryption-password",
|
|
287
295
|
help="加密密码(或设置 MOHA_ENCRYPTION_PASSWORD 环境变量)",
|
|
288
296
|
)
|
|
297
|
+
parser.add_argument(
|
|
298
|
+
"--algorithm", "-a",
|
|
299
|
+
choices=["AES", "SM4"],
|
|
300
|
+
default="AES",
|
|
301
|
+
help="加密算法(默认: AES)",
|
|
302
|
+
)
|
|
289
303
|
|
|
290
304
|
|
|
291
305
|
def create_parser():
|
xiaoshiai_hub/client.py
CHANGED
|
@@ -8,7 +8,7 @@ from typing import Dict, List, Optional
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
import requests
|
|
11
|
-
from xiaoshiai_hub.envelope_crypto import DataKey
|
|
11
|
+
from xiaoshiai_hub.envelope_crypto import DEFAULT_ALGORITHM, Algorithm, DataKey
|
|
12
12
|
|
|
13
13
|
try:
|
|
14
14
|
from tqdm.auto import tqdm
|
|
@@ -338,12 +338,18 @@ class HubClient:
|
|
|
338
338
|
progress_bar.close()
|
|
339
339
|
|
|
340
340
|
|
|
341
|
-
def generate_data_key(
|
|
341
|
+
def generate_data_key(
|
|
342
|
+
self,
|
|
343
|
+
algorithm : Optional[str] = DEFAULT_ALGORITHM,
|
|
344
|
+
password: Optional[str] = None) -> DataKey:
|
|
342
345
|
url = f"{self.base_url}/api/kms/generate-data-key"
|
|
343
346
|
try:
|
|
344
347
|
resp = requests.post(
|
|
345
348
|
url,
|
|
346
|
-
json={
|
|
349
|
+
json={
|
|
350
|
+
"algorithm": algorithm,
|
|
351
|
+
"password": password,
|
|
352
|
+
},
|
|
347
353
|
timeout=30
|
|
348
354
|
)
|
|
349
355
|
resp.raise_for_status()
|
xiaoshiai_hub/envelope_crypto.py
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
"""
|
|
2
2
|
信封加密核心模块
|
|
3
3
|
|
|
4
|
-
实现基于 AES-256-CTR 的信封加密/解密逻辑。
|
|
4
|
+
实现基于 AES-256-CTR 和 SM4-CTR 的信封加密/解密逻辑。
|
|
5
5
|
支持随机访问和流式解密,适合大文件部分读取场景。
|
|
6
6
|
|
|
7
7
|
信封加密文件格式:
|
|
8
8
|
- 前 4 字节: 元数据长度 (big-endian)
|
|
9
|
-
- 元数据: JSON 格式,包含 encryptedKey 和
|
|
10
|
-
- 16 字节: IV (用于
|
|
9
|
+
- 元数据: JSON 格式,包含 encryptedKey 和 algorithm
|
|
10
|
+
- 16 字节: IV (用于 CTR 模式)
|
|
11
11
|
- 剩余部分: 加密后的文件内容
|
|
12
|
+
|
|
13
|
+
支持的算法:
|
|
14
|
+
- AES: 使用 32 字节密钥
|
|
15
|
+
- SM4: 使用 16 字节密钥
|
|
12
16
|
"""
|
|
13
17
|
|
|
14
18
|
import io
|
|
15
19
|
import os
|
|
16
20
|
import json
|
|
17
21
|
from dataclasses import dataclass
|
|
22
|
+
from enum import Enum
|
|
18
23
|
from pathlib import Path
|
|
19
24
|
import shutil
|
|
20
25
|
import tempfile
|
|
@@ -24,17 +29,29 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
|
24
29
|
from cryptography.hazmat.backends import default_backend
|
|
25
30
|
|
|
26
31
|
|
|
27
|
-
# IV 长度 (AES-256-CTR)
|
|
32
|
+
# IV 长度 (AES-256-CTR 和 SM4-CTR 都使用 16 字节 IV)
|
|
28
33
|
IV_SIZE = 16
|
|
29
34
|
# 元数据长度字段大小
|
|
30
35
|
METADATA_LENGTH_SIZE = 4
|
|
31
36
|
CHUNK_SIZE = 64 * 1024 # 64KB chunks
|
|
32
37
|
|
|
38
|
+
|
|
39
|
+
class Algorithm(str, Enum):
|
|
40
|
+
"""支持的加密算法"""
|
|
41
|
+
AES = "AES"
|
|
42
|
+
SM4 = "SM4"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# 默认算法
|
|
46
|
+
DEFAULT_ALGORITHM = Algorithm.AES
|
|
47
|
+
|
|
48
|
+
|
|
33
49
|
@dataclass
|
|
34
50
|
class DataKey:
|
|
35
51
|
"""数据密钥对象,包含明文密钥和加密后的密钥"""
|
|
36
|
-
plaintext_key: bytes # 明文密钥(
|
|
37
|
-
encrypted_key: str
|
|
52
|
+
plaintext_key: bytes # 明文密钥(AES-256: 32字节,SM4: 16字节)
|
|
53
|
+
encrypted_key: str
|
|
54
|
+
|
|
38
55
|
|
|
39
56
|
def _raw_open(path, mode="rb"):
|
|
40
57
|
"""
|
|
@@ -49,26 +66,51 @@ def _raw_open(path, mode="rb"):
|
|
|
49
66
|
return file_io
|
|
50
67
|
|
|
51
68
|
|
|
52
|
-
def
|
|
53
|
-
"""
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
def create_cipher(key: bytes, iv: bytes, algorithm: Algorithm = DEFAULT_ALGORITHM):
|
|
70
|
+
"""
|
|
71
|
+
创建加密 cipher
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
key: 密钥(AES-256: 32字节,SM4: 16字节)
|
|
75
|
+
iv: 初始化向量(16字节)
|
|
76
|
+
algorithm: 加密算法
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Cipher 对象
|
|
80
|
+
"""
|
|
81
|
+
if algorithm == Algorithm.AES:
|
|
82
|
+
if len(key) != 32:
|
|
83
|
+
raise ValueError(f"AES requires 32-byte key, got {len(key)} bytes")
|
|
84
|
+
return Cipher(
|
|
85
|
+
algorithms.AES(key),
|
|
86
|
+
modes.CTR(iv),
|
|
87
|
+
backend=default_backend()
|
|
88
|
+
)
|
|
89
|
+
elif algorithm == Algorithm.SM4:
|
|
90
|
+
if len(key) != 16:
|
|
91
|
+
raise ValueError(f"SM4 requires 16-byte key, got {len(key)} bytes")
|
|
92
|
+
return Cipher(
|
|
93
|
+
algorithms.SM4(key),
|
|
94
|
+
modes.CTR(iv),
|
|
95
|
+
backend=default_backend()
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
raise ValueError(f"Unsupported algorithm: {algorithm}")
|
|
99
|
+
|
|
100
|
+
|
|
59
101
|
|
|
60
102
|
|
|
61
103
|
@dataclass
|
|
62
104
|
class EnvelopeMetadata:
|
|
63
105
|
"""加密文件元数据"""
|
|
64
106
|
encrypted_key: str # 加密后的数据密钥(Base64)
|
|
65
|
-
|
|
107
|
+
algorithm: str # 加密算法名称
|
|
66
108
|
|
|
67
109
|
def to_bytes(self) -> bytes:
|
|
68
110
|
"""序列化为 JSON 字节"""
|
|
69
111
|
return json.dumps({
|
|
70
112
|
"encryptedKey": self.encrypted_key,
|
|
71
|
-
"
|
|
113
|
+
"algorithm": self.algorithm
|
|
72
114
|
}).encode("utf-8")
|
|
73
115
|
|
|
74
116
|
@classmethod
|
|
@@ -77,30 +119,30 @@ class EnvelopeMetadata:
|
|
|
77
119
|
obj = json.loads(data.decode("utf-8"))
|
|
78
120
|
return cls(
|
|
79
121
|
encrypted_key=obj["encryptedKey"],
|
|
80
|
-
|
|
122
|
+
algorithm=obj.get("algorithm", Algorithm.AES.value) # 兼容旧格式
|
|
81
123
|
)
|
|
82
124
|
|
|
83
125
|
|
|
84
126
|
def _envelope_encrypt_file(
|
|
85
127
|
input_path: Union[str, Path],
|
|
86
128
|
output_path: Union[str, Path],
|
|
87
|
-
password: str,
|
|
88
129
|
data_key: DataKey,
|
|
130
|
+
algorithm: Algorithm = DEFAULT_ALGORITHM,
|
|
89
131
|
) -> None:
|
|
90
132
|
"""
|
|
91
|
-
使用信封加密对文件进行加密
|
|
133
|
+
使用信封加密对文件进行加密
|
|
92
134
|
|
|
93
135
|
Args:
|
|
94
136
|
input_path: 明文文件路径
|
|
95
137
|
output_path: 加密文件输出路径
|
|
96
|
-
|
|
97
|
-
|
|
138
|
+
data_key: 数据密钥对象
|
|
139
|
+
algorithm: 加密算法(默认 AES)
|
|
98
140
|
"""
|
|
99
141
|
input_path = Path(input_path)
|
|
100
142
|
output_path = Path(output_path)
|
|
101
143
|
|
|
102
144
|
iv = os.urandom(IV_SIZE)
|
|
103
|
-
cipher =
|
|
145
|
+
cipher = create_cipher(data_key.plaintext_key, iv, algorithm)
|
|
104
146
|
encryptor = cipher.encryptor()
|
|
105
147
|
with _raw_open(input_path, "rb") as f:
|
|
106
148
|
plaintext = f.read()
|
|
@@ -108,7 +150,7 @@ def _envelope_encrypt_file(
|
|
|
108
150
|
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
|
|
109
151
|
metadata = EnvelopeMetadata(
|
|
110
152
|
encrypted_key=data_key.encrypted_key,
|
|
111
|
-
|
|
153
|
+
algorithm=algorithm.value
|
|
112
154
|
)
|
|
113
155
|
metadata_bytes = metadata.to_bytes()
|
|
114
156
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -123,25 +165,26 @@ def _envelope_encrypt_file(
|
|
|
123
165
|
def envelope_enc_file(
|
|
124
166
|
source: Union[Path, str],
|
|
125
167
|
*,
|
|
126
|
-
password: str,
|
|
127
168
|
data_key: DataKey,
|
|
128
169
|
dest: Optional[Union[Path, str]] = None,
|
|
129
170
|
replace: bool = False,
|
|
130
171
|
chunked: bool = True,
|
|
131
|
-
chunk_size: int = CHUNK_SIZE,
|
|
172
|
+
chunk_size: int = CHUNK_SIZE,
|
|
173
|
+
algorithm: Algorithm = DEFAULT_ALGORITHM,
|
|
132
174
|
) -> Path:
|
|
133
|
-
"""
|
|
175
|
+
"""使用信封加密模式加密单个文件。
|
|
134
176
|
|
|
135
177
|
信封加密使用 KMS 服务生成数据密钥,数据密钥用于加密文件内容,
|
|
136
178
|
加密后的数据密钥存储在文件头部。
|
|
137
179
|
|
|
138
180
|
Args:
|
|
139
181
|
source: 源文件路径
|
|
140
|
-
|
|
182
|
+
data_key: 数据密钥对象
|
|
141
183
|
dest: 目标文件路径(默认添加 .encrypted 后缀)
|
|
142
184
|
replace: 是否原地加密(替换原文件)
|
|
143
185
|
chunked: 是否使用流式加密(适用于大文件,减少内存占用)
|
|
144
186
|
chunk_size: 流式加密时每次处理的块大小
|
|
187
|
+
algorithm: 加密算法(默认 AES,可选 SM4)
|
|
145
188
|
|
|
146
189
|
Returns:
|
|
147
190
|
加密后的文件路径
|
|
@@ -161,9 +204,9 @@ def envelope_enc_file(
|
|
|
161
204
|
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
162
205
|
|
|
163
206
|
if chunked:
|
|
164
|
-
_envelope_encrypt_file_streaming(src, dst,
|
|
207
|
+
_envelope_encrypt_file_streaming(src, dst, data_key, chunk_size, algorithm)
|
|
165
208
|
else:
|
|
166
|
-
_envelope_encrypt_file(src, dst,
|
|
209
|
+
_envelope_encrypt_file(src, dst, data_key, algorithm)
|
|
167
210
|
|
|
168
211
|
# 替换模式:移动加密文件到原位置
|
|
169
212
|
if replace:
|
|
@@ -185,32 +228,32 @@ def _make_temp_path(parent: Path, suffix: str) -> Path:
|
|
|
185
228
|
def _envelope_encrypt_file_streaming(
|
|
186
229
|
input_path: Union[str, Path],
|
|
187
230
|
output_path: Union[str, Path],
|
|
188
|
-
password: str,
|
|
189
231
|
data_key: DataKey,
|
|
190
232
|
chunk_size: int = CHUNK_SIZE,
|
|
233
|
+
algorithm: Algorithm = DEFAULT_ALGORITHM,
|
|
191
234
|
) -> None:
|
|
192
235
|
"""
|
|
193
|
-
使用信封加密对大文件进行流式加密
|
|
236
|
+
使用信封加密对大文件进行流式加密
|
|
194
237
|
|
|
195
|
-
|
|
238
|
+
CTR 模式天然支持流式加密,不需要将整个文件加载到内存。
|
|
196
239
|
加密后的文件格式与普通模式完全相同,可以用普通模式解密。
|
|
197
240
|
|
|
198
241
|
Args:
|
|
199
242
|
input_path: 明文文件路径
|
|
200
243
|
output_path: 加密文件输出路径
|
|
201
|
-
|
|
202
|
-
kms_client: KMS 客户端实例
|
|
244
|
+
data_key: 数据密钥对象
|
|
203
245
|
chunk_size: 每次读取的块大小
|
|
246
|
+
algorithm: 加密算法(默认 AES)
|
|
204
247
|
"""
|
|
205
248
|
input_path = Path(input_path)
|
|
206
249
|
output_path = Path(output_path)
|
|
207
250
|
iv = os.urandom(IV_SIZE)
|
|
208
251
|
metadata = EnvelopeMetadata(
|
|
209
252
|
encrypted_key=data_key.encrypted_key,
|
|
210
|
-
|
|
253
|
+
algorithm=algorithm.value
|
|
211
254
|
)
|
|
212
255
|
metadata_bytes = metadata.to_bytes()
|
|
213
|
-
cipher =
|
|
256
|
+
cipher = create_cipher(data_key.plaintext_key, iv, algorithm)
|
|
214
257
|
encryptor = cipher.encryptor()
|
|
215
258
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
216
259
|
with _raw_open(input_path, "rb") as fin, _raw_open(output_path, "wb") as fout:
|
xiaoshiai_hub/upload.py
CHANGED
|
@@ -12,7 +12,7 @@ from typing import List, Optional, Union, Dict
|
|
|
12
12
|
import requests
|
|
13
13
|
|
|
14
14
|
from xiaoshiai_hub.client import DEFAULT_BASE_URL, HubClient
|
|
15
|
-
from xiaoshiai_hub.envelope_crypto import DataKey, envelope_enc_file
|
|
15
|
+
from xiaoshiai_hub.envelope_crypto import Algorithm, DataKey, envelope_enc_file
|
|
16
16
|
from .exceptions import HubException, AuthenticationError, RepositoryNotFoundError
|
|
17
17
|
|
|
18
18
|
|
|
@@ -125,6 +125,7 @@ def _encrypt_file_if_needed(
|
|
|
125
125
|
file_path: Path,
|
|
126
126
|
encryption_password: Optional[str] = None,
|
|
127
127
|
data_key: Optional[DataKey] = None,
|
|
128
|
+
algorithm: Optional[Algorithm] = None,
|
|
128
129
|
) -> tuple[Optional[Path], Optional[Path]]:
|
|
129
130
|
"""
|
|
130
131
|
Encrypt file if encryption_password is provided, file is large enough, and file extension is encryptable.
|
|
@@ -133,6 +134,7 @@ def _encrypt_file_if_needed(
|
|
|
133
134
|
file_path: Path to the file to encrypt
|
|
134
135
|
encryption_password: Password for encryption
|
|
135
136
|
data_key: DataKey for encryption (required if encryption_password is provided)
|
|
137
|
+
algorithm: Encryption algorithm (default: AES-256-CTR)
|
|
136
138
|
|
|
137
139
|
Returns:
|
|
138
140
|
Tuple of (encrypted_file_path, temp_dir_path)
|
|
@@ -154,13 +156,15 @@ def _encrypt_file_if_needed(
|
|
|
154
156
|
encrypted_file = temp_dir / file_path.name
|
|
155
157
|
|
|
156
158
|
# Encrypt the file
|
|
157
|
-
|
|
158
|
-
source
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
159
|
+
enc_kwargs = {
|
|
160
|
+
"source": file_path,
|
|
161
|
+
"data_key": data_key,
|
|
162
|
+
"dest": encrypted_file,
|
|
163
|
+
"chunked": True,
|
|
164
|
+
}
|
|
165
|
+
if algorithm:
|
|
166
|
+
enc_kwargs["algorithm"] = algorithm
|
|
167
|
+
envelope_enc_file(**enc_kwargs)
|
|
164
168
|
print(f"Encrypted file path: {encrypted_file}")
|
|
165
169
|
|
|
166
170
|
return encrypted_file, temp_dir
|
|
@@ -295,6 +299,7 @@ def upload_folder(
|
|
|
295
299
|
encryption_password: Optional[str] = None,
|
|
296
300
|
ignore_patterns: Optional[List[str]] = None,
|
|
297
301
|
temp_dir: Optional[Union[str, Path]] = None,
|
|
302
|
+
algorithm: Optional[str] = None,
|
|
298
303
|
) -> Dict:
|
|
299
304
|
"""
|
|
300
305
|
Upload a folder to a repository using HTTP API.
|
|
@@ -313,6 +318,7 @@ def upload_folder(
|
|
|
313
318
|
encryption_password: Password for file encryption (optional)
|
|
314
319
|
ignore_patterns: List of patterns to ignore
|
|
315
320
|
temp_dir: Temporary directory for encrypted files (optional, auto-created if not specified)
|
|
321
|
+
algorithm: Encryption algorithm ("AES-256-CTR" or "SM4-CTR", default: AES-256-CTR)
|
|
316
322
|
|
|
317
323
|
Returns:
|
|
318
324
|
Upload response
|
|
@@ -361,7 +367,7 @@ def upload_folder(
|
|
|
361
367
|
# Auto-create temp directory
|
|
362
368
|
temp_dir_path = Path(tempfile.mkdtemp())
|
|
363
369
|
# Generate data key for encryption
|
|
364
|
-
data_key = client.generate_data_key(encryption_password)
|
|
370
|
+
data_key = client.generate_data_key(algorithm,encryption_password)
|
|
365
371
|
|
|
366
372
|
try:
|
|
367
373
|
# Create session and API URL
|
|
@@ -409,13 +415,16 @@ def upload_folder(
|
|
|
409
415
|
# Preserve directory structure in temp dir
|
|
410
416
|
encrypted_file = temp_dir_path / rel_file_path
|
|
411
417
|
encrypted_file.parent.mkdir(parents=True, exist_ok=True)
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
data_key
|
|
416
|
-
dest
|
|
417
|
-
chunked
|
|
418
|
-
|
|
418
|
+
# Build encryption kwargs
|
|
419
|
+
enc_kwargs = {
|
|
420
|
+
"source": local_file,
|
|
421
|
+
"data_key": data_key,
|
|
422
|
+
"dest": encrypted_file,
|
|
423
|
+
"chunked": True,
|
|
424
|
+
}
|
|
425
|
+
if algorithm:
|
|
426
|
+
enc_kwargs["algorithm"] = Algorithm(algorithm)
|
|
427
|
+
envelope_enc_file(**enc_kwargs)
|
|
419
428
|
print(f"Encrypted file path: {encrypted_file}")
|
|
420
429
|
# 加密后重新计算大小,避免被huggingface下载的时候大小一致性检查不通过
|
|
421
430
|
file_size = encrypted_file.stat().st_size
|
|
@@ -512,6 +521,7 @@ def upload_file(
|
|
|
512
521
|
password: Optional[str] = None,
|
|
513
522
|
token: Optional[str] = None,
|
|
514
523
|
encryption_password: Optional[str] = None,
|
|
524
|
+
algorithm: Optional[str] = None,
|
|
515
525
|
) -> Dict:
|
|
516
526
|
"""
|
|
517
527
|
Upload a single file to a repository using HTTP API.
|
|
@@ -529,6 +539,7 @@ def upload_file(
|
|
|
529
539
|
password: Password for authentication
|
|
530
540
|
token: Token for authentication (preferred)
|
|
531
541
|
encryption_password: Password for file encryption (optional)
|
|
542
|
+
algorithm: Encryption algorithm ("AES" or "SM4", default: AES)
|
|
532
543
|
|
|
533
544
|
Returns:
|
|
534
545
|
Upload response
|
|
@@ -561,10 +572,11 @@ def upload_file(
|
|
|
561
572
|
# Generate data key if encryption is needed
|
|
562
573
|
data_key: Optional[DataKey] = None
|
|
563
574
|
if encryption_password:
|
|
564
|
-
data_key = client.generate_data_key(encryption_password)
|
|
575
|
+
data_key = client.generate_data_key(algorithm,encryption_password)
|
|
565
576
|
|
|
566
577
|
# Encrypt file if needed
|
|
567
|
-
|
|
578
|
+
algo = Algorithm(algorithm) if algorithm else None
|
|
579
|
+
encrypted_file, temp_dir = _encrypt_file_if_needed(path_file, encryption_password, data_key, algo)
|
|
568
580
|
actual_file = encrypted_file if encrypted_file else path_file
|
|
569
581
|
file_was_encrypted = encrypted_file is not None
|
|
570
582
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xiaoshiai-hub
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: Python SDK for XiaoShi AI Hub - Upload, download, and manage AI models and datasets with xpai-enc encryption support
|
|
5
5
|
Home-page: https://github.com/poxiaoyun/moha-sdk
|
|
6
6
|
Author: XiaoShi AI
|
|
@@ -262,7 +262,14 @@ for entry in content.entries:
|
|
|
262
262
|
|
|
263
263
|
## 🔐 加密功能
|
|
264
264
|
|
|
265
|
-
SDK
|
|
265
|
+
SDK 提供了智能加密功能,支持 **AES** 和 **SM4** 两种加密算法对大型模型文件进行加密。
|
|
266
|
+
|
|
267
|
+
### 支持的加密算法
|
|
268
|
+
|
|
269
|
+
| 算法 | 说明 |
|
|
270
|
+
|------|------|
|
|
271
|
+
| `AES` | AES-256-CTR 模式,国际通用标准(默认) |
|
|
272
|
+
| `SM4` | SM4-CTR 模式,国密标准 |
|
|
266
273
|
|
|
267
274
|
### 自动加密规则
|
|
268
275
|
|
|
@@ -278,11 +285,21 @@ SDK 提供了智能加密功能,使用 AES-256-CTR 算法对大型模型文件
|
|
|
278
285
|
```python
|
|
279
286
|
from xiaoshiai_hub import upload_folder
|
|
280
287
|
|
|
281
|
-
#
|
|
288
|
+
# 上传文件夹,使用 AES 加密(默认)
|
|
282
289
|
result = upload_folder(
|
|
283
290
|
folder_path="./llama-7b",
|
|
284
291
|
repo_id="demo/llama-7b",
|
|
285
|
-
encryption_password="my-secure-password-123",
|
|
292
|
+
encryption_password="my-secure-password-123",
|
|
293
|
+
username="your-username",
|
|
294
|
+
password="your-password",
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# 使用 SM4 国密算法加密
|
|
298
|
+
result = upload_folder(
|
|
299
|
+
folder_path="./llama-7b",
|
|
300
|
+
repo_id="demo/llama-7b",
|
|
301
|
+
encryption_password="my-secure-password-123",
|
|
302
|
+
algorithm="SM4", # 使用 SM4 加密
|
|
286
303
|
username="your-username",
|
|
287
304
|
password="your-password",
|
|
288
305
|
)
|
|
@@ -353,10 +370,18 @@ moha upload ./my_model org/my-model \
|
|
|
353
370
|
--username your-username \
|
|
354
371
|
--password your-password
|
|
355
372
|
|
|
356
|
-
#
|
|
373
|
+
# 启用加密(默认使用 AES)
|
|
374
|
+
moha upload ./my_model org/my-model \
|
|
375
|
+
--encrypt \
|
|
376
|
+
--encryption-password "your-secret" \
|
|
377
|
+
--username your-username \
|
|
378
|
+
--password your-password
|
|
379
|
+
|
|
380
|
+
# 使用 SM4 国密算法加密
|
|
357
381
|
moha upload ./my_model org/my-model \
|
|
358
382
|
--encrypt \
|
|
359
383
|
--encryption-password "your-secret" \
|
|
384
|
+
--algorithm SM4 \
|
|
360
385
|
--username your-username \
|
|
361
386
|
--password your-password
|
|
362
387
|
```
|
|
@@ -434,6 +459,7 @@ moha download-file org/my-model model.safetensors \
|
|
|
434
459
|
| `--include` | 包含模式(可多次使用) | download |
|
|
435
460
|
| `--encrypt, -e` | 启用加密 | upload, upload-file |
|
|
436
461
|
| `--encryption-password` | 加密密码 | upload, upload-file |
|
|
462
|
+
| `--algorithm, -a` | 加密算法:`AES` 或 `SM4`(默认:AES) | upload, upload-file |
|
|
437
463
|
| `--path-in-repo, -p` | 仓库中的文件路径 | upload-file |
|
|
438
464
|
| `--temp-dir` | 加密临时目录 | upload |
|
|
439
465
|
| `--local-dir, -o` | 本地保存目录 | download, download-file |
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
xiaoshiai_hub/__init__.py,sha256=2hnhTGad1GPTzRstfwZhAF2204Pl2VF8YHD8HQkAYGY,1469
|
|
2
|
+
xiaoshiai_hub/auth.py,sha256=Pv0P6f76flOefYmALyehuMu2Klyc-u_gtXlJYJR4eMY,4105
|
|
3
|
+
xiaoshiai_hub/cli.py,sha256=lBmIKlxmDk74WYttDCqF5fAuIKjvcIlETipWLyBUP4U,15292
|
|
4
|
+
xiaoshiai_hub/client.py,sha256=Ikq-eJz1kA5KA0_PbzAKhmnPOpZT_AubRIefcCOZYBk,12113
|
|
5
|
+
xiaoshiai_hub/download.py,sha256=9Uido7cJjGVd6ERDKu_xNMPliarPdZVVb8hkLgYfFcU,13613
|
|
6
|
+
xiaoshiai_hub/envelope_crypto.py,sha256=zjrt5fc3ya86Y4N7_4OI5_NnmFpQ5HiHgoP31ioJRmw,8235
|
|
7
|
+
xiaoshiai_hub/exceptions.py,sha256=24QzgHWq_4bes07UkC3vGi2oT8SMH6Xu4FNlKt52QHo,672
|
|
8
|
+
xiaoshiai_hub/types.py,sha256=ZC5elxqles8_ODl-fCSssOzm9q8_KjA9mGpiGgObgls,1915
|
|
9
|
+
xiaoshiai_hub/upload.py,sha256=Hb9KX-87YP7HyskHbQCtfgn11ARzzKzPX_i-fhRtZ0E,21372
|
|
10
|
+
xiaoshiai_hub-1.1.2.dist-info/licenses/LICENSE,sha256=tS28u6VpvqNisRWGeufp-XYQc6p194vOGARl3OIjidA,9110
|
|
11
|
+
xiaoshiai_hub-1.1.2.dist-info/METADATA,sha256=QEUR27oyHBY2bm-CIseIRQZzYFzWgtoMORZExQQeAEo,17454
|
|
12
|
+
xiaoshiai_hub-1.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
+
xiaoshiai_hub-1.1.2.dist-info/entry_points.txt,sha256=fVh_IA1mbRWl7LEd4-RhENMaspSBa4Hxbsg8_HDWc6Y,48
|
|
14
|
+
xiaoshiai_hub-1.1.2.dist-info/top_level.txt,sha256=9AQDFb5Xn7RLQPdbk1aA0QpntbKhlhlT6Z_g-zUBtlM,14
|
|
15
|
+
xiaoshiai_hub-1.1.2.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
xiaoshiai_hub/__init__.py,sha256=ZDZEGKBbv8zEDO9GkKPy0uiTva9gPPEMIU9_A_MzCZA,1339
|
|
2
|
-
xiaoshiai_hub/auth.py,sha256=Pv0P6f76flOefYmALyehuMu2Klyc-u_gtXlJYJR4eMY,4105
|
|
3
|
-
xiaoshiai_hub/cli.py,sha256=IKuHYGyk-Kg57BUquWyZFMK6rPfLMmwEOWGZq8E7LJs,14885
|
|
4
|
-
xiaoshiai_hub/client.py,sha256=OQyyKD5kyDaPlI81-js1Uj9gYjVPkS60UicZAtokDVw,11914
|
|
5
|
-
xiaoshiai_hub/download.py,sha256=9Uido7cJjGVd6ERDKu_xNMPliarPdZVVb8hkLgYfFcU,13613
|
|
6
|
-
xiaoshiai_hub/envelope_crypto.py,sha256=PWZrVCkgQckFQO6putRDlZXkQoEYOkq5O0PPltdBDE4,7161
|
|
7
|
-
xiaoshiai_hub/exceptions.py,sha256=24QzgHWq_4bes07UkC3vGi2oT8SMH6Xu4FNlKt52QHo,672
|
|
8
|
-
xiaoshiai_hub/types.py,sha256=ZC5elxqles8_ODl-fCSssOzm9q8_KjA9mGpiGgObgls,1915
|
|
9
|
-
xiaoshiai_hub/upload.py,sha256=cYKCvaxfO5wOmjCmWp_rgbTOdEr73MN2EoBUCwyqqvE,20713
|
|
10
|
-
xiaoshiai_hub-1.1.1.dist-info/licenses/LICENSE,sha256=tS28u6VpvqNisRWGeufp-XYQc6p194vOGARl3OIjidA,9110
|
|
11
|
-
xiaoshiai_hub-1.1.1.dist-info/METADATA,sha256=0zbLRQQGJbyp5nAcmloTSvoRe9LEXuB8pKGBWNgUudo,16689
|
|
12
|
-
xiaoshiai_hub-1.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
-
xiaoshiai_hub-1.1.1.dist-info/entry_points.txt,sha256=fVh_IA1mbRWl7LEd4-RhENMaspSBa4Hxbsg8_HDWc6Y,48
|
|
14
|
-
xiaoshiai_hub-1.1.1.dist-info/top_level.txt,sha256=9AQDFb5Xn7RLQPdbk1aA0QpntbKhlhlT6Z_g-zUBtlM,14
|
|
15
|
-
xiaoshiai_hub-1.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|