Go中敏感信息加密存储唯一稳妥路径是crypto/aes+cipher.NewGCM,因base64仅为编码、md5/sha256为单向摘要,均不满足可逆加密与完整性校验要求。

Go 本身不提供开箱即用的“敏感信息加密存储”能力,crypto/aes + cipher.NewGCM 是唯一稳妥路径,但密钥长度、nonce 生成、错误处理三处极易出错,踩坑后解密失败只返回 cipher.ErrAuthentication 或空切片,几乎无法定位原因。
为什么不能直接用 base64 或 md5 当加密
base64 是编码不是加密,base64.StdEncoding.EncodeToString([]byte("password123")) 输出可逆,日志一打、内存一 dump 就暴露;md5 和 sha256 是单向摘要,无法还原,根本不能用于“存储后读取使用”的场景。真正需要的是可逆的对称加密,且带完整性校验——只有 AES-GCM 同时满足机密性 + 认证性,标准库原生支持,别手搓 CBC+HMAC。
密钥和 nonce 必须严格按规格生成
常见 panic:crypto/aes: invalid key size 12,因为 AES-256 要求密钥必须是 32 字节,不能直接用字符串字面量如 []byte("my-secret-key")(长度不对)。正确做法:
- 生产环境从环境变量读取原始密钥字符串,再用
sha256.Sum256哈希后取前 32 字节 - 或更安全地用
pbkdf2.Key派生,加随机 salt、≥100000 轮迭代 -
nonce必须每次加密都新生成,调用crypto/rand.Read(nonce[:]),长度固定为 12 字节(aesgcm.NonceSize()) - 绝不能用时间戳、自增 ID、用户 ID 等可预测值当
nonce,否则 GCM 安全性归零
字段级加密比整文件/整结构加密更实用
加密整个配置文件或整个 struct 会导致工具链断裂:viper 无法 UnmarshalKey,jq 解析失败,git diff 失去可读性。真实项目中应只加密具体字段值,例如 database.password 或 api.token:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 用
viper.AllSettings()获取顶层map[string]interface{} - 递归遍历键路径,匹配预设敏感键列表(如
["database.password", "redis.auth"]) - 对匹配到的字符串值调用
aesgcm.Open解密,成功后直接覆写内存中的值 - 加密时也只操作原始明文字符串,base64 编码后写回 YAML/TOML 对应字段,其余结构完全不动
解密失败时不要依赖返回值长度判断
aesgcm.Open 认证失败时不会 panic,而是返回 cipher.ErrAuthentication 或 cipher.ErrInvalidLength;若忽略 error 直接用返回的切片,可能拿到乱码甚至部分解密内容——这比报错更危险。必须显式检查 error 是否为 nil:
decrypted, err := aesgcm.Open(nil, nonce, ciphertextWithTag, nil)
if err != nil {
// 不要 log.Printf("decrypt failed: %v", err) —— 可能泄露密钥错误等细节
return "", fmt.Errorf("invalid credentials")
}
另外,密文存储格式必须固定:推荐 [nonce(12)][ciphertext][tag(16)] 拼接后 base64,解密时先 base64 解码,再切片取前 12 字节作 nonce,剩余末尾 16 字节为 tag,中间为密文——这个顺序和长度一旦写死,后续所有环节才可控。

















