crypto/aes + cipher.newgcm 是唯一推荐路径,密钥必须为16/24/32字节,nonce严格12字节且每次加密独立生成并前置拼接,解密时切前12字节;禁止用字符串字面量、时间戳或uid作nonce,生产密钥须base64解码后校验长度,全局复用aead实例以避免gc压力。

crypto/aes + cipher.NewGCM 是唯一可选的加密路径
别找第三方 AES 包,也别封装“更易用”的 wrapper——Go 标准库 crypto/aes 和 cipher.NewGCM 已通过 FIPS、NIST 审计,且底层调用硬件 AES 指令(Intel AES-NI / ARM Crypto Extensions),手写或引入非标实现只会引入填充错误、IV 重用、密钥派生弱等高危漏洞。
关键约束必须硬记:aes.NewCipher 只接受 16/24/32 字节密钥;传入 []byte("mykey")(长度 5)会 panic;传入 base64 解码后长度不是 32 的密钥,cipher.NewGCM 直接返回 error。
- 生产密钥必须从环境变量读取:
os.Getenv("ENCRYPTION_KEY_BASE64")→ base64.StdEncoding.DecodeString() → 校验 len(decoded) == 32 - 开发调试可用
crypto/rand.Read(make([]byte, 32))生成一次性密钥,但禁止提交到 Git - 绝对不要用
sha256.Sum256([]byte(password)).[0:32]当密钥——无 salt、无迭代,形同明文
nonce 必须每次加密独立生成且拼在密文前
GCM 模式下,nonce 不是“随便填的随机数”,它是密码学安全的 IV:相同密钥 + 相同 nonce 加密任意两段明文,攻击者能推断其异同。对手机号、身份证号这类低熵字段,复用 nonce 等于直接暴露数据规律。
正确做法是每次调用加密函数时,用 crypto/rand.Read(nonce[:]) 生成 12 字节 nonce(GCM 最佳长度),再用 aead.Seal(nonce, nonce, plaintext, nil) ——注意第一个参数是 dst,第二个才是 nonce,顺序错会导致密文不可解。
- 数据库字段必须预留足够空间:密文长度 = 明文 UTF-8 字节数 + 12(nonce) + 16(auth tag)
- 存入 DB 前直接
append(nonce, ciphertext...),解密时切前 12 字节即可,别自己解析分隔符 - 严禁用时间戳、自增 ID、用户 UID 当 nonce——它们不具备密码学随机性,且可能重复
框架层集成时避免覆盖原始结构体定义
在 GORM 或 YAML 配置加载场景中,直接改 User.Password string 为加密逻辑会污染业务模型。真正干净的做法是利用 Go 的自定义反序列化钩子,比如 UnmarshalYAML 或 GORM 的 Scanner/Valuer 接口。
以 YAML 配置为例:定义一个 EncryptedString 类型,实现 UnmarshalYAML(*yaml.Node) error,内部调用标准 GCM 解密;业务代码仍用 DBConfig.Password string,但实际存储的是加密后的 base64 字符串。
- GORM 场景下,让
Password字段类型为EncryptedString,而非string - 解密失败时统一返回泛化 error(如
errors.New("failed to decrypt field")),绝不暴露cipher: message authentication failed这类底层错误 - HTTP handler 中加解密大字段(如用户上传的 JSON)必须流式处理:
io.Copy+cipher.Stream,别一次性读全再 Seal
性能瓶颈从来不在 AES-GCM 算法本身
AES-GCM 在现代 CPU 上吞吐可达 1–3 GB/s,远高于 SSD 或千兆网卡带宽。真正拖慢服务的,往往是开发者没意识到的内存和实例开销。
一个典型问题是高频创建 aes.Cipher 实例:每次加密都调 aes.NewCipher(key) → 分配新 block → GC 压力陡增。正确做法是全局复用一个 aead 实例(密钥不变时)。
- 初始化阶段一次构建
aead:block := aes.NewCipher(key); aead := cipher.NewGCM(block) - 小数据块(如单个手机号)反复 Seal 会放大内存分配,应合并批量加密或启用 streaming
- 日志里打印密文?别用
fmt.Sprintf("%x", ciphertext)——触发大量 []byte → string 转换和内存分配 - HTTP handler 同步加解密 >1MB 文件?立刻改用
io.Copy+cipher.StreamWriter,否则 goroutine 阻塞超时
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











