xiaoshiai-hub 0.1.3__py3-none-any.whl → 1.0.1__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 +2 -27
- xiaoshiai_hub/client.py +11 -58
- xiaoshiai_hub/download.py +20 -199
- xiaoshiai_hub/exceptions.py +1 -5
- xiaoshiai_hub/types.py +0 -16
- xiaoshiai_hub/upload.py +477 -711
- xiaoshiai_hub-1.0.1.dist-info/METADATA +473 -0
- xiaoshiai_hub-1.0.1.dist-info/RECORD +11 -0
- xiaoshiai_hub/encryption.py +0 -777
- xiaoshiai_hub-0.1.3.dist-info/METADATA +0 -560
- xiaoshiai_hub-0.1.3.dist-info/RECORD +0 -12
- {xiaoshiai_hub-0.1.3.dist-info → xiaoshiai_hub-1.0.1.dist-info}/WHEEL +0 -0
- {xiaoshiai_hub-0.1.3.dist-info → xiaoshiai_hub-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {xiaoshiai_hub-0.1.3.dist-info → xiaoshiai_hub-1.0.1.dist-info}/top_level.txt +0 -0
xiaoshiai_hub/encryption.py
DELETED
|
@@ -1,777 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Encryption utilities for XiaoShi AI Hub SDK.
|
|
3
|
-
|
|
4
|
-
Supports multiple encryption algorithms:
|
|
5
|
-
- Symmetric: AES-256-CBC, AES-256-GCM, SM4-CBC, SM4-GCM
|
|
6
|
-
- Asymmetric: RSA-OAEP, RSA-PKCS1v15, SM2
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from typing import Optional, Union
|
|
11
|
-
from enum import Enum
|
|
12
|
-
|
|
13
|
-
from .exceptions import EncryptionError
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class EncryptionAlgorithm(str, Enum):
|
|
17
|
-
"""Supported encryption algorithms."""
|
|
18
|
-
|
|
19
|
-
# 对称加密算法
|
|
20
|
-
AES_256_CBC = "aes-256-cbc"
|
|
21
|
-
AES_256_GCM = "aes-256-gcm"
|
|
22
|
-
SM4_CBC = "sm4-cbc"
|
|
23
|
-
SM4_GCM = "sm4-gcm"
|
|
24
|
-
|
|
25
|
-
# 非对称加密算法
|
|
26
|
-
RSA_OAEP = "rsa-oaep"
|
|
27
|
-
RSA_PKCS1V15 = "rsa-pkcs1v15"
|
|
28
|
-
SM2 = "sm2"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def _import_crypto_dependencies():
|
|
32
|
-
"""Import cryptography dependencies."""
|
|
33
|
-
try:
|
|
34
|
-
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
35
|
-
from cryptography.hazmat.backends import default_backend
|
|
36
|
-
from cryptography.hazmat.primitives import padding, hashes, serialization
|
|
37
|
-
from cryptography.hazmat.primitives.asymmetric import rsa, padding as asym_padding
|
|
38
|
-
import hashlib
|
|
39
|
-
import os as os_module
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
'Cipher': Cipher,
|
|
43
|
-
'algorithms': algorithms,
|
|
44
|
-
'modes': modes,
|
|
45
|
-
'default_backend': default_backend,
|
|
46
|
-
'padding': padding,
|
|
47
|
-
'hashes': hashes,
|
|
48
|
-
'serialization': serialization,
|
|
49
|
-
'rsa': rsa,
|
|
50
|
-
'asym_padding': asym_padding,
|
|
51
|
-
'hashlib': hashlib,
|
|
52
|
-
'os': os_module,
|
|
53
|
-
}
|
|
54
|
-
except ImportError:
|
|
55
|
-
raise EncryptionError(
|
|
56
|
-
"Encryption requires the 'cryptography' package. "
|
|
57
|
-
"Install it with: pip install cryptography"
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _import_gmssl_dependencies():
|
|
62
|
-
"""Import gmssl dependencies for Chinese SM algorithms."""
|
|
63
|
-
try:
|
|
64
|
-
from gmssl import sm2, sm3, sm4
|
|
65
|
-
import os as os_module
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
'sm2': sm2,
|
|
69
|
-
'sm3': sm3,
|
|
70
|
-
'sm4': sm4,
|
|
71
|
-
'os': os_module,
|
|
72
|
-
}
|
|
73
|
-
except ImportError:
|
|
74
|
-
raise EncryptionError(
|
|
75
|
-
"SM encryption requires the 'gmssl' package. "
|
|
76
|
-
"Install it with: pip install gmssl or pip install xiaoshiai-hub[sm]"
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def _encrypt_aes_cbc(plaintext: bytes, key: bytes) -> bytes:
|
|
81
|
-
"""
|
|
82
|
-
Encrypt data using AES-256-CBC.
|
|
83
|
-
|
|
84
|
-
Args:
|
|
85
|
-
plaintext: Data to encrypt
|
|
86
|
-
key: 32-byte encryption key
|
|
87
|
-
|
|
88
|
-
Returns:
|
|
89
|
-
IV (16 bytes) + Ciphertext
|
|
90
|
-
"""
|
|
91
|
-
deps = _import_crypto_dependencies()
|
|
92
|
-
|
|
93
|
-
# Generate random IV
|
|
94
|
-
iv = deps['os'].urandom(16)
|
|
95
|
-
|
|
96
|
-
# Pad plaintext
|
|
97
|
-
padder = deps['padding'].PKCS7(128).padder()
|
|
98
|
-
padded_data = padder.update(plaintext) + padder.finalize()
|
|
99
|
-
|
|
100
|
-
# Encrypt
|
|
101
|
-
cipher = deps['Cipher'](
|
|
102
|
-
deps['algorithms'].AES(key),
|
|
103
|
-
deps['modes'].CBC(iv),
|
|
104
|
-
backend=deps['default_backend']()
|
|
105
|
-
)
|
|
106
|
-
encryptor = cipher.encryptor()
|
|
107
|
-
ciphertext = encryptor.update(padded_data) + encryptor.finalize()
|
|
108
|
-
|
|
109
|
-
# Return IV + ciphertext
|
|
110
|
-
return iv + ciphertext
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def _encrypt_aes_gcm(plaintext: bytes, key: bytes) -> bytes:
|
|
114
|
-
"""
|
|
115
|
-
Encrypt data using AES-256-GCM.
|
|
116
|
-
|
|
117
|
-
Args:
|
|
118
|
-
plaintext: Data to encrypt
|
|
119
|
-
key: 32-byte encryption key
|
|
120
|
-
|
|
121
|
-
Returns:
|
|
122
|
-
IV (12 bytes) + Tag (16 bytes) + Ciphertext
|
|
123
|
-
"""
|
|
124
|
-
deps = _import_crypto_dependencies()
|
|
125
|
-
|
|
126
|
-
# Generate random IV (12 bytes for GCM)
|
|
127
|
-
iv = deps['os'].urandom(12)
|
|
128
|
-
|
|
129
|
-
# Encrypt
|
|
130
|
-
cipher = deps['Cipher'](
|
|
131
|
-
deps['algorithms'].AES(key),
|
|
132
|
-
deps['modes'].GCM(iv),
|
|
133
|
-
backend=deps['default_backend']()
|
|
134
|
-
)
|
|
135
|
-
encryptor = cipher.encryptor()
|
|
136
|
-
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
|
|
137
|
-
|
|
138
|
-
# Return IV + tag + ciphertext
|
|
139
|
-
return iv + encryptor.tag + ciphertext
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
def _encrypt_rsa(plaintext: bytes, public_key_pem: str, algorithm: EncryptionAlgorithm) -> bytes:
|
|
143
|
-
"""
|
|
144
|
-
Encrypt data using RSA.
|
|
145
|
-
|
|
146
|
-
For large files, uses hybrid encryption:
|
|
147
|
-
1. Generate random AES key
|
|
148
|
-
2. Encrypt file with AES-256-GCM
|
|
149
|
-
3. Encrypt AES key with RSA
|
|
150
|
-
4. Return: RSA(AES_key) + AES-GCM(plaintext)
|
|
151
|
-
|
|
152
|
-
Args:
|
|
153
|
-
plaintext: Data to encrypt
|
|
154
|
-
public_key_pem: RSA public key in PEM format
|
|
155
|
-
algorithm: RSA algorithm to use
|
|
156
|
-
|
|
157
|
-
Returns:
|
|
158
|
-
Encrypted key length (4 bytes) + Encrypted AES key + AES-GCM encrypted data
|
|
159
|
-
"""
|
|
160
|
-
deps = _import_crypto_dependencies()
|
|
161
|
-
|
|
162
|
-
# Load public key
|
|
163
|
-
try:
|
|
164
|
-
public_key = deps['serialization'].load_pem_public_key(
|
|
165
|
-
public_key_pem.encode() if isinstance(public_key_pem, str) else public_key_pem,
|
|
166
|
-
backend=deps['default_backend']()
|
|
167
|
-
)
|
|
168
|
-
except Exception as e:
|
|
169
|
-
raise EncryptionError(f"Failed to load RSA public key: {e}")
|
|
170
|
-
|
|
171
|
-
# Generate random AES key for hybrid encryption
|
|
172
|
-
aes_key = deps['os'].urandom(32) # 256-bit key
|
|
173
|
-
|
|
174
|
-
# Encrypt data with AES-256-GCM
|
|
175
|
-
encrypted_data = _encrypt_aes_gcm(plaintext, aes_key)
|
|
176
|
-
|
|
177
|
-
# Choose RSA padding
|
|
178
|
-
if algorithm == EncryptionAlgorithm.RSA_OAEP:
|
|
179
|
-
rsa_padding = deps['asym_padding'].OAEP(
|
|
180
|
-
mgf=deps['asym_padding'].MGF1(algorithm=deps['hashes'].SHA256()),
|
|
181
|
-
algorithm=deps['hashes'].SHA256(),
|
|
182
|
-
label=None
|
|
183
|
-
)
|
|
184
|
-
else: # RSA_PKCS1V15
|
|
185
|
-
rsa_padding = deps['asym_padding'].PKCS1v15()
|
|
186
|
-
|
|
187
|
-
# Encrypt AES key with RSA
|
|
188
|
-
try:
|
|
189
|
-
encrypted_key = public_key.encrypt(aes_key, rsa_padding)
|
|
190
|
-
except Exception as e:
|
|
191
|
-
raise EncryptionError(f"Failed to encrypt with RSA: {e}")
|
|
192
|
-
|
|
193
|
-
# Return: encrypted_key_length (4 bytes) + encrypted_key + encrypted_data
|
|
194
|
-
import struct
|
|
195
|
-
key_length = struct.pack('>I', len(encrypted_key))
|
|
196
|
-
return key_length + encrypted_key + encrypted_data
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
def _encrypt_sm4_cbc(plaintext: bytes, key: bytes) -> bytes:
|
|
200
|
-
"""
|
|
201
|
-
Encrypt data using SM4-CBC (Chinese national standard).
|
|
202
|
-
|
|
203
|
-
Args:
|
|
204
|
-
plaintext: Data to encrypt
|
|
205
|
-
key: 16-byte encryption key
|
|
206
|
-
|
|
207
|
-
Returns:
|
|
208
|
-
IV (16 bytes) + Ciphertext
|
|
209
|
-
"""
|
|
210
|
-
deps = _import_gmssl_dependencies()
|
|
211
|
-
|
|
212
|
-
# Generate random IV
|
|
213
|
-
iv = deps['os'].urandom(16)
|
|
214
|
-
|
|
215
|
-
# SM4 requires 16-byte key
|
|
216
|
-
if len(key) != 16:
|
|
217
|
-
# Derive 16-byte key using SM3 hash
|
|
218
|
-
key = bytes(deps['sm3'].sm3_hash(list(key)))[:16]
|
|
219
|
-
|
|
220
|
-
# Create SM4 cipher in CBC mode
|
|
221
|
-
cipher = deps['sm4'].CryptSM4()
|
|
222
|
-
cipher.set_key(key, deps['sm4'].SM4_ENCRYPT)
|
|
223
|
-
|
|
224
|
-
# Pad plaintext to 16-byte blocks
|
|
225
|
-
padding_length = 16 - (len(plaintext) % 16)
|
|
226
|
-
padded_plaintext = plaintext + bytes([padding_length] * padding_length)
|
|
227
|
-
|
|
228
|
-
# Encrypt in CBC mode
|
|
229
|
-
ciphertext = b''
|
|
230
|
-
prev_block = iv
|
|
231
|
-
for i in range(0, len(padded_plaintext), 16):
|
|
232
|
-
block = padded_plaintext[i:i+16]
|
|
233
|
-
# XOR with previous ciphertext block (CBC mode)
|
|
234
|
-
xor_block = bytes(a ^ b for a, b in zip(block, prev_block))
|
|
235
|
-
encrypted_block = cipher.crypt_ecb(xor_block)
|
|
236
|
-
ciphertext += encrypted_block
|
|
237
|
-
prev_block = encrypted_block
|
|
238
|
-
|
|
239
|
-
return iv + ciphertext
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
def _encrypt_sm4_gcm(plaintext: bytes, key: bytes) -> bytes:
|
|
243
|
-
"""
|
|
244
|
-
Encrypt data using SM4-GCM (authenticated encryption).
|
|
245
|
-
|
|
246
|
-
Note: gmssl library may not support GCM mode directly.
|
|
247
|
-
This is a placeholder implementation using CBC mode.
|
|
248
|
-
For production, consider using a library with full SM4-GCM support.
|
|
249
|
-
|
|
250
|
-
Args:
|
|
251
|
-
plaintext: Data to encrypt
|
|
252
|
-
key: 16-byte encryption key
|
|
253
|
-
|
|
254
|
-
Returns:
|
|
255
|
-
IV (12 bytes) + Tag (16 bytes) + Ciphertext
|
|
256
|
-
"""
|
|
257
|
-
# For now, use CBC mode as fallback
|
|
258
|
-
# TODO: Implement proper SM4-GCM when library support is available
|
|
259
|
-
deps = _import_gmssl_dependencies()
|
|
260
|
-
|
|
261
|
-
# Generate random IV (12 bytes for GCM)
|
|
262
|
-
iv = deps['os'].urandom(12)
|
|
263
|
-
|
|
264
|
-
# SM4 requires 16-byte key
|
|
265
|
-
if len(key) != 16:
|
|
266
|
-
key = bytes(deps['sm3'].sm3_hash(list(key)))[:16]
|
|
267
|
-
|
|
268
|
-
# Use CBC mode as fallback
|
|
269
|
-
cipher = deps['sm4'].CryptSM4()
|
|
270
|
-
cipher.set_key(key, deps['sm4'].SM4_ENCRYPT)
|
|
271
|
-
|
|
272
|
-
# Pad plaintext
|
|
273
|
-
padding_length = 16 - (len(plaintext) % 16)
|
|
274
|
-
padded_plaintext = plaintext + bytes([padding_length] * padding_length)
|
|
275
|
-
|
|
276
|
-
# Encrypt
|
|
277
|
-
# Extend IV to 16 bytes for CBC mode
|
|
278
|
-
extended_iv = iv + b'\x00' * 4
|
|
279
|
-
ciphertext = b''
|
|
280
|
-
prev_block = extended_iv
|
|
281
|
-
for i in range(0, len(padded_plaintext), 16):
|
|
282
|
-
block = padded_plaintext[i:i+16]
|
|
283
|
-
xor_block = bytes(a ^ b for a, b in zip(block, prev_block))
|
|
284
|
-
encrypted_block = cipher.crypt_ecb(xor_block)
|
|
285
|
-
ciphertext += encrypted_block
|
|
286
|
-
prev_block = encrypted_block
|
|
287
|
-
|
|
288
|
-
# Generate authentication tag (simplified - compute hash of ciphertext)
|
|
289
|
-
tag = bytes(deps['sm3'].sm3_hash(list(iv + ciphertext)))[:16]
|
|
290
|
-
|
|
291
|
-
return iv + tag + ciphertext
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
def _encrypt_sm2(plaintext: bytes, public_key_hex: str) -> bytes:
|
|
295
|
-
"""
|
|
296
|
-
Encrypt data using SM2 (Chinese national standard asymmetric algorithm).
|
|
297
|
-
|
|
298
|
-
Uses hybrid encryption:
|
|
299
|
-
1. Generate random SM4 key
|
|
300
|
-
2. Encrypt data with SM4-CBC
|
|
301
|
-
3. Encrypt SM4 key with SM2
|
|
302
|
-
4. Return: SM2(SM4_key) + SM4-CBC(plaintext)
|
|
303
|
-
|
|
304
|
-
Args:
|
|
305
|
-
plaintext: Data to encrypt
|
|
306
|
-
public_key_hex: SM2 public key in hex format (130 hex chars, starting with '04')
|
|
307
|
-
|
|
308
|
-
Returns:
|
|
309
|
-
Encrypted key length (4 bytes) + Encrypted SM4 key + SM4-CBC encrypted data
|
|
310
|
-
"""
|
|
311
|
-
deps = _import_gmssl_dependencies()
|
|
312
|
-
|
|
313
|
-
# Generate random SM4 key (16 bytes)
|
|
314
|
-
sm4_key = deps['os'].urandom(16)
|
|
315
|
-
|
|
316
|
-
# Encrypt data with SM4-CBC
|
|
317
|
-
encrypted_data = _encrypt_sm4_cbc(plaintext, sm4_key)
|
|
318
|
-
|
|
319
|
-
# Encrypt SM4 key with SM2
|
|
320
|
-
try:
|
|
321
|
-
sm2_cipher = deps['sm2'].CryptSM2(public_key=public_key_hex, private_key=None)
|
|
322
|
-
# SM2 encrypt returns hex string
|
|
323
|
-
encrypted_key_hex = sm2_cipher.encrypt(sm4_key.hex())
|
|
324
|
-
encrypted_key = bytes.fromhex(encrypted_key_hex)
|
|
325
|
-
except Exception as e:
|
|
326
|
-
raise EncryptionError(f"Failed to encrypt with SM2: {e}")
|
|
327
|
-
|
|
328
|
-
# Return: encrypted_key_length (4 bytes) + encrypted_key + encrypted_data
|
|
329
|
-
import struct
|
|
330
|
-
key_length = struct.pack('>I', len(encrypted_key))
|
|
331
|
-
return key_length + encrypted_key + encrypted_data
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
def encrypt_file(
|
|
335
|
-
file_path: Path,
|
|
336
|
-
encryption_key: Union[str, bytes],
|
|
337
|
-
algorithm: EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
|
|
338
|
-
) -> None:
|
|
339
|
-
"""
|
|
340
|
-
Encrypt a file in-place using the specified algorithm.
|
|
341
|
-
|
|
342
|
-
Args:
|
|
343
|
-
file_path: Path to the file to encrypt
|
|
344
|
-
encryption_key: Encryption key (string for symmetric, PEM for asymmetric)
|
|
345
|
-
algorithm: Encryption algorithm to use
|
|
346
|
-
|
|
347
|
-
Raises:
|
|
348
|
-
EncryptionError: If encryption fails
|
|
349
|
-
"""
|
|
350
|
-
deps = _import_crypto_dependencies()
|
|
351
|
-
|
|
352
|
-
try:
|
|
353
|
-
# Read file content
|
|
354
|
-
plaintext = file_path.read_bytes()
|
|
355
|
-
|
|
356
|
-
# Encrypt based on algorithm
|
|
357
|
-
if algorithm == EncryptionAlgorithm.AES_256_CBC:
|
|
358
|
-
# Derive 256-bit key from string
|
|
359
|
-
if isinstance(encryption_key, str):
|
|
360
|
-
key = deps['hashlib'].sha256(encryption_key.encode()).digest()
|
|
361
|
-
else:
|
|
362
|
-
key = encryption_key
|
|
363
|
-
ciphertext = _encrypt_aes_cbc(plaintext, key)
|
|
364
|
-
|
|
365
|
-
elif algorithm == EncryptionAlgorithm.AES_256_GCM:
|
|
366
|
-
# Derive 256-bit key from string
|
|
367
|
-
if isinstance(encryption_key, str):
|
|
368
|
-
key = deps['hashlib'].sha256(encryption_key.encode()).digest()
|
|
369
|
-
else:
|
|
370
|
-
key = encryption_key
|
|
371
|
-
ciphertext = _encrypt_aes_gcm(plaintext, key)
|
|
372
|
-
|
|
373
|
-
elif algorithm in (EncryptionAlgorithm.RSA_OAEP, EncryptionAlgorithm.RSA_PKCS1V15):
|
|
374
|
-
# Use RSA public key
|
|
375
|
-
if isinstance(encryption_key, bytes):
|
|
376
|
-
public_key_pem = encryption_key.decode()
|
|
377
|
-
else:
|
|
378
|
-
public_key_pem = encryption_key
|
|
379
|
-
ciphertext = _encrypt_rsa(plaintext, public_key_pem, algorithm)
|
|
380
|
-
|
|
381
|
-
elif algorithm == EncryptionAlgorithm.SM4_CBC:
|
|
382
|
-
# Derive 16-byte key from string using SM3
|
|
383
|
-
if isinstance(encryption_key, str):
|
|
384
|
-
gm_deps = _import_gmssl_dependencies()
|
|
385
|
-
key = bytes(gm_deps['sm3'].sm3_hash(list(encryption_key.encode())))[:16]
|
|
386
|
-
else:
|
|
387
|
-
key = encryption_key[:16] if len(encryption_key) >= 16 else encryption_key
|
|
388
|
-
ciphertext = _encrypt_sm4_cbc(plaintext, key)
|
|
389
|
-
|
|
390
|
-
elif algorithm == EncryptionAlgorithm.SM4_GCM:
|
|
391
|
-
# Derive 16-byte key from string using SM3
|
|
392
|
-
if isinstance(encryption_key, str):
|
|
393
|
-
gm_deps = _import_gmssl_dependencies()
|
|
394
|
-
key = bytes(gm_deps['sm3'].sm3_hash(list(encryption_key.encode())))[:16]
|
|
395
|
-
else:
|
|
396
|
-
key = encryption_key[:16] if len(encryption_key) >= 16 else encryption_key
|
|
397
|
-
ciphertext = _encrypt_sm4_gcm(plaintext, key)
|
|
398
|
-
|
|
399
|
-
elif algorithm == EncryptionAlgorithm.SM2:
|
|
400
|
-
# Use SM2 public key (hex format)
|
|
401
|
-
if isinstance(encryption_key, bytes):
|
|
402
|
-
public_key_hex = encryption_key.decode()
|
|
403
|
-
else:
|
|
404
|
-
public_key_hex = encryption_key
|
|
405
|
-
ciphertext = _encrypt_sm2(plaintext, public_key_hex)
|
|
406
|
-
|
|
407
|
-
else:
|
|
408
|
-
raise EncryptionError(f"Unsupported encryption algorithm: {algorithm}")
|
|
409
|
-
|
|
410
|
-
# Write encrypted content back to file
|
|
411
|
-
file_path.write_bytes(ciphertext)
|
|
412
|
-
|
|
413
|
-
except EncryptionError:
|
|
414
|
-
raise
|
|
415
|
-
except Exception as e:
|
|
416
|
-
raise EncryptionError(f"Failed to encrypt file {file_path}: {e}")
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
def _decrypt_aes_cbc(ciphertext: bytes, key: bytes) -> bytes:
|
|
420
|
-
"""
|
|
421
|
-
Decrypt data using AES-256-CBC.
|
|
422
|
-
|
|
423
|
-
Args:
|
|
424
|
-
ciphertext: IV (16 bytes) + Ciphertext
|
|
425
|
-
key: 32-byte decryption key
|
|
426
|
-
|
|
427
|
-
Returns:
|
|
428
|
-
Decrypted plaintext
|
|
429
|
-
"""
|
|
430
|
-
deps = _import_crypto_dependencies()
|
|
431
|
-
|
|
432
|
-
if len(ciphertext) < 16:
|
|
433
|
-
raise EncryptionError("Ciphertext too short (missing IV)")
|
|
434
|
-
|
|
435
|
-
# Extract IV and ciphertext
|
|
436
|
-
iv = ciphertext[:16]
|
|
437
|
-
encrypted_data = ciphertext[16:]
|
|
438
|
-
|
|
439
|
-
# Decrypt
|
|
440
|
-
cipher = deps['Cipher'](
|
|
441
|
-
deps['algorithms'].AES(key),
|
|
442
|
-
deps['modes'].CBC(iv),
|
|
443
|
-
backend=deps['default_backend']()
|
|
444
|
-
)
|
|
445
|
-
decryptor = cipher.decryptor()
|
|
446
|
-
padded_plaintext = decryptor.update(encrypted_data) + decryptor.finalize()
|
|
447
|
-
|
|
448
|
-
# Remove padding
|
|
449
|
-
unpadder = deps['padding'].PKCS7(128).unpadder()
|
|
450
|
-
plaintext = unpadder.update(padded_plaintext) + unpadder.finalize()
|
|
451
|
-
|
|
452
|
-
return plaintext
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
def _decrypt_aes_gcm(ciphertext: bytes, key: bytes) -> bytes:
|
|
456
|
-
"""
|
|
457
|
-
Decrypt data using AES-256-GCM.
|
|
458
|
-
|
|
459
|
-
Args:
|
|
460
|
-
ciphertext: IV (12 bytes) + Tag (16 bytes) + Ciphertext
|
|
461
|
-
key: 32-byte decryption key
|
|
462
|
-
|
|
463
|
-
Returns:
|
|
464
|
-
Decrypted plaintext
|
|
465
|
-
"""
|
|
466
|
-
deps = _import_crypto_dependencies()
|
|
467
|
-
|
|
468
|
-
if len(ciphertext) < 28: # 12 (IV) + 16 (tag)
|
|
469
|
-
raise EncryptionError("Ciphertext too short (missing IV or tag)")
|
|
470
|
-
|
|
471
|
-
# Extract IV, tag, and ciphertext
|
|
472
|
-
iv = ciphertext[:12]
|
|
473
|
-
tag = ciphertext[12:28]
|
|
474
|
-
encrypted_data = ciphertext[28:]
|
|
475
|
-
|
|
476
|
-
# Decrypt
|
|
477
|
-
cipher = deps['Cipher'](
|
|
478
|
-
deps['algorithms'].AES(key),
|
|
479
|
-
deps['modes'].GCM(iv, tag),
|
|
480
|
-
backend=deps['default_backend']()
|
|
481
|
-
)
|
|
482
|
-
decryptor = cipher.decryptor()
|
|
483
|
-
plaintext = decryptor.update(encrypted_data) + decryptor.finalize()
|
|
484
|
-
|
|
485
|
-
return plaintext
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
def _decrypt_rsa(ciphertext: bytes, private_key_pem: str, algorithm: EncryptionAlgorithm) -> bytes:
|
|
489
|
-
"""
|
|
490
|
-
Decrypt data using RSA (hybrid encryption).
|
|
491
|
-
|
|
492
|
-
Args:
|
|
493
|
-
ciphertext: Encrypted key length (4 bytes) + Encrypted AES key + AES-GCM encrypted data
|
|
494
|
-
private_key_pem: RSA private key in PEM format
|
|
495
|
-
algorithm: RSA algorithm that was used
|
|
496
|
-
|
|
497
|
-
Returns:
|
|
498
|
-
Decrypted plaintext
|
|
499
|
-
"""
|
|
500
|
-
deps = _import_crypto_dependencies()
|
|
501
|
-
|
|
502
|
-
# Load private key
|
|
503
|
-
try:
|
|
504
|
-
private_key = deps['serialization'].load_pem_private_key(
|
|
505
|
-
private_key_pem.encode() if isinstance(private_key_pem, str) else private_key_pem,
|
|
506
|
-
password=None,
|
|
507
|
-
backend=deps['default_backend']()
|
|
508
|
-
)
|
|
509
|
-
except Exception as e:
|
|
510
|
-
raise EncryptionError(f"Failed to load RSA private key: {e}")
|
|
511
|
-
|
|
512
|
-
# Extract encrypted key length
|
|
513
|
-
import struct
|
|
514
|
-
if len(ciphertext) < 4:
|
|
515
|
-
raise EncryptionError("Ciphertext too short")
|
|
516
|
-
|
|
517
|
-
key_length = struct.unpack('>I', ciphertext[:4])[0]
|
|
518
|
-
|
|
519
|
-
if len(ciphertext) < 4 + key_length:
|
|
520
|
-
raise EncryptionError("Ciphertext corrupted (invalid key length)")
|
|
521
|
-
|
|
522
|
-
# Extract encrypted AES key and encrypted data
|
|
523
|
-
encrypted_key = ciphertext[4:4+key_length]
|
|
524
|
-
encrypted_data = ciphertext[4+key_length:]
|
|
525
|
-
|
|
526
|
-
# Choose RSA padding
|
|
527
|
-
if algorithm == EncryptionAlgorithm.RSA_OAEP:
|
|
528
|
-
rsa_padding = deps['asym_padding'].OAEP(
|
|
529
|
-
mgf=deps['asym_padding'].MGF1(algorithm=deps['hashes'].SHA256()),
|
|
530
|
-
algorithm=deps['hashes'].SHA256(),
|
|
531
|
-
label=None
|
|
532
|
-
)
|
|
533
|
-
else: # RSA_PKCS1V15
|
|
534
|
-
rsa_padding = deps['asym_padding'].PKCS1v15()
|
|
535
|
-
|
|
536
|
-
# Decrypt AES key with RSA
|
|
537
|
-
try:
|
|
538
|
-
aes_key = private_key.decrypt(encrypted_key, rsa_padding)
|
|
539
|
-
except Exception as e:
|
|
540
|
-
raise EncryptionError(f"Failed to decrypt with RSA: {e}")
|
|
541
|
-
|
|
542
|
-
# Decrypt data with AES-256-GCM
|
|
543
|
-
plaintext = _decrypt_aes_gcm(encrypted_data, aes_key)
|
|
544
|
-
|
|
545
|
-
return plaintext
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
def _decrypt_sm4_cbc(ciphertext: bytes, key: bytes) -> bytes:
|
|
549
|
-
"""
|
|
550
|
-
Decrypt data using SM4-CBC.
|
|
551
|
-
|
|
552
|
-
Args:
|
|
553
|
-
ciphertext: IV (16 bytes) + Ciphertext
|
|
554
|
-
key: 16-byte decryption key
|
|
555
|
-
|
|
556
|
-
Returns:
|
|
557
|
-
Decrypted plaintext
|
|
558
|
-
"""
|
|
559
|
-
deps = _import_gmssl_dependencies()
|
|
560
|
-
|
|
561
|
-
if len(ciphertext) < 16:
|
|
562
|
-
raise EncryptionError("Ciphertext too short (missing IV)")
|
|
563
|
-
|
|
564
|
-
# Extract IV and ciphertext
|
|
565
|
-
iv = ciphertext[:16]
|
|
566
|
-
encrypted_data = ciphertext[16:]
|
|
567
|
-
|
|
568
|
-
# SM4 requires 16-byte key
|
|
569
|
-
if len(key) != 16:
|
|
570
|
-
key = bytes(deps['sm3'].sm3_hash(list(key)))[:16]
|
|
571
|
-
|
|
572
|
-
# Create SM4 cipher
|
|
573
|
-
cipher = deps['sm4'].CryptSM4()
|
|
574
|
-
cipher.set_key(key, deps['sm4'].SM4_DECRYPT)
|
|
575
|
-
|
|
576
|
-
# Decrypt in CBC mode
|
|
577
|
-
plaintext = b''
|
|
578
|
-
prev_block = iv
|
|
579
|
-
for i in range(0, len(encrypted_data), 16):
|
|
580
|
-
encrypted_block = encrypted_data[i:i+16]
|
|
581
|
-
decrypted_block = cipher.crypt_ecb(encrypted_block)
|
|
582
|
-
# XOR with previous ciphertext block (CBC mode)
|
|
583
|
-
block = bytes(a ^ b for a, b in zip(decrypted_block, prev_block))
|
|
584
|
-
plaintext += block
|
|
585
|
-
prev_block = encrypted_block
|
|
586
|
-
|
|
587
|
-
# Remove padding
|
|
588
|
-
if plaintext:
|
|
589
|
-
padding_length = plaintext[-1]
|
|
590
|
-
if 1 <= padding_length <= 16:
|
|
591
|
-
plaintext = plaintext[:-padding_length]
|
|
592
|
-
|
|
593
|
-
return plaintext
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
def _decrypt_sm4_gcm(ciphertext: bytes, key: bytes) -> bytes:
|
|
597
|
-
"""
|
|
598
|
-
Decrypt data using SM4-GCM.
|
|
599
|
-
|
|
600
|
-
Note: This is a fallback implementation using CBC mode.
|
|
601
|
-
|
|
602
|
-
Args:
|
|
603
|
-
ciphertext: IV (12 bytes) + Tag (16 bytes) + Ciphertext
|
|
604
|
-
key: 16-byte decryption key
|
|
605
|
-
|
|
606
|
-
Returns:
|
|
607
|
-
Decrypted plaintext
|
|
608
|
-
"""
|
|
609
|
-
deps = _import_gmssl_dependencies()
|
|
610
|
-
|
|
611
|
-
if len(ciphertext) < 28: # 12 (IV) + 16 (tag)
|
|
612
|
-
raise EncryptionError("Ciphertext too short (missing IV or tag)")
|
|
613
|
-
|
|
614
|
-
# Extract IV, tag, and ciphertext
|
|
615
|
-
iv = ciphertext[:12]
|
|
616
|
-
tag = ciphertext[12:28]
|
|
617
|
-
encrypted_data = ciphertext[28:]
|
|
618
|
-
|
|
619
|
-
# Verify tag (simplified - compute hash and compare)
|
|
620
|
-
expected_tag = bytes(deps['sm3'].sm3_hash(list(iv + encrypted_data)))[:16]
|
|
621
|
-
if tag != expected_tag:
|
|
622
|
-
raise EncryptionError("Authentication tag verification failed")
|
|
623
|
-
|
|
624
|
-
# SM4 requires 16-byte key
|
|
625
|
-
if len(key) != 16:
|
|
626
|
-
key = bytes(deps['sm3'].sm3_hash(list(key)))[:16]
|
|
627
|
-
|
|
628
|
-
# Decrypt using CBC mode
|
|
629
|
-
cipher = deps['sm4'].CryptSM4()
|
|
630
|
-
cipher.set_key(key, deps['sm4'].SM4_DECRYPT)
|
|
631
|
-
|
|
632
|
-
# Extend IV to 16 bytes
|
|
633
|
-
extended_iv = iv + b'\x00' * 4
|
|
634
|
-
plaintext = b''
|
|
635
|
-
prev_block = extended_iv
|
|
636
|
-
for i in range(0, len(encrypted_data), 16):
|
|
637
|
-
encrypted_block = encrypted_data[i:i+16]
|
|
638
|
-
decrypted_block = cipher.crypt_ecb(encrypted_block)
|
|
639
|
-
block = bytes(a ^ b for a, b in zip(decrypted_block, prev_block))
|
|
640
|
-
plaintext += block
|
|
641
|
-
prev_block = encrypted_block
|
|
642
|
-
|
|
643
|
-
# Remove padding
|
|
644
|
-
if plaintext:
|
|
645
|
-
padding_length = plaintext[-1]
|
|
646
|
-
if 1 <= padding_length <= 16:
|
|
647
|
-
plaintext = plaintext[:-padding_length]
|
|
648
|
-
|
|
649
|
-
return plaintext
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
def _decrypt_sm2(ciphertext: bytes, private_key_hex: str) -> bytes:
|
|
653
|
-
"""
|
|
654
|
-
Decrypt data using SM2 (hybrid encryption).
|
|
655
|
-
|
|
656
|
-
Args:
|
|
657
|
-
ciphertext: Encrypted key length (4 bytes) + Encrypted SM4 key + SM4-CBC encrypted data
|
|
658
|
-
private_key_hex: SM2 private key in hex format
|
|
659
|
-
|
|
660
|
-
Returns:
|
|
661
|
-
Decrypted plaintext
|
|
662
|
-
"""
|
|
663
|
-
deps = _import_gmssl_dependencies()
|
|
664
|
-
|
|
665
|
-
# Extract encrypted key length
|
|
666
|
-
import struct
|
|
667
|
-
if len(ciphertext) < 4:
|
|
668
|
-
raise EncryptionError("Ciphertext too short")
|
|
669
|
-
|
|
670
|
-
key_length = struct.unpack('>I', ciphertext[:4])[0]
|
|
671
|
-
|
|
672
|
-
if len(ciphertext) < 4 + key_length:
|
|
673
|
-
raise EncryptionError("Ciphertext corrupted (invalid key length)")
|
|
674
|
-
|
|
675
|
-
# Extract encrypted SM4 key and encrypted data
|
|
676
|
-
encrypted_key = ciphertext[4:4+key_length]
|
|
677
|
-
encrypted_data = ciphertext[4+key_length:]
|
|
678
|
-
|
|
679
|
-
# Decrypt SM4 key with SM2
|
|
680
|
-
try:
|
|
681
|
-
sm2_cipher = deps['sm2'].CryptSM2(public_key=None, private_key=private_key_hex)
|
|
682
|
-
# SM2 decrypt expects hex string
|
|
683
|
-
sm4_key_hex = sm2_cipher.decrypt(encrypted_key.hex())
|
|
684
|
-
sm4_key = bytes.fromhex(sm4_key_hex)
|
|
685
|
-
except Exception as e:
|
|
686
|
-
raise EncryptionError(f"Failed to decrypt with SM2: {e}")
|
|
687
|
-
|
|
688
|
-
# Decrypt data with SM4-CBC
|
|
689
|
-
plaintext = _decrypt_sm4_cbc(encrypted_data, sm4_key)
|
|
690
|
-
|
|
691
|
-
return plaintext
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
def decrypt_file(
|
|
695
|
-
file_path: Path,
|
|
696
|
-
decryption_key: Union[str, bytes],
|
|
697
|
-
algorithm: EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
|
|
698
|
-
) -> None:
|
|
699
|
-
"""
|
|
700
|
-
Decrypt a file in-place using the specified algorithm.
|
|
701
|
-
|
|
702
|
-
Args:
|
|
703
|
-
file_path: Path to the file to decrypt
|
|
704
|
-
decryption_key: Decryption key (string for symmetric, PEM for asymmetric)
|
|
705
|
-
algorithm: Encryption algorithm that was used
|
|
706
|
-
|
|
707
|
-
Raises:
|
|
708
|
-
EncryptionError: If decryption fails
|
|
709
|
-
"""
|
|
710
|
-
deps = _import_crypto_dependencies()
|
|
711
|
-
|
|
712
|
-
try:
|
|
713
|
-
# Read encrypted file
|
|
714
|
-
ciphertext = file_path.read_bytes()
|
|
715
|
-
|
|
716
|
-
# Decrypt based on algorithm
|
|
717
|
-
if algorithm == EncryptionAlgorithm.AES_256_CBC:
|
|
718
|
-
# Derive 256-bit key from string
|
|
719
|
-
if isinstance(decryption_key, str):
|
|
720
|
-
key = deps['hashlib'].sha256(decryption_key.encode()).digest()
|
|
721
|
-
else:
|
|
722
|
-
key = decryption_key
|
|
723
|
-
plaintext = _decrypt_aes_cbc(ciphertext, key)
|
|
724
|
-
|
|
725
|
-
elif algorithm == EncryptionAlgorithm.AES_256_GCM:
|
|
726
|
-
# Derive 256-bit key from string
|
|
727
|
-
if isinstance(decryption_key, str):
|
|
728
|
-
key = deps['hashlib'].sha256(decryption_key.encode()).digest()
|
|
729
|
-
else:
|
|
730
|
-
key = decryption_key
|
|
731
|
-
plaintext = _decrypt_aes_gcm(ciphertext, key)
|
|
732
|
-
|
|
733
|
-
elif algorithm in (EncryptionAlgorithm.RSA_OAEP, EncryptionAlgorithm.RSA_PKCS1V15):
|
|
734
|
-
# Use RSA private key
|
|
735
|
-
if isinstance(decryption_key, bytes):
|
|
736
|
-
private_key_pem = decryption_key.decode()
|
|
737
|
-
else:
|
|
738
|
-
private_key_pem = decryption_key
|
|
739
|
-
plaintext = _decrypt_rsa(ciphertext, private_key_pem, algorithm)
|
|
740
|
-
|
|
741
|
-
elif algorithm == EncryptionAlgorithm.SM4_CBC:
|
|
742
|
-
# Derive 16-byte key from string using SM3
|
|
743
|
-
if isinstance(decryption_key, str):
|
|
744
|
-
gm_deps = _import_gmssl_dependencies()
|
|
745
|
-
key = bytes(gm_deps['sm3'].sm3_hash(list(decryption_key.encode())))[:16]
|
|
746
|
-
else:
|
|
747
|
-
key = decryption_key[:16] if len(decryption_key) >= 16 else decryption_key
|
|
748
|
-
plaintext = _decrypt_sm4_cbc(ciphertext, key)
|
|
749
|
-
|
|
750
|
-
elif algorithm == EncryptionAlgorithm.SM4_GCM:
|
|
751
|
-
# Derive 16-byte key from string using SM3
|
|
752
|
-
if isinstance(decryption_key, str):
|
|
753
|
-
gm_deps = _import_gmssl_dependencies()
|
|
754
|
-
key = bytes(gm_deps['sm3'].sm3_hash(list(decryption_key.encode())))[:16]
|
|
755
|
-
else:
|
|
756
|
-
key = decryption_key[:16] if len(decryption_key) >= 16 else decryption_key
|
|
757
|
-
plaintext = _decrypt_sm4_gcm(ciphertext, key)
|
|
758
|
-
|
|
759
|
-
elif algorithm == EncryptionAlgorithm.SM2:
|
|
760
|
-
# Use SM2 private key (hex format)
|
|
761
|
-
if isinstance(decryption_key, bytes):
|
|
762
|
-
private_key_hex = decryption_key.decode()
|
|
763
|
-
else:
|
|
764
|
-
private_key_hex = decryption_key
|
|
765
|
-
plaintext = _decrypt_sm2(ciphertext, private_key_hex)
|
|
766
|
-
|
|
767
|
-
else:
|
|
768
|
-
raise EncryptionError(f"Unsupported decryption algorithm: {algorithm}")
|
|
769
|
-
|
|
770
|
-
# Write decrypted content back to file
|
|
771
|
-
file_path.write_bytes(plaintext)
|
|
772
|
-
|
|
773
|
-
except EncryptionError:
|
|
774
|
-
raise
|
|
775
|
-
except Exception as e:
|
|
776
|
-
raise EncryptionError(f"Failed to decrypt file {file_path}: {e}")
|
|
777
|
-
|