加密需分离敏感字段并用encrypt:"true"标签标记,数据库存BYTEA/BLOB,必须用cipher.AEAD(如aes.GCM),nonce每次随机生成12字节,密钥须合规且不可硬编码,GORM中通过Valuer/Scanner自动加解密,解密失败必须显式判断err。

加密前必须分离敏感字段与非敏感字段
用户提交的结构体里往往混着 Name、Email、Phone、IDCard、PasswordHash,但不是所有字段都需要加密:PasswordHash 本身已是加盐哈希值,再 AES 加密会破坏后续验证逻辑;而 Phone 和 IDCard 属于强敏感字段,必须加密落盘。
实操建议:
- 用结构体标签显式标记,例如:
Phone string `json:"phone" encrypt:"true"` - 避免硬编码字段名——通过反射或代码生成(如
stringer+ 自定义注解)自动识别带encrypt:"true"的字段 - 数据库建表时,加密字段统一用
BYTEA(PostgreSQL)或BLOB(MySQL),别用TEXT存 base64:多一次编解码开销,且容易被误查或截断
必须用 cipher.AEAD 而不是 crypto/cipher.CBC
CBC 模式需要手动处理填充、IV 管理和 MAC 校验,极易出错:复用 IV 会导致密文可预测,漏校验会导致篡改不被发现。Go 标准库的 aes.GCM 把加密、认证、nonce 管理打包成原子操作,是当前唯一推荐的对称加密方式。
关键点:
立即学习“go语言免费学习笔记(深入)”;
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
-
nonce必须每次加密都调用crypto/rand.Read(nonce[:])生成 12 字节随机值(aesgcm.NonceSize()返回值),绝不能复用 - 密文输出格式为
nonce + ciphertext + auth tag,拼接后存入数据库或配置文件 - 别自己封装
Encrypt/Decrypt函数——直接用cipher.AEAD.Seal和cipher.AEAD.Open,它们已内置 AEAD 安全边界
密钥不能硬编码,且长度必须严格合规
常见 panic:crypto/aes: invalid key size 12,因为 AES-256 要求密钥必须是 32 字节,而 []byte("my-key") 长度不对。
实操建议:
- 生产环境从环境变量读原始密钥字符串(如
os.Getenv("ENCRYPTION_KEY")),再用sha256.Sum256哈希后取前 32 字节作为密钥 - 更安全的做法是用
pbkdf2.Key派生,加随机 salt、≥100000 轮迭代 - 密钥绝不能出现在代码、配置文件或 git 历史中;若本地管理,至少用
syscall.Mlock锁住内存页防 swap 泄露
在 GORM 或 HTTP Handler 中注入加解密逻辑
不想每次 db.Create() 前手动调 Encrypt()?也不希望解密后明文长期驻留内存。
实操建议:
- 对 GORM,实现
driver.Valuer和sql.Scanner接口:写入时自动加密,查询时自动解密,透明兼容密钥轮换与版本管理 - 对 HTTP Handler,在绑定请求体后、入库前遍历结构体字段,对带
encrypt:"true"标签的字段调用aesgcm.Seal - 解密后若需更高安全等级,使用完立即调用
bytes.Fill(decrypted, 0)清空明文切片 - 解密失败时务必检查
err != nil,不要依赖返回切片长度——cipher.ErrAuthentication表示密文被篡改、nonce 错、密钥错或数据损坏,静默忽略会导致后续 panic 或乱码

















