GoWind 开源生态GoWind 开源生态
首页
框架
GoWind Admin
GoWind CMS
GoWind IM
GoWind UBA
GoWind IoT
GoWind Toolkit
GoWind Quant
GitHub
首页
框架
GoWind Admin
GoWind CMS
GoWind IM
GoWind UBA
GoWind IoT
GoWind Toolkit
GoWind Quant
GitHub
  • 介绍

    • GoWind Admin 产品介绍
    • GoWind Admin 安装指南
  • 后端文档

    • 后端架构总览
    • 后端核心模块详解
    • 后端 API 与 Protobuf 定义
    • 后端配置与部署
    • 后端扩展开发
  • 前端文档

    • 前端架构总览
    • 前端核心功能详解
  • 二开教程

    • 后端新增业务模块实战教程
    • 前端新增业务页面实战教程
    • 前后端联调完整实战教程
  • 高级教程

    • Lua 脚本扩展实战教程
    • 权限系统深度解析教程
    • 多租户架构实战教程
    • 任务调度与异步处理教程
    • 文件上传与对象存储教程
    • 事件总线与解耦架构教程
    • 前端主题定制与国际化教程
    • 性能优化与监控教程
    • SSE 实时推送
    • 登录策略与安全加固
    • 加密工具实战

加密工具实战

GoWind Admin 提供了 pkg/crypto 加密工具包,基于 AES-256-GCM 算法,用于敏感数据加密存储、任务配置加密等场景。本章详细介绍加密工具的设计、使用方法和二开实战。

一、加密工具架构

1.1 核心特性

特性说明
加密算法AES-256-GCM(认证加密)
密钥派生SHA-256(任意长度输入 → 32 字节密钥)
编码格式Base64 + enc: 前缀
向后兼容自动识别未加密数据,透明处理
全局实例单例模式,支持启用/禁用开关
Payload 加密支持整块 Map 数据加密存储

1.2 包结构

pkg/crypto/
├── aes_gcm.go          # AES-256-GCM 加密/解密核心实现
├── encryptor.go        # 全局加密器实例管理
├── payload.go          # Map Payload 加密/解密(用于任务配置等)
├── aes_gcm_test.go     # 单元测试
├── payload_test.go     # Payload 测试
├── example_test.go     # 使用示例
└── README.md           # 包说明文档

1.3 适用场景

  • 数据库中敏感字段加密存储(手机号、身份证号等)
  • Asynq 任务配置加密(数据库密码、API Key 等)
  • 任何需要在服务端安全存储的敏感配置

二、核心 API

2.1 Encryptor — 加密器

Encryptor 是加密工具的核心结构体:

type Encryptor struct {
    key []byte
}

创建加密器:

// 使用密钥字符串创建加密器
// 内部通过 SHA-256 将任意长度密钥转换为 32 字节
encryptor, err := crypto.NewEncryptor("my-secret-key")
if err != nil {
    log.Fatal(err)
}

加密:

// 加密明文,返回带 "enc:" 前缀的 Base64 字符串
encrypted, err := encryptor.Encrypt("敏感数据")
// 结果示例: "enc:YWJjZGVmZ2hpamtsbW5vcA=="

解密:

// 解密密文,自动识别 "enc:" 前缀
decrypted, err := encryptor.Decrypt(encrypted)
// 结果: "敏感数据"

判断是否已加密:

crypto.IsEncrypted("enc:abc123")      // true
crypto.IsEncrypted("plain text")      // false

2.2 全局加密器

encryptor.go 提供全局单例加密器,适合在应用启动时初始化:

// 初始化全局加密器(通常在 main 或 wire 中调用)
err := crypto.InitGlobalEncryptor("production-secret-key", true)
// 参数二: enabled — false 时创建无操作加密器(加密/解密直接返回原文)

// 使用全局函数(无需持有 Encryptor 实例)
encrypted, err := crypto.EncryptIfNeeded("敏感数据")
decrypted, err := crypto.DecryptIfNeeded(encrypted)

启用/禁用开关:

// 禁用加密(开发环境或不需要加密的场景)
crypto.InitGlobalEncryptor("", false)

// 此时 EncryptIfNeeded 直接返回原文
encrypted, _ := crypto.EncryptIfNeeded("hello")  // "hello"(未加密)

2.3 Payload 加密

payload.go 提供针对 map[string]interface{} 的整体加密,常用于 Asynq 任务配置的安全存储:

// 任务配置(包含敏感信息)
taskConfig := map[string]interface{}{
    "task_id":   "task-001",
    "task_type": "email_sync",
    "host":      "imap.gmail.com",
    "port":      993,
    "username":  "user@example.com",
    "password":  "super-secret-password",
}

// 加密整个 Payload
encryptedPayload, err := crypto.EncryptPayload(taskConfig)
// 结果:
// {
//   "_encrypted_config": "enc:...",     // 加密后的完整 JSON
//   "_is_encrypted": true,
//   "task_id": "task-001",              // 保留路由字段
//   "task_type": "email_sync"           // 保留路由字段
// }

// 解密 Payload
decryptedPayload, err := crypto.DecryptPayload(encryptedPayload)
// 恢复为原始 map

Payload 加密策略:

  • task_id 和 task_type 不会被加密(用于 Asynq 路由和调度)
  • 其余所有字段整体加密为一个 _encrypted_config
  • _is_encrypted 标记用于兼容未加密的旧数据

2.4 工具函数

// 强制加密/解密(出错时 panic,适合测试)
encrypted := encryptor.MustEncrypt("test data")
decrypted := encryptor.MustDecrypt(encrypted)

// 强制 Payload 加密/解密
encryptedPayload := crypto.MustEncryptPayload(payload)
decryptedPayload := crypto.MustDecryptPayload(encryptedPayload)

// 检查 Payload 是否加密
isEncrypted := crypto.HasEncryptedPayload(payload)

三、加密流程详解

3.1 加密流程

详细步骤:

  1. 密钥派生:使用 SHA-256 将任意长度密钥转为 32 字节
  2. 创建 AES Block:使用 32 字节密钥创建 AES-256 cipher block
  3. 创建 GCM:基于 cipher block 创建 GCM 模式
  4. 生成 Nonce:使用 crypto/rand 生成随机 12 字节 Nonce
  5. 加密:gcm.Seal(nonce, nonce, plaintext, nil) — 输出格式为 Nonce || Ciphertext || AuthTag
  6. 编码:Base64 编码 + enc: 前缀

3.2 解密流程

func (e *Encryptor) Decrypt(ciphertext string) (string, error) {
    // 1. 空字符串直接返回
    if ciphertext == "" { return "", nil }

    // 2. 检查 "enc:" 前缀(向后兼容)
    if !strings.HasPrefix(ciphertext, EncryptedPrefix) {
        return ciphertext, nil  // 未加密数据直接返回
    }

    // 3. 去除前缀,Base64 解码
    data, _ := base64.StdEncoding.DecodeString(encoded)

    // 4. 分离 Nonce 和密文
    nonce, cipherData := data[:nonceSize], data[nonceSize:]

    // 5. GCM 解密(同时验证认证标签)
    plaintext, _ := gcm.Open(nil, nonce, cipherData, nil)

    return string(plaintext), nil
}

四、二开实战

4.1 场景一:敏感字段加密存储

需求:用户表中手机号字段需要加密存储。

步骤一:初始化加密器

在应用启动时初始化全局加密器(cmd/server/wire.go 或 main.go):

func main() {
    // 从配置读取密钥
    encryptKey := cfg.Encryption.Key      // 例如从 server.yaml 中读取
    encryptEnabled := cfg.Encryption.Enabled

    // 初始化全局加密器
    if err := crypto.InitGlobalEncryptor(encryptKey, encryptEnabled); err != nil {
        log.Fatalf("初始化加密器失败: %v", err)
    }

    // 启动应用...
}

步骤二:在 Service 层加密/解密

func (s *UserService) CreateUser(ctx context.Context, req *identityV1.CreateUserRequest) (*identityV1.User, error) {
    // 加密手机号
    if req.Data.Phone != nil {
        encrypted, err := crypto.EncryptIfNeeded(req.Data.GetPhone())
        if err != nil {
            return nil, err
        }
        req.Data.Phone = trans.Ptr(encrypted)
    }
    return s.userRepo.Create(ctx, req)
}

func (s *UserService) GetUser(ctx context.Context, req *identityV1.GetUserRequest) (*identityV1.User, error) {
    user, err := s.userRepo.Get(ctx, req)
    if err != nil {
        return nil, err
    }

    // 解密手机号(如果已加密)
    if user.Phone != nil && crypto.IsEncrypted(user.GetPhone()) {
        decrypted, err := crypto.DecryptIfNeeded(user.GetPhone())
        if err != nil {
            return nil, err
        }
        user.Phone = trans.Ptr(decrypted)
    }
    return user, nil
}

步骤三:脱敏展示

// 脱敏函数:138****8000
func maskPhone(phone string) string {
    if len(phone) < 7 {
        return "****"
    }
    return phone[:3] + "****" + phone[len(phone)-4:]
}

// 在 API 返回时脱敏
user.Phone = trans.Ptr(maskPhone(user.GetPhone()))

4.2 场景二:任务配置加密

需求:Asynq 任务中包含数据库连接密码,需要在 Redis 中加密存储。

写入时加密:

func (s *TaskService) CreateEmailSyncTask(ctx context.Context, req *CreateEmailSyncRequest) error {
    taskConfig := map[string]interface{}{
        "task_id":   id.NewGUIDv4(false),
        "task_type": "email_sync",
        "host":      req.Host,
        "port":      req.Port,
        "username":  req.Username,
        "password":  req.Password,    // 敏感信息
        "tls":       req.TLS,
    }

    // 加密 Payload
    encryptedConfig, err := crypto.EncryptPayload(taskConfig)
    if err != nil {
        return err
    }

    // 投递到 Asynq 队列
    payload, _ := json.Marshal(encryptedConfig)
    task := asynq.NewTask("email_sync", payload)
    _, err = s.asynqClient.Enqueue(task)
    return err
}

读取时解密:

func HandleEmailSyncTask(ctx context.Context, t *asynq.Task) error {
    var encryptedPayload map[string]interface{}
    json.Unmarshal(t.Payload(), &encryptedPayload)

    // 解密 Payload
    config, err := crypto.DecryptPayload(encryptedPayload)
    if err != nil {
        return fmt.Errorf("解密任务配置失败: %w", err)
    }

    // 使用解密后的配置
    host := config["host"].(string)
    password := config["password"].(string)
    // ... 执行邮件同步

    return nil
}

4.3 场景三:Lua 脚本中使用加密

在 Lua 脚本引擎中,可以通过预注册 Go 函数的方式使用加密能力:

// 注册加密函数到 Lua VM
func registerCryptoFunctions(L *lua.LState) {
    // 加密函数
    L.SetGlobal("encrypt", L.NewFunction(func(L *lua.LState) int {
        plaintext := L.CheckString(1)
        encrypted, err := crypto.EncryptIfNeeded(plaintext)
        if err != nil {
            L.RaiseError(err.Error())
        }
        L.Push(lua.LString(encrypted))
        return 1
    }))

    // 解密函数
    L.SetGlobal("decrypt", L.NewFunction(func(L *lua.LState) int {
        ciphertext := L.CheckString(1)
        decrypted, err := crypto.DecryptIfNeeded(ciphertext)
        if err != nil {
            L.RaiseError(err.Error())
        }
        L.Push(lua.LString(decrypted))
        return 1
    }))
}

Lua 脚本中使用:

local encrypted = encrypt("敏感数据")
logger.info("加密结果: %s", encrypted)

local decrypted = decrypt(encrypted)
logger.info("解密结果: %s", decrypted)

五、向后兼容设计

加密工具的设计充分考虑了向后兼容性:

5.1 数据格式识别

通过 enc: 前缀区分加密数据和未加密数据:

func IsEncrypted(data string) bool {
    return strings.HasPrefix(data, EncryptedPrefix)
}

5.2 透明解密

Decrypt 方法对未加密数据直接返回原文:

func (e *Encryptor) Decrypt(ciphertext string) (string, error) {
    if !strings.HasPrefix(ciphertext, EncryptedPrefix) {
        return ciphertext, nil  // 未加密数据,直接返回
    }
    // ... 正常解密流程
}

5.3 可切换开关

通过 InitGlobalEncryptor(key, enabled) 控制是否启用加密:

// 开发环境:禁用加密
crypto.InitGlobalEncryptor("", false)
// EncryptIfNeeded → 返回原文
// DecryptIfNeeded → 返回原文

// 生产环境:启用加密
crypto.InitGlobalEncryptor("strong-key", true)
// EncryptIfNeeded → 返回密文
// DecryptIfNeeded → 返回明文

这意味着:

  • 已有数据库中的未加密数据不需要迁移
  • 可以随时启用加密,新旧数据可以共存
  • 开发环境可以禁用加密方便调试

六、安全建议

6.1 密钥管理

  • 密钥长度至少 32 字节
  • 不要将密钥硬编码在代码中,使用环境变量或密钥管理服务
  • 不同环境(开发/测试/生产)使用不同密钥
  • 定期轮换密钥(注意:轮换后旧数据需要重新加密)

6.2 AES-256-GCM 优势

特性说明
认证加密同时提供加密和完整性验证
防篡改任何对密文的修改都会在解密时被检测到
无需 IV 管理GCM 内部管理计数器
性能硬件加速支持(AES-NI),性能优秀

6.3 生产环境清单

  • [ ] 修改默认加密密钥
  • [ ] 密钥通过环境变量注入,不写入配置文件
  • [ ] 启用加密(enabled: true)
  • [ ] 数据库连接使用 SSL/TLS
  • [ ] Redis 连接使用密码认证
  • [ ] 定期备份加密密钥

相关文档:

  • 后端扩展
  • 任务调度
  • Lua 脚本扩展
  • 登录策略与安全
Edit this page
Last Updated:: 6/5/26, 12:13 PM
Contributors: Bobo
Prev
登录策略与安全加固