import base64 import os from dotenv import load_dotenv from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.Random import get_random_bytes # 加载环境变量(从 .env 文件读取密钥) load_dotenv() class AESCipher: """AES-CBC 对称加密工具类""" # 从环境变量获取密钥(AES-256 需 32 字节密钥,AES-128 需 16 字节) SECRET_KEY = "jr1vA6tfWMHOYi6UXw67UuO6fdak2rMa".encode("utf-8") # AES 块大小固定为 16 字节 BLOCK_SIZE = 16 @classmethod def _validate_key(cls): """校验密钥长度(AES-256 需 32 字节,AES-128 需 16 字节)""" if len(cls.SECRET_KEY) not in (16, 32): raise ValueError("AES 密钥长度必须为 16 字节(AES-128)或 32 字节(AES-256)") @classmethod def encrypt(cls, data: dict) -> dict: """ 加密函数:将字典类型的 data 加密 返回:{encrypted_data: 加密后Base64字符串, iv: 16字节IV的Base64字符串} """ cls._validate_key() # 1. 生成 16 字节随机 IV(每次加密都生成新 IV,无需保密但需和解密一致) iv = get_random_bytes(cls.BLOCK_SIZE) # 2. 初始化 AES-CBC 加密器 cipher = AES.new(cls.SECRET_KEY, AES.MODE_CBC, iv) # 3. 数据序列化(字典转JSON字符串)→ 编码为字节 → 填充(PKCS7) data_str = str(data) # 若需更严谨,可使用 json.dumps(data, ensure_ascii=False) data_bytes = data_str.encode("utf-8") padded_data = pad(data_bytes, cls.BLOCK_SIZE, style="pkcs7") # 4. 加密 → 转为 Base64 字符串(便于接口传输) encrypted_bytes = cipher.encrypt(padded_data) encrypted_data = base64.b64encode(encrypted_bytes).decode("utf-8") iv_b64 = base64.b64encode(iv).decode("utf-8") return { "encrypted_data": encrypted_data, "iv": iv_b64 # IV 需随密文一起返回,供前端解密 } @classmethod def decrypt(cls, encrypted_data: str, iv_b64: str) -> dict: """ 解密函数:将加密后的 Base64 字符串解密为字典 参数:encrypted_data(加密数据)、iv_b64(加密时的IV) """ cls._validate_key() # 1. 解码 Base64(IV 和 密文) iv = base64.b64decode(iv_b64) encrypted_bytes = base64.b64decode(encrypted_data) # 2. 初始化 AES-CBC 解密器 cipher = AES.new(cls.SECRET_KEY, AES.MODE_CBC, iv) # 3. 解密 → 去除填充 → 解码为字符串 → 转为字典(此处简化,实际可用 json.loads) decrypted_bytes = unpad(cipher.decrypt(encrypted_bytes), cls.BLOCK_SIZE, style="pkcs7") decrypted_str = decrypted_bytes.decode("utf-8") return eval(decrypted_str) # 生产环境建议用 json.loads,避免 eval 安全风险