全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-21 8 分钟 ✍️ juanwangdev

GORM 敏感数据加密存储

GORM 通过模型钩子 BeforeCreateAfterFind 实现字段级自动加解密,确保敏感数据落盘加密、读取解密。

加密机制概述

字段级加密流程

Go
type User struct {
    ID           uint   `gorm:"primaryKey"`
    Name         string
    EncryptedID  string `gorm:"column:encrypted_id;type:varchar(256)"` // 加密字段
    PlainID      string `gorm:"-:all"` // 明文字段,不映射数据库
}

AES 加密实现

加解密工具函数

Go
import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/base64"
    "io"
)

var aesKey = []byte("32-byte-encryption-key-here!!") // 必须 16/24/32 字节

func Encrypt(plainText string) (string, error) {
    block, err := aes.NewCipher(aesKey)
    if err != nil {
        return "", err
    }
    
    aesGCM, err := cipher.NewGCM(block)
    if err != nil {
        return "", err
    }
    
    nonce := make([]byte, aesGCM.NonceSize())
    if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
        return "", err
    }
    
    cipherText := aesGCM.Seal(nonce, nonce, []byte(plainText), nil)
    return base64.StdEncoding.EncodeToString(cipherText), nil
}

func Decrypt(cipherText string) (string, error) {
    block, err := aes.NewCipher(aesKey)
    if err != nil {
        return "", err
    }
    
    aesGCM, err := cipher.NewGCM(block)
    if err != nil {
        return "", err
    }
    
    data, err := base64.StdEncoding.DecodeString(cipherText)
    if err != nil {
        return "", err
    }
    
    nonceSize := aesGCM.NonceSize()
    nonce, cipherBytes := data[:nonceSize], data[nonceSize:]
    
    plainText, err := aesGCM.Open(nil, nonce, cipherBytes, nil)
    return string(plainText), err
}

模型钩子实现

BeforeCreate 自动加密

Go
func (u *User) BeforeCreate(tx *gorm.DB) error {
    if u.PlainID != "" {
        encrypted, err := Encrypt(u.PlainID)
        if err != nil {
            return err
        }
        u.EncryptedID = encrypted
        u.PlainID = "" // 清空明文
    }
    return nil
}

// 创建时自动加密
user := &User{Name: "张三", PlainID: "110101199001011234"}
db.Create(user)
// 数据库存储:EncryptedID = "base64加密后的密文"

AfterFind 自动解密

Go
func (u *User) AfterFind(tx *gorm.DB) error {
    if u.EncryptedID != "" {
        decrypted, err := Decrypt(u.EncryptedID)
        if err != nil {
            return err
        }
        u.PlainID = decrypted
    }
    return nil
}

// 查询时自动解密
var user User
db.First(&user, 1)
// user.PlainID = "110101199001011234"(已解密)

更新操作加密

BeforeUpdate 钩子

Go
func (u *User) BeforeUpdate(tx *gorm.DB) error {
    if u.PlainID != "" {
        encrypted, err := Encrypt(u.PlainID)
        if err != nil {
            return err
        }
        u.EncryptedID = encrypted
        u.PlainID = ""
    }
    return nil
}

// 更新时自动加密
db.Model(&user).Where("id = ?", 1).Update("plain_id", "new-id-value")

密钥管理

环境变量注入密钥

Go
func LoadEncryptionKey() error {
    key := os.Getenv("AES_ENCRYPTION_KEY")
    if key == "" {
        return errors.New("AES_ENCRYPTION_KEY not set")
    }
    if len(key) != 16 && len(key) != 24 && len(key) != 32 {
        return errors.New("key length must be 16, 24, or 32 bytes")
    }
    aesKey = []byte(key)
    return nil
}

注意: 加密密钥严禁硬编码,必须通过环境变量或密钥管理服务注入。定期轮换密钥需处理历史数据重加密。

加密字段查询

加密后匹配查询

Go
// 查询加密字段需先加密查询条件
func FindByEncryptedID(db *gorm.DB, id string) (*User, error) {
    encrypted, err := Encrypt(id)
    if err != nil {
        return nil, err
    }
    var user User
    err = db.Where("encrypted_id = ?", encrypted).First(&user).Error
    return &user, err
}

注意: 加密字段无法使用范围查询和模糊匹配,适合精确匹配场景。需要模糊查询时应采用其他方案(如脱敏索引)。

要点总结

  • 使用 AES-GCM 算法对敏感字段加密,确保数据落盘安全
  • BeforeCreateBeforeUpdate 钩子负责自动加密
  • AfterFind 钩子负责自动解密,业务无感知使用明文
  • 加密密钥通过环境变量注入,禁止硬编码
  • 加密字段仅支持精确匹配查询,不支持范围或模糊查询

存放路径:D:\git2\jwdev\articles\GORM\专家\安全与数据保护\敏感数据加密存储.md

📝 发现内容有误?点击此处直接编辑

← 上一篇 GORM SQL 注入防护
下一篇 → GORM 数据脱敏处理
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库