refactor(cryptio): 重构密钥管理模块并优化加密功能

将密钥管理逻辑从 keys.py 迁移到 utils 模块,实现 KeyManager 单例类
新增 crypto_utils 模块集中管理加密/解密功能
优化混合加密方案实现,提高代码复用性和可维护性
This commit is contained in:
Suzu008
2025-10-12 13:30:29 +08:00
parent b809568a75
commit 2ebf987648
10 changed files with 479 additions and 294 deletions
+27 -42
View File
@@ -3,11 +3,15 @@ import uuid
import json
import base64
from aiohttp import web
from server import PromptServer
from .keys import _get_keys, update_client_public_key
import folder_paths
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization, hashes
from server import PromptServer # pyright: ignore[reportMissingImports]
import folder_paths # pyright: ignore[reportMissingImports]
from .utils import _key_manager
from .utils.crypto_utils import (
encrypt_data_with_public_key,
generate_aes_key_and_iv,
encrypt_with_aes_cbc,
)
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
@@ -29,55 +33,37 @@ async def exchange_keys(request):
# 保存客户端公钥
client_public_key_bytes = client_public_key_pem.encode("utf-8")
update_client_public_key(client_public_key_bytes)
_key_manager.update_client_key(client_public_key_bytes)
# 获取服务端密钥
keys = _get_keys()
server_public_key = keys.get("server_public_key")
server_private_key = keys.get("server_private_key")
# 获取服务端密钥 PEM 格式
server_public_key_pem = _key_manager.server_public_key_pem
server_private_key_pem = _key_manager.server_private_key_pem
if not server_public_key or not server_private_key:
if not server_public_key_pem or not server_private_key_pem:
return web.json_response({"error": "Server keys not found"}, status=500)
# 使用客户端公钥加密服务端密钥
client_public_key = serialization.load_pem_public_key(client_public_key_bytes, backend=default_backend())
client_public_key = serialization.load_pem_public_key(
client_public_key_bytes, backend=default_backend()
)
# 准备要加密的数据(服务端公钥和私钥)
keys_data = json.dumps(
{
"server_public_key": server_public_key.decode("utf-8"),
"server_private_key": server_private_key.decode("utf-8"),
"server_public_key": server_public_key_pem.decode("utf-8"),
"server_private_key": server_private_key_pem.decode("utf-8"),
}
)
# 由于RSA加密有大小限制,使用混合加密方案
# 1. 生成随机AES密钥
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding as sym_padding
import secrets
aes_key = secrets.token_bytes(32) # 256-bit AES key
iv = secrets.token_bytes(16) # 128-bit IV
# 由于RSA加密有大小限制,使用混合加密方案(AES-CBC + RSA-OAEP)
# 1. 生成随机AES密钥和IV
aes_key, iv = generate_aes_key_and_iv()
# 2. 使用AES加密数据
cipher = Cipher(algorithms.AES(aes_key), modes.CBC(iv), backend=default_backend())
encryptor = cipher.encryptor()
# PKCS7 padding
padder = sym_padding.PKCS7(128).padder()
padded_data = padder.update(keys_data.encode("utf-8")) + padder.finalize()
encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
encrypted_data = encrypt_with_aes_cbc(keys_data.encode("utf-8"), aes_key, iv)
# 3. 使用RSA加密AES密钥
encrypted_aes_key = client_public_key.encrypt(
aes_key,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None,
),
)
encrypted_aes_key = encrypt_data_with_public_key(aes_key, client_public_key)
# 4. 返回加密后的数据
return web.json_response(
@@ -99,10 +85,9 @@ async def exchange_keys(request):
@PromptServer.instance.routes.get("/cryptio/public_key")
async def get_public_key(request):
"""兼容旧的公钥获取接口(不加密传输)"""
keys = _get_keys()
pub = keys.get("server_public_key")
if pub:
return web.json_response({"public_key": pub.decode("utf-8")})
pub_pem = _key_manager.server_public_key_pem
if pub_pem:
return web.json_response({"public_key": pub_pem.decode("utf-8")})
else:
return web.json_response({"error": "Public key not found"}, status=404)
+4 -7
View File
@@ -1,4 +1,4 @@
from .keys import _ensure_keys_exist, _get_keys
from .utils import _key_manager
class KeyGenerator:
@@ -7,7 +7,8 @@ class KeyGenerator:
"""
def __init__(self):
_ensure_keys_exist()
# 密钥会在首次访问 _key_manager 时自动生成
pass
@classmethod
def INPUT_TYPES(s):
@@ -18,11 +19,7 @@ class KeyGenerator:
FUNCTION = "generate"
CATEGORY = "CryptIO"
def generate_keys_if_not_exist(self):
"""兼容旧接口:确保密钥存在(现使用 JSON 存储)。"""
_ensure_keys_exist()
def generate(self):
"""返回公钥(使用缓存避免频繁 IO)"""
public_key = _get_keys()["server_public_key"].decode("utf-8")
public_key = _key_manager.server_public_key_pem.decode("utf-8")
return (public_key,)
-134
View File
@@ -1,134 +0,0 @@
import os
import json
import base64
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
# 密钥存储路径(JSON + Base64)
KEY_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "keys")
KEY_JSON_PATH = os.path.join(KEY_DIR, "keys.json")
# 兼容旧版 PEM 文件路径(用于迁移)
SERVER_PUBLIC_KEY_PATH = os.path.join(KEY_DIR, "server_public_key.pem")
SERVER_PRIVATE_KEY_PATH = os.path.join(KEY_DIR, "server_private_key.pem")
CLIENT_PUBLIC_KEY_PATH = os.path.join(KEY_DIR, "client_public_key.pem")
os.makedirs(KEY_DIR, exist_ok=True)
# 内存缓存,避免频繁 IO
_KEY_CACHE = None
_KEY_CACHE_MTIME = None
def _read_file_bytes(path: str) -> bytes:
with open(path, "rb") as f:
return f.read()
def _save_keys_json(server_public_pem: bytes, server_private_pem: bytes, client_public_pem: bytes | None = None):
data = {
"server_public_key": base64.b64encode(server_public_pem).decode("ascii"),
"server_private_key": base64.b64encode(server_private_pem).decode("ascii"),
}
if client_public_pem is not None:
data["client_public_key"] = base64.b64encode(client_public_pem).decode("ascii")
with open(KEY_JSON_PATH, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
def _load_keys_json() -> dict:
with open(KEY_JSON_PATH, "r", encoding="utf-8") as f:
raw = json.load(f)
keys = {
"server_public_key": base64.b64decode(raw["server_public_key"]),
"server_private_key": base64.b64decode(raw["server_private_key"]),
}
if "client_public_key" in raw:
keys["client_public_key"] = base64.b64decode(raw["client_public_key"])
return keys
def _ensure_keys_exist():
"""确保 keys.json 存在;若不存在则生成或从旧文件迁移。"""
if os.path.exists(KEY_JSON_PATH):
return
server_pub = None
server_pri = None
client_pub = None
# 迁移旧 PEM 文件到 JSON
if os.path.exists(SERVER_PUBLIC_KEY_PATH) and os.path.exists(SERVER_PRIVATE_KEY_PATH):
try:
server_pub = _read_file_bytes(SERVER_PUBLIC_KEY_PATH)
server_pri = _read_file_bytes(SERVER_PRIVATE_KEY_PATH)
if os.path.exists(CLIENT_PUBLIC_KEY_PATH):
client_pub = _read_file_bytes(CLIENT_PUBLIC_KEY_PATH)
except Exception:
server_pub = None
server_pri = None
# 如果没有旧文件,生成新的密钥对
if server_pub is None or server_pri is None:
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend())
public_key = private_key.public_key()
server_pri = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
)
server_pub = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
_save_keys_json(server_pub, server_pri, client_pub)
def _get_keys() -> dict:
"""获取密钥(带内存缓存),当文件变化时自动刷新。"""
global _KEY_CACHE, _KEY_CACHE_MTIME
try:
mtime = os.path.getmtime(KEY_JSON_PATH) if os.path.exists(KEY_JSON_PATH) else None
except Exception:
mtime = None
if _KEY_CACHE is not None and _KEY_CACHE_MTIME == mtime:
return _KEY_CACHE
# 初次或文件已更新,重新加载
_ensure_keys_exist()
if os.path.exists(KEY_JSON_PATH):
keys = _load_keys_json()
_KEY_CACHE = keys
try:
_KEY_CACHE_MTIME = os.path.getmtime(KEY_JSON_PATH)
except Exception:
_KEY_CACHE_MTIME = None
return keys
else:
_KEY_CACHE = {}
_KEY_CACHE_MTIME = None
return _KEY_CACHE
def update_client_public_key(client_public_pem: bytes):
"""更新客户端公钥到 keys.json"""
global _KEY_CACHE, _KEY_CACHE_MTIME
# 确保服务端密钥存在
_ensure_keys_exist()
keys = _load_keys_json()
# 更新客户端公钥
_save_keys_json(
keys["server_public_key"],
keys["server_private_key"],
client_public_pem
)
# 清空缓存,强制重新加载
_KEY_CACHE = None
_KEY_CACHE_MTIME = None
+8 -38
View File
@@ -5,12 +5,10 @@ import numpy as np
import torch
from PIL import Image
from PIL.PngImagePlugin import PngInfo
import folder_paths
from .keys import _get_keys
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import folder_paths # pyright: ignore[reportMissingImports]
from .utils import _key_manager
from .utils.crypto_utils import encrypt_data_hybrid
class SaveImageCryptIO:
@@ -168,36 +166,8 @@ def encrypt_data(data: bytes) -> bytes:
"""
使用混合加密方案加密数据(使用客户端公钥加密)
"""
# 1. 生成随机 AES 密钥和 IV
aes_key = os.urandom(32) # 256-bit key
iv = os.urandom(12) # 96-bit IV for GCM
# 2. 使用 AES-GCM 加密数据
cipher = Cipher(algorithms.AES(aes_key), modes.GCM(iv), backend=default_backend())
encryptor = cipher.encryptor()
encrypted_data = encryptor.update(data) + encryptor.finalize()
tag = encryptor.tag # type: ignore # 获取认证标签
# 3. 使用客户端公钥加密 AES 密钥(这样只有客户端能解密)
client_public_pem = _get_keys().get("client_public_key")
if not client_public_pem:
# 使用客户端公钥加密(这样只有客户端能解密)
if not _key_manager.client_public_key:
raise ValueError("Client public key not found. Please upload an image first to exchange keys.")
public_key = serialization.load_pem_public_key(client_public_pem, backend=default_backend()) # type: ignore
encrypted_aes_key = public_key.encrypt( # type: ignore
aes_key,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None,
),
)
# 4. 组合数据:[加密的AES密钥长度(4字节)] + [加密的AES密钥] + [IV长度(4字节)] + [IV] + [加密的数据] + [认证标签]
encrypted_aes_key_length = len(encrypted_aes_key).to_bytes(4, byteorder="big")
iv_length = len(iv).to_bytes(4, byteorder="big")
combined = (
encrypted_aes_key_length
+ encrypted_aes_key
+ iv_length
+ iv
+ encrypted_data
+ tag # GCM认证标签附加在末尾
)
return combined
return encrypt_data_hybrid(data, _key_manager.client_public_key)
+6 -18
View File
@@ -1,8 +1,6 @@
import base64
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.backends import default_backend
from .keys import _get_keys
from .utils import _key_manager
from .utils.crypto_utils import decrypt_data_hybrid
class ServerDecrypt:
@@ -27,24 +25,14 @@ class ServerDecrypt:
CATEGORY = "CryptIO"
def decrypt(self, encrypted_text):
"""使用私钥解密文本(私钥从缓存加载)"""
"""使用私钥解密文本(支持混合加密格式)"""
try:
if encrypted_text.startswith("ENCRYPTED:"):
encrypted_bytes = base64.b64decode(encrypted_text[10:])
private_pem = _get_keys().get("server_private_key")
private_key = serialization.load_pem_private_key(private_pem, password=None, backend=default_backend())
decrypted_bytes = private_key.decrypt(
encrypted_bytes,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None,
),
)
decrypted_text = decrypted_bytes.decode("utf-8")
# 使用混合解密
decrypted_data = decrypt_data_hybrid(encrypted_bytes, _key_manager.server_private_key)
decrypted_text = decrypted_data.decode("utf-8")
return (decrypted_text,)
else:
return (encrypted_text,)
+4 -54
View File
@@ -1,13 +1,10 @@
import os
import base64
import hashlib
import numpy as np
import torch
from PIL import Image, ImageSequence, ImageOps
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.backends import default_backend
from .keys import _get_keys
from .utils import _key_manager
from .utils.crypto_utils import decrypt_data_hybrid
import folder_paths
import node_helpers
@@ -50,55 +47,8 @@ class UploadImageCryptIO:
with open(image_path, "rb") as f:
encrypted_combined = f.read()
# 解析组合数据
offset = 0
# 1. 读取加密的AES密钥长度
encrypted_aes_key_length = int.from_bytes(encrypted_combined[offset : offset + 4], byteorder="big")
offset += 4
# 2. 读取加密的AES密钥
encrypted_aes_key = encrypted_combined[offset : offset + encrypted_aes_key_length]
offset += encrypted_aes_key_length
# 3. 读取IV长度
iv_length = int.from_bytes(encrypted_combined[offset : offset + 4], byteorder="big")
offset += 4
# 4. 读取IV
iv = encrypted_combined[offset : offset + iv_length]
offset += iv_length
# 5. 读取加密的数据(包含认证标签)
encrypted_data_with_tag = encrypted_combined[offset:]
# 6. 使用RSA私钥解密AES密钥
private_pem = _get_keys().get("server_private_key")
private_key = serialization.load_pem_private_key(private_pem, password=None, backend=default_backend()) # type: ignore
aes_key_bytes = private_key.decrypt( # type: ignore
encrypted_aes_key,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None,
),
)
# 7. 使用AES密钥解密图片数据
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
# GCM模式:Web Crypto API将16字节的认证标签附加在密文末尾
# 需要分离出认证标签和密文
tag_length = 16
encrypted_data = encrypted_data_with_tag[:-tag_length]
tag = encrypted_data_with_tag[-tag_length:]
cipher = Cipher(algorithms.AES(aes_key_bytes), modes.GCM(iv, tag), backend=default_backend())
decryptor = cipher.decryptor()
# 解密数据(认证标签已在cipher初始化时提供)
decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
# 使用服务端私钥解密(混合加密)
decrypted_data = decrypt_data_hybrid(encrypted_combined, _key_manager.server_private_key)
# 从字节数据创建图片
from io import BytesIO
+30
View File
@@ -0,0 +1,30 @@
"""
CryptIO Utils Module
提供加密、解密、密钥管理等通用功能
"""
from .crypto_utils import (
encrypt_data_with_public_key,
decrypt_data_with_private_key,
encrypt_data_hybrid,
decrypt_data_hybrid,
generate_aes_key_and_iv,
encrypt_with_aes_cbc,
decrypt_with_aes_cbc,
)
from .key_utils import KeyManager, _key_manager
__all__ = [
# 加密/解密
"encrypt_data_with_public_key",
"decrypt_data_with_private_key",
"encrypt_data_hybrid",
"decrypt_data_hybrid",
"generate_aes_key_and_iv",
"encrypt_with_aes_cbc",
"decrypt_with_aes_cbc",
# 密钥管理
"KeyManager",
"_key_manager",
]
+216
View File
@@ -0,0 +1,216 @@
"""
加密/解密工具模块
提供数据加密和解密的核心功能
"""
import os
import secrets
from typing import Tuple
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
def encrypt_data_with_public_key(data: bytes, public_key) -> bytes:
"""
使用RSA公钥直接加密数据(适用于小数据,最大约190字节)
Args:
data: 要加密的数据
public_key: RSA公钥对象
Returns:
加密后的数据
"""
encrypted = public_key.encrypt(
data,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None,
),
)
return encrypted
def decrypt_data_with_private_key(encrypted_data: bytes, private_key) -> bytes:
"""
使用RSA私钥直接解密数据
Args:
encrypted_data: 加密的数据
private_key: RSA私钥对象
Returns:
解密后的数据
"""
decrypted = private_key.decrypt(
encrypted_data,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None,
),
)
return decrypted
def encrypt_data_hybrid(data: bytes, public_key) -> bytes:
"""
使用混合加密方案加密数据(AES-GCM + RSA-OAEP)
适用于任意大小的数据
数据格式:
[加密的AES密钥长度(4字节)] + [加密的AES密钥] + [IV长度(4字节)] + [IV] + [加密的数据]
Args:
data: 要加密的数据
public_key: RSA公钥对象(用于加密AES密钥)
Returns:
组合的加密数据
"""
# 1. 生成随机 AES 密钥和 IV
aes_key = os.urandom(32) # 256-bit key
iv = os.urandom(12) # 96-bit IV for GCM
# 2. 使用 AES-GCM 加密数据
cipher = Cipher(algorithms.AES(aes_key), modes.GCM(iv), backend=default_backend())
encryptor = cipher.encryptor()
encrypted_data = encryptor.update(data) + encryptor.finalize()
tag = encryptor.tag # 获取认证标签
# 3. 使用RSA公钥加密 AES 密钥
encrypted_aes_key = encrypt_data_with_public_key(aes_key, public_key)
# 4. 组合数据
encrypted_aes_key_length = len(encrypted_aes_key).to_bytes(4, byteorder="big")
iv_length = len(iv).to_bytes(4, byteorder="big")
combined = (
encrypted_aes_key_length
+ encrypted_aes_key
+ iv_length
+ iv
+ encrypted_data
+ tag # GCM认证标签附加在末尾
)
return combined
def decrypt_data_hybrid(encrypted_combined: bytes, private_key) -> bytes:
"""
解密混合加密的数据(AES-GCM + RSA-OAEP)
Args:
encrypted_combined: 组合的加密数据
private_key: RSA私钥对象(用于解密AES密钥)
Returns:
解密后的原始数据
"""
offset = 0
# 1. 读取加密的AES密钥长度
encrypted_aes_key_length = int.from_bytes(
encrypted_combined[offset : offset + 4], byteorder="big"
)
offset += 4
# 2. 读取加密的AES密钥
encrypted_aes_key = encrypted_combined[offset : offset + encrypted_aes_key_length]
offset += encrypted_aes_key_length
# 3. 读取IV长度
iv_length = int.from_bytes(encrypted_combined[offset : offset + 4], byteorder="big")
offset += 4
# 4. 读取IV
iv = encrypted_combined[offset : offset + iv_length]
offset += iv_length
# 5. 读取加密的数据(包含认证标签)
encrypted_data_with_tag = encrypted_combined[offset:]
# 6. 使用RSA私钥解密AES密钥
aes_key = decrypt_data_with_private_key(encrypted_aes_key, private_key)
# 7. 使用AES密钥解密数据
# GCM模式:认证标签附加在密文末尾
tag_length = 16
encrypted_data = encrypted_data_with_tag[:-tag_length]
tag = encrypted_data_with_tag[-tag_length:]
cipher = Cipher(
algorithms.AES(aes_key), modes.GCM(iv, tag), backend=default_backend()
)
decryptor = cipher.decryptor()
decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
return decrypted_data
def generate_aes_key_and_iv() -> Tuple[bytes, bytes]:
"""
生成随机的AES密钥和IV
Returns:
(aes_key, iv) 元组
"""
aes_key = secrets.token_bytes(32) # 256-bit key
iv = secrets.token_bytes(16) # 128-bit IV for CBC
return aes_key, iv
def encrypt_with_aes_cbc(data: bytes, aes_key: bytes, iv: bytes) -> bytes:
"""
使用AES-CBC模式加密数据(带PKCS7填充)
Args:
data: 要加密的数据
aes_key: AES密钥
iv: 初始化向量
Returns:
加密后的数据
"""
from cryptography.hazmat.primitives import padding as sym_padding
# PKCS7 padding
padder = sym_padding.PKCS7(128).padder()
padded_data = padder.update(data) + padder.finalize()
# AES-CBC 加密
cipher = Cipher(algorithms.AES(aes_key), modes.CBC(iv), backend=default_backend())
encryptor = cipher.encryptor()
encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
return encrypted_data
def decrypt_with_aes_cbc(encrypted_data: bytes, aes_key: bytes, iv: bytes) -> bytes:
"""
使用AES-CBC模式解密数据(带PKCS7填充)
Args:
encrypted_data: 加密的数据
aes_key: AES密钥
iv: 初始化向量
Returns:
解密后的数据
"""
from cryptography.hazmat.primitives import padding as sym_padding
# AES-CBC 解密
cipher = Cipher(algorithms.AES(aes_key), modes.CBC(iv), backend=default_backend())
decryptor = cipher.decryptor()
padded_data = decryptor.update(encrypted_data) + decryptor.finalize()
# 移除 PKCS7 padding
unpadder = sym_padding.PKCS7(128).unpadder()
data = unpadder.update(padded_data) + unpadder.finalize()
return data
+183
View File
@@ -0,0 +1,183 @@
"""
密钥工具模块
提供密钥导入、导出、加载、存储管理等功能
"""
import os
import json
import base64
from typing import Optional, cast
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey, RSAPrivateKey
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
class KeyManager:
"""
密钥管理器单例类
负责密钥的加载、缓存、更新和持久化
"""
_instance: Optional["KeyManager"] = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
"""初始化密钥管理器(仅首次调用时执行)"""
if self._initialized:
return
# 密钥存储路径
base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
self._key_dir = os.path.join(base_dir, "keys")
self._key_file = os.path.join(self._key_dir, "keys.json")
os.makedirs(self._key_dir, exist_ok=True)
# 缓存状态
self._cache_mtime: Optional[float] = None
self._server_public_key: Optional[RSAPublicKey] = None
self._server_private_key: Optional[RSAPrivateKey] = None
self._client_public_key: Optional[RSAPublicKey] = None
self._server_public_key_pem: Optional[bytes] = None
self._server_private_key_pem: Optional[bytes] = None
self._client_public_key_pem: Optional[bytes] = None
# 初始化加载
self._load()
self._initialized = True
def _load(self):
"""加载密钥到缓存"""
# 检查文件修改时间
try:
current_mtime = os.path.getmtime(self._key_file) if os.path.exists(self._key_file) else None
except Exception:
current_mtime = None
# 缓存命中
if self._cache_mtime == current_mtime and self._server_private_key is not None:
return
# 确保密钥文件存在
if not os.path.exists(self._key_file):
self._generate_server_keys()
# 从文件加载
with open(self._key_file, "r", encoding="utf-8") as f:
data = json.load(f)
# 解码 PEM 数据
self._server_public_key_pem = base64.b64decode(data["server_public_key"])
self._server_private_key_pem = base64.b64decode(data["server_private_key"])
if "client_public_key" in data:
self._client_public_key_pem = base64.b64decode(data["client_public_key"])
# 加载密钥对象
self._server_public_key = cast(
RSAPublicKey, serialization.load_pem_public_key(self._server_public_key_pem, backend=default_backend())
)
self._server_private_key = cast(
RSAPrivateKey, serialization.load_pem_private_key(self._server_private_key_pem, password=None, backend=default_backend())
)
if self._client_public_key_pem:
self._client_public_key = cast(
RSAPublicKey, serialization.load_pem_public_key(self._client_public_key_pem, backend=default_backend())
)
# 更新缓存时间
self._cache_mtime = current_mtime
def _generate_server_keys(self):
"""生成服务端密钥对"""
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend())
public_key = private_key.public_key()
self._server_private_key_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
)
self._server_public_key_pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
self._save()
def _save(self):
"""保存密钥到文件"""
data: dict[str, str] = {}
if self._server_public_key_pem and self._server_private_key_pem:
data["server_public_key"] = base64.b64encode(self._server_public_key_pem).decode("ascii")
data["server_private_key"] = base64.b64encode(self._server_private_key_pem).decode("ascii")
if self._client_public_key_pem:
data["client_public_key"] = base64.b64encode(self._client_public_key_pem).decode("ascii")
with open(self._key_file, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
def reload(self):
"""重新加载密钥(当外部修改文件时调用)"""
self._cache_mtime = None
self._load()
def update_client_key(self, client_public_pem: bytes):
"""
更新客户端公钥
Args:
client_public_pem: 客户端公钥(PEM格式)
"""
self._client_public_key_pem = client_public_pem
self._save()
self.reload()
# ===== 属性访问器 =====
@property
def server_public_key(self) -> Optional[RSAPublicKey]:
"""服务端公钥对象"""
self._load()
return self._server_public_key
@property
def server_private_key(self) -> Optional[RSAPrivateKey]:
"""服务端私钥对象"""
self._load()
return self._server_private_key
@property
def client_public_key(self) -> Optional[RSAPublicKey]:
"""客户端公钥对象"""
self._load()
return self._client_public_key
@property
def server_public_key_pem(self) -> Optional[bytes]:
"""服务端公钥(PEM格式)"""
self._load()
return self._server_public_key_pem
@property
def server_private_key_pem(self) -> Optional[bytes]:
"""服务端私钥(PEM格式)"""
self._load()
return self._server_private_key_pem
@property
def client_public_key_pem(self) -> Optional[bytes]:
"""客户端公钥(PEM格式)"""
self._load()
return self._client_public_key_pem
# ===== 全局单例实例 =====
_key_manager = KeyManager()
+1 -1
View File
@@ -15,7 +15,7 @@ import { loadEncryptedImageFromFilename } from "./imageLoader.js";
export async function uploadEncryptedImage(file: File): Promise<any> {
console.log("Encrypting and uploading image:", file.name);
// 加密文件(使用服务端公钥,如果文件已经加密则直接读取)
// 加密文件(使用服务端公钥,如果文件已经使用服务端公钥加密则直接读取)
const encryptedData = file?.name?.endsWith(".encrypted")
? new Uint8Array(await file.arrayBuffer())
: await encryptFileWithServerKey(file);