hi-server/doc/加解密说明文档.md
2026-01-09 00:28:37 -08:00

114 lines
3.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 项目加解密使用说明
本指南介绍了 PPanel Server 项目中使用的加解密机制主要用于设备端Device通信的安全保障。
## 1. 核心算法
项目使用 **AES-256-CBC** 加密算法。
- **填充方式**PKCS7 Padding。
- **数据编码**Base64。
## 2. 密钥Key与初始化向量IV生成逻辑
### 2.1 密钥生成 (Key Generation)
密钥由一个预定义的 `SecuritySecret`(简称 Secret生成
1. 对 Secret 进行 **SHA-256** 哈希。
2. 取哈希结果的前 **32 字节** 作为 AES-256 的密钥。
### 2.2 初始化向量生成 (IV Generation)
IV 是动态生成的,以增强安全性:
1. 客户端或服务端生成一个随机字符串Nonce通常是纳秒级时间戳
2. 对 Nonce 进行 **MD5** 哈希。
3. 将 MD5 结果(十六进制字符串)与 Secret 拼接。
4. 对拼接后的字符串按 2.1 节的方式生成密钥逻辑处理,取结果的前 **16 字节** 作为 IV。
> [!NOTE]
> 在 API 通信中Nonce 字符串通常通过请求参数中的 `time` 字段传递。
## 3. 身份识别与优先顺序
服务端通过 `Login-Type` 来识别是否需要进行加解密逻辑(值为 `device` 时触发)。
### 3.1 识别途径
1. **Token 负载 (JWT Claims)**Token 中包含 `LoginType` (值为 `device`) 和 `DeviceId`
2. **请求头 (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 重新注入到请求上下文中。
- **响应加密**
- 拦截响应 Body。
- 加密 Body 中的 `data` 字段。
- 将响应格式化为:
```json
{
"data": "ENCRYPTED_BASE64_STRING",
"time": "NONCE_STRING"
}
```
## 5. Token 负载详情 (JWT Payload)
当 `Login-Type` 为 `device` 时JWT Token 会包含以下自定义字段:
- `LoginType`: `"device"`
- `DeviceId`: 设备的数据库唯一 ID。
## 6. 代码示例
### Go 语言 (服务端)
参考 [pkg/aes/aes.go](file:///Users/Apple/vpn/ppanel-server/pkg/aes/aes.go)
```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` 库:
```javascript
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) 在每次请求时都是唯一的,以防止重放攻击和频率分析。