113 lines
3.3 KiB
Python
113 lines
3.3 KiB
Python
"""
|
||
services.py
|
||
业务服务模块:封装授权文件加密解密、核心逻辑。
|
||
|
||
加密方案说明(简单但有效):
|
||
1. 将授权字典序列化为 JSON 字符串
|
||
2. 使用 XOR 异或加密(密钥混合 SN,实现一机一密)
|
||
3. 将加密后的字节进行 Base64 编码,方便传输
|
||
|
||
为什么用 XOR + SN 混合密钥:
|
||
- 轻量级,不依赖额外加密库
|
||
- 绑定设备 SN,即使授权文件被拷贝到其他设备也无法解密
|
||
- 对于 Demo 和内部系统足够安全;生产环境可替换为 AES
|
||
"""
|
||
|
||
import json
|
||
import base64
|
||
from datetime import datetime, timedelta
|
||
from typing import Optional
|
||
|
||
from models import LicenseData
|
||
|
||
|
||
def _derive_key(device_sn: str) -> bytes:
|
||
"""
|
||
根据设备 SN 派生加密密钥。
|
||
将 SN 字符串的每个字符 ASCII 码叠加后,扩展为 32 字节密钥。
|
||
这样每台设备的密钥都不同,实现"一机一密"。
|
||
"""
|
||
seed = sum(ord(c) for c in device_sn) % 256
|
||
# 用 seed 生成 32 字节的密钥序列
|
||
key = bytes((seed + i * 7) % 256 for i in range(32))
|
||
return key
|
||
|
||
|
||
def encrypt_license(license_data: LicenseData) -> str:
|
||
"""
|
||
加密授权文件:
|
||
LicenseData → JSON 字符串 → XOR 加密 → Base64 编码
|
||
|
||
参数:
|
||
license_data: 明文授权数据对象
|
||
|
||
返回:
|
||
base64 编码的加密字符串,可直接写入文件或传给 APP
|
||
"""
|
||
# 1. 转为 JSON 字节
|
||
json_bytes = license_data.model_dump_json().encode("utf-8")
|
||
|
||
# 2. 派生密钥
|
||
key = _derive_key(license_data.device_sn)
|
||
|
||
# 3. XOR 循环加密:密文 = 明文 ^ 密钥(循环使用)
|
||
encrypted = bytearray()
|
||
for i, byte in enumerate(json_bytes):
|
||
encrypted.append(byte ^ key[i % len(key)])
|
||
|
||
# 4. Base64 编码,变成可打印字符串
|
||
return base64.b64encode(encrypted).decode("ascii")
|
||
|
||
|
||
def decrypt_license(encrypted_b64: str, device_sn: str) -> Optional[dict]:
|
||
"""
|
||
解密授权文件:
|
||
Base64 解码 → XOR 解密 → JSON 反序列化为字典
|
||
|
||
参数:
|
||
encrypted_b64: encrypt_license 返回的 Base64 字符串
|
||
device_sn: 设备 SN(用于派生解密密钥,必须与加密时一致)
|
||
|
||
返回:
|
||
解密后的授权字典;如果 SN 不匹配或数据损坏则返回 None
|
||
"""
|
||
try:
|
||
# 1. Base64 解码
|
||
encrypted = base64.b64decode(encrypted_b64)
|
||
|
||
# 2. 派生相同密钥
|
||
key = _derive_key(device_sn)
|
||
|
||
# 3. XOR 解密(XOR 的逆运算还是 XOR)
|
||
decrypted = bytearray()
|
||
for i, byte in enumerate(encrypted):
|
||
decrypted.append(byte ^ key[i % len(key)])
|
||
|
||
# 4. JSON 反序列化
|
||
return json.loads(decrypted.decode("utf-8"))
|
||
except Exception:
|
||
# 解密失败(SN 不匹配、Base64 错误、JSON 损坏等)
|
||
return None
|
||
|
||
|
||
def build_license_data(
|
||
device_sn: str,
|
||
modules: list[str],
|
||
valid_days: int = 365
|
||
) -> LicenseData:
|
||
"""
|
||
构造明文授权数据对象。
|
||
根据当前时间计算有效期截止时间。
|
||
"""
|
||
now = datetime.utcnow()
|
||
valid_until = now + timedelta(days=valid_days)
|
||
|
||
return LicenseData(
|
||
version="1.0",
|
||
device_sn=device_sn,
|
||
generated_at=now.isoformat() + "Z",
|
||
valid_until=valid_until.isoformat() + "Z",
|
||
modules=modules,
|
||
status="active"
|
||
)
|