crypto/cipher.newgcm要求密钥严格为16/24/32字节,nonce必须为12字节且唯一,encrypt/decrypt的dst需预留overhead空间,aad若使用则加解密必须完全一致。

crypto/cipher.NewGCM 要求密钥长度必须是 16、24 或 32 字节
Go 的 crypto/cipher.NewGCM 不接受任意长度密钥,它底层调用 cipher.NewAES,而 AES 只支持 AES-128(16 字节)、AES-192(24 字节)、AES-256(32 字节)。传入 17 字节密钥会直接 panic:crypto/aes: invalid key size 17。
常见错误是直接拿字符串当密钥(比如 "my-secret-key"),但它的字节数不匹配。正确做法是:用 sha256.Sum256 或 hmac.New 派生固定长度密钥,或明确用 []byte 构造 16/24/32 字节密钥:
// ✅ 正确:显式构造 32 字节密钥
key := make([]byte, 32)
rand.Read(key) // 或从安全来源填充
<p>// ❌ 错误:字符串长度不可控
key := []byte("short") // 仅 5 字节 → panic</p>
nonce 长度不能随便设,必须与 GCM 实现兼容
crypto/cipher.NewGCM 默认期望 nonce 长度为 12 字节(96 位),这是最常用且最安全的配置。虽然 GCM 理论上支持 1–16 字节 nonce,但 Go 标准库只允许 12 字节;传入其他长度会返回 gcm: invalid nonce length 错误。
- 务必用
make([]byte, 12)分配 nonce,别用 8 或 16 - 每次加密必须用**唯一** nonce,重复使用会导致密文被破解
- 不要把 nonce 当作“盐”反复复用——它是一次性的,建议用
rand.Read(nonce)生成 - nonce 本身不保密,通常和密文一起传输(如前置在密文前)
Encrypt 和 Decrypt 的参数顺序容易搞反
(*cipher.GCM).Encrypt 和 (*cipher.GCM).Decrypt 的第一个参数都是用于存放结果的切片(dst),不是源数据。这个 dst 必须预留足够空间:对 Encrypt 来说,长度至少是明文字节数 + gcm.Overhead()(通常是 16);对 Decrypt 则至少等于密文字节数减去 Overhead。
典型错误写法:
// ❌ 错误:dst 太小,或把 src 当第一个参数 dst := make([]byte, len(plaintext)) dst = gcm.Encrypt(dst, nonce, plaintext, nil) // panic: dst too small <p>// ✅ 正确:dst 预留 overhead 空间 dst := make([]byte, len(plaintext)+gcm.Overhead()) dst = gcm.Encrypt(dst, nonce, plaintext, nil) // 第二个参数是 nonce,第三个才是 plaintext</p>
附加说明:
- 第四个参数是额外认证数据(AAD),可为
nil,但若非空,加解密时必须完全一致 -
Decrypt成功返回明文切片;失败(如 nonce/AAD 不匹配或密文篡改)直接返回 error,不会填充 dst
不要忽略 AAD 的语义和生命周期管理
AES-GCM 的 AAD(Additional Authenticated Data)不是可选的“功能开关”,而是认证边界的一部分。如果你把请求路径、HTTP 方法、时间戳等作为 AAD,那它们就必须在解密时**一字不差地提供**,否则 Decrypt 立即失败。
实际中容易踩的坑:
- 序列化 AAD 时用了不同格式(如 JSON vs map[string]interface{} 的键序不一致)→ 解密失败
- 服务端加了 AAD,客户端忘记传或传空 → 认证失败,不是解密失败
- 把敏感信息(如用户 ID)放进 AAD 并以为它被加密了 → AAD 是明文传输的,只参与认证,不加密
- 重放攻击防护依赖 AAD 中的时间戳,但没校验其是否过期 → AAD 正确但业务逻辑已失效
一个最小可用示例里,AAD 常设为 nil;一旦引入,就得把它当作协议字段来维护版本和兼容性。
nonce 生成、密钥派生、AAD 构造这三件事,任何一个出错都会让整个 GCM 流程静默失败或暴露密钥——它们不是“之后再补”的环节,而是在第一次调用 NewGCM 前就必须设计清楚的约束。











