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

3.9 KiB
Raw Permalink Blame History

项目加解密使用说明

本指南介绍了 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(加密数据)和 timeNonce
    • 使用配置的 Secret 和 Nonce 解密 data
    • 将解密后的 JSON 重新注入到请求上下文中。
  • 响应加密
    • 拦截响应 Body。
    • 加密 Body 中的 data 字段。
    • 将响应格式化为:
      {
        "data": "ENCRYPTED_BASE64_STRING",
        "time": "NONCE_STRING"
      }
      

5. Token 负载详情 (JWT Payload)

Login-TypedeviceJWT Token 会包含以下自定义字段:

  • LoginType: "device"
  • DeviceId: 设备的数据库唯一 ID。

6. 代码示例

Go 语言 (服务端)

参考 pkg/aes/aes.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) 在每次请求时都是唯一的,以防止重放攻击和频率分析。