Skip to content

Commit b2259ae

Browse files
committed
refactor(serve): remove pycryptodome dependency, use openssl/bcrypt.dll
1 parent e125ea4 commit b2259ae

2 files changed

Lines changed: 86 additions & 82 deletions

File tree

assets/imgs/web-dashboard.png

264 KB
Loading

bin/serve.py

Lines changed: 86 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import http.server
66
import json
77
import os
8-
import secrets
8+
import platform
99
import subprocess
1010
import sys
1111
import urllib.request
@@ -14,97 +14,104 @@
1414

1515
SALT = b"oroio"
1616
ITERATIONS = 10000
17+
IS_WINDOWS = platform.system() == 'Windows'
1718

18-
def _ensure_crypto():
19-
"""确保加密库可用,自动安装 pycryptodome"""
20-
try:
21-
from cryptography.hazmat.primitives.ciphers import Cipher
22-
return True
23-
except ImportError:
24-
pass
25-
try:
26-
from Crypto.Cipher import AES
27-
return True
28-
except ImportError:
29-
pass
30-
# 尝试安装 pycryptodome
31-
try:
32-
subprocess.check_call([sys.executable, '-m', 'pip', 'install', '-q', 'pycryptodome'],
33-
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
34-
# 安装后清除导入缓存并重试
35-
import importlib
36-
if 'Crypto' in sys.modules:
37-
del sys.modules['Crypto']
38-
if 'Crypto.Cipher' in sys.modules:
39-
del sys.modules['Crypto.Cipher']
40-
if 'Crypto.Cipher.AES' in sys.modules:
41-
del sys.modules['Crypto.Cipher.AES']
42-
from Crypto.Cipher import AES
43-
return True
44-
except Exception:
45-
return False
46-
47-
def derive_key_iv(salt: bytes) -> tuple:
48-
"""PBKDF2 派生 key 和 iv,与 dk.ps1/dk 兼容"""
19+
def _derive_key_iv(salt: bytes) -> tuple:
20+
"""PBKDF2-SHA256 派生 key(32) 和 iv(16)"""
4921
derived = hashlib.pbkdf2_hmac('sha256', SALT, salt, ITERATIONS, dklen=48)
5022
return derived[:32], derived[32:48]
5123

24+
if IS_WINDOWS:
25+
import ctypes
26+
from ctypes import wintypes
27+
28+
bcrypt = ctypes.windll.bcrypt
29+
BCRYPT_AES_ALGORITHM = "AES"
30+
BCRYPT_CHAIN_MODE_CBC = "ChainingModeCBC"
31+
BCRYPT_CHAINING_MODE = "ChainingMode"
32+
33+
class AESCipher:
34+
def __init__(self, key: bytes, iv: bytes):
35+
self.hAlg = ctypes.c_void_p()
36+
self.hKey = ctypes.c_void_p()
37+
self.iv = (ctypes.c_ubyte * len(iv))(*iv)
38+
bcrypt.BCryptOpenAlgorithmProvider(ctypes.byref(self.hAlg), BCRYPT_AES_ALGORITHM, None, 0)
39+
mode = BCRYPT_CHAIN_MODE_CBC.encode('utf-16-le') + b'\x00\x00'
40+
bcrypt.BCryptSetProperty(self.hAlg, BCRYPT_CHAINING_MODE, mode, len(mode), 0)
41+
bcrypt.BCryptGenerateSymmetricKey(self.hAlg, ctypes.byref(self.hKey), None, 0, key, len(key), 0)
42+
43+
def decrypt(self, ciphertext: bytes) -> bytes:
44+
out_len = wintypes.ULONG()
45+
iv_copy = (ctypes.c_ubyte * len(self.iv))(*self.iv)
46+
bcrypt.BCryptDecrypt(self.hKey, ciphertext, len(ciphertext), None, iv_copy, len(iv_copy), None, 0, ctypes.byref(out_len), 1)
47+
out_buf = (ctypes.c_ubyte * out_len.value)()
48+
iv_copy = (ctypes.c_ubyte * len(self.iv))(*self.iv)
49+
bcrypt.BCryptDecrypt(self.hKey, ciphertext, len(ciphertext), None, iv_copy, len(iv_copy), out_buf, out_len.value, ctypes.byref(out_len), 1)
50+
return bytes(out_buf[:out_len.value])
51+
52+
def encrypt(self, plaintext: bytes) -> bytes:
53+
pad_len = 16 - (len(plaintext) % 16)
54+
plaintext = plaintext + bytes([pad_len] * pad_len)
55+
out_len = wintypes.ULONG()
56+
iv_copy = (ctypes.c_ubyte * len(self.iv))(*self.iv)
57+
bcrypt.BCryptEncrypt(self.hKey, plaintext, len(plaintext), None, iv_copy, len(iv_copy), None, 0, ctypes.byref(out_len), 0)
58+
out_buf = (ctypes.c_ubyte * out_len.value)()
59+
iv_copy = (ctypes.c_ubyte * len(self.iv))(*self.iv)
60+
bcrypt.BCryptEncrypt(self.hKey, plaintext, len(plaintext), None, iv_copy, len(iv_copy), out_buf, out_len.value, ctypes.byref(out_len), 0)
61+
return bytes(out_buf[:out_len.value])
62+
63+
def __del__(self):
64+
if self.hKey: bcrypt.BCryptDestroyKey(self.hKey)
65+
if self.hAlg: bcrypt.BCryptCloseAlgorithmProvider(self.hAlg, 0)
66+
5267
def decrypt_keys(keys_file: str) -> list:
5368
"""解密 keys.enc 文件,返回 key 列表"""
5469
if not os.path.isfile(keys_file):
5570
return []
56-
with open(keys_file, 'rb') as f:
57-
data = f.read()
58-
if len(data) < 17:
59-
return []
60-
if data[:8] != b'Salted__':
61-
return []
62-
salt = data[8:16]
63-
ciphertext = data[16:]
64-
key, iv = derive_key_iv(salt)
6571
try:
66-
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
67-
from cryptography.hazmat.primitives import padding
68-
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
69-
decryptor = cipher.decryptor()
70-
padded = decryptor.update(ciphertext) + decryptor.finalize()
71-
unpadder = padding.PKCS7(128).unpadder()
72-
plaintext = unpadder.update(padded) + unpadder.finalize()
73-
except ImportError:
74-
# fallback: PyCryptodome
75-
from Crypto.Cipher import AES
76-
from Crypto.Util.Padding import unpad
77-
cipher = AES.new(key, AES.MODE_CBC, iv)
78-
plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)
79-
text = plaintext.decode('utf-8')
80-
keys = []
81-
for line in text.split('\n'):
82-
line = line.strip()
83-
if line:
84-
keys.append(line.split('\t')[0])
85-
return keys
72+
if IS_WINDOWS:
73+
with open(keys_file, 'rb') as f:
74+
data = f.read()
75+
if len(data) < 17 or data[:8] != b'Salted__':
76+
return []
77+
salt, ciphertext = data[8:16], data[16:]
78+
key, iv = _derive_key_iv(salt)
79+
cipher = AESCipher(key, iv)
80+
text = cipher.decrypt(ciphertext).decode('utf-8')
81+
else:
82+
result = subprocess.run(
83+
['openssl', 'enc', '-d', '-aes-256-cbc', '-pbkdf2', '-in', keys_file, '-pass', f'pass:{SALT.decode()}'],
84+
capture_output=True
85+
)
86+
if result.returncode != 0:
87+
return []
88+
text = result.stdout.decode('utf-8')
89+
keys = []
90+
for line in text.split('\n'):
91+
line = line.strip()
92+
if line:
93+
keys.append(line.split('\t')[0])
94+
return keys
95+
except Exception:
96+
return []
8697

8798
def encrypt_keys(keys: list, keys_file: str):
8899
"""加密 key 列表并写入文件"""
89-
salt = secrets.token_bytes(8)
90-
key, iv = derive_key_iv(salt)
91100
text = '\n'.join(f"{k}\t" for k in keys)
92-
plaintext = text.encode('utf-8')
93-
try:
94-
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
95-
from cryptography.hazmat.primitives import padding
96-
padder = padding.PKCS7(128).padder()
97-
padded = padder.update(plaintext) + padder.finalize()
98-
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
99-
encryptor = cipher.encryptor()
100-
ciphertext = encryptor.update(padded) + encryptor.finalize()
101-
except ImportError:
102-
from Crypto.Cipher import AES
103-
from Crypto.Util.Padding import pad
104-
cipher = AES.new(key, AES.MODE_CBC, iv)
105-
ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))
106-
with open(keys_file, 'wb') as f:
107-
f.write(b'Salted__' + salt + ciphertext)
101+
if IS_WINDOWS:
102+
import secrets
103+
salt = secrets.token_bytes(8)
104+
key, iv = _derive_key_iv(salt)
105+
cipher = AESCipher(key, iv)
106+
ciphertext = cipher.encrypt(text.encode('utf-8'))
107+
with open(keys_file, 'wb') as f:
108+
f.write(b'Salted__' + salt + ciphertext)
109+
else:
110+
subprocess.run(
111+
['openssl', 'enc', '-aes-256-cbc', '-pbkdf2', '-salt', '-out', keys_file, '-pass', f'pass:{SALT.decode()}'],
112+
input=text.encode('utf-8'),
113+
check=True
114+
)
108115

109116
API_URL = 'https://app.factory.ai/api/organization/members/chat-usage'
110117
API_TIMEOUT = 4
@@ -619,9 +626,6 @@ def log_message(self, format, *args):
619626
pass
620627

621628
def run(port, web_dir, oroio_dir, dk_path):
622-
if not _ensure_crypto():
623-
print("错误: 无法加载加密库,请手动安装: pip install pycryptodome", file=sys.stderr)
624-
sys.exit(1)
625629
os.chdir(web_dir)
626630

627631
handler = lambda *args, **kwargs: OroioHandler(

0 commit comments

Comments
 (0)