3.9 KiB
3.9 KiB
项目加解密使用说明
本指南介绍了 PPanel Server 项目中使用的加解密机制,主要用于设备端(Device)通信的安全保障。
1. 核心算法
项目使用 AES-256-CBC 加密算法。
- 填充方式:PKCS7 Padding。
- 数据编码:Base64。
2. 密钥(Key)与初始化向量(IV)生成逻辑
2.1 密钥生成 (Key Generation)
密钥由一个预定义的 SecuritySecret(简称 Secret)生成:
- 对 Secret 进行 SHA-256 哈希。
- 取哈希结果的前 32 字节 作为 AES-256 的密钥。
2.2 初始化向量生成 (IV Generation)
IV 是动态生成的,以增强安全性:
- 客户端或服务端生成一个随机字符串(Nonce,通常是纳秒级时间戳)。
- 对 Nonce 进行 MD5 哈希。
- 将 MD5 结果(十六进制字符串)与 Secret 拼接。
- 对拼接后的字符串按 2.1 节的方式生成密钥逻辑处理,取结果的前 16 字节 作为 IV。
Note
在 API 通信中,Nonce 字符串通常通过请求参数中的
time字段传递。
3. 身份识别与优先顺序
服务端通过 Login-Type 来识别是否需要进行加解密逻辑(值为 device 时触发)。
3.1 识别途径
- Token 负载 (JWT Claims):Token 中包含
LoginType(值为device) 和DeviceId。 - 请求头 (Header):
Login-Type: device。
3.2 优先顺序与场景
- 已登录场景:服务端优先从 Token 负载中读取
LoginType。如果 Token 合法且包含LoginType: device,则启用加解密。 - 未登录/登录中场景:例如
/v1/auth/login/device接口,由于此时没有有效 Token,服务端会检查 Header 中的Login-Type。
Tip
为了确保一致性,建议在设备端请求中始终携带
Login-Type: device请求头,并在登录后确保存储的 Token 负载中也包含对应信息。
4. 中间件应用 (DeviceMiddleware)
DeviceMiddleware 处理 Login-Type: device 的请求:
- 请求解密:
- 检查 URL 参数或 JSON Body 中的
data(加密数据)和time(Nonce)。 - 使用配置的 Secret 和 Nonce 解密
data。 - 将解密后的 JSON 重新注入到请求上下文中。
- 检查 URL 参数或 JSON Body 中的
- 响应加密:
- 拦截响应 Body。
- 加密 Body 中的
data字段。 - 将响应格式化为:
{ "data": "ENCRYPTED_BASE64_STRING", "time": "NONCE_STRING" }
5. Token 负载详情 (JWT Payload)
当 Login-Type 为 device 时,JWT Token 会包含以下自定义字段:
LoginType:"device"DeviceId: 设备的数据库唯一 ID。
6. 代码示例
Go 语言 (服务端)
import pkgaes "github.com/perfect-panel/server/pkg/aes"
// 加密
encrypt, nonce, err := pkgaes.Encrypt([]byte("plain text"), secret)
// 解密
decrypt, err := pkgaes.Decrypt(cipherText, secret, nonce)
JavaScript (客户端示例)
使用 crypto-js 库:
const CryptoJS = require("crypto-js");
function getIv(nonce, secret) {
const md5Nonce = CryptoJS.MD5(nonce).toString();
const ivStr = md5Nonce + secret;
const key = CryptoJS.SHA256(ivStr);
return CryptoJS.enc.Hex.parse(key.toString().substring(0, 32));
}
function getKey(secret) {
const key = CryptoJS.SHA256(secret);
return CryptoJS.enc.Hex.parse(key.toString().substring(0, 64));
}
// 加密示例
const key = getKey(secret);
const iv = getIv(nonce, secret);
const encrypted = CryptoJS.AES.encrypt("plain text", key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
console.log(encrypted.toString()); // Base64
5. 安全建议
- 请务必在配置文件中修改默认的
SecuritySecret。 - 确保
time(Nonce) 在每次请求时都是唯一的,以防止重放攻击和频率分析。