Go中AES加密需手动处理填充、IV和密钥长度:CBC需PKCS#7填充与16字节随机IV;GCM更安全,内置认证且无需填充,但须确保密钥长度合规、nonce不重复,并严格校验错误。

AES 加密在 Go 里不是调个 Encrypt() 就完事的——它默认不带填充、不处理 IV 生成、不校验密钥长度,直接套用标准库容易解密失败或被攻击。
为什么 crypto/aes + crypto/cipher 组合老是 panic 或解密乱码
Go 标准库的 aes.NewCipher 只负责构建分组密码实例,不处理模式(如 CBC、GCM)、不自动补位(PKCS#7)、也不管理 IV。常见错误包括:
-
aes.NewCipher传入 16 字节密钥却用 AES-256(要求 32 字节),直接 panic:crypto/aes: invalid key size 16 - CBC 模式下没给 IV 或 IV 长度不对(必须 16 字节),
cipher.NewCBCEncrypter会 panic - 明文长度不是 16 的倍数,又没手动 PKCS#7 填充,加密后解密时末尾字节错乱
- 把 IV 当作固定值硬编码,导致相同明文每次加密结果一样,丧失语义安全
用 crypto/cipher.BlockMode 做 CBC 加密,必须自己填 PKCS#7
CBC 是最常被问到的“兼容其他语言”的模式,但 Go 不提供开箱即用的填充逻辑。你得手动补位再加密:
- 填充规则:计算
pad := 16 - len(plaintext)%16,然后追加pad个字节,每个值为byte(pad) - 解密后必须验证末尾字节是否合法,再截掉——不能只截最后
pad字节,防止填充 oracle 攻击 - IV 必须随密文一起传输(通常前置),且每次加密随机生成,别复用
示例关键片段:
立即学习“go语言免费学习笔记(深入)”;
iv := make([]byte, block.BlockSize())
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
mode := cipher.NewCBCEncrypter(block, iv)
plaintext = pkcs7Pad(plaintext, block.BlockSize()) // 自己实现
ciphertext := make([]byte, len(plaintext))
mode.CryptBlocks(ciphertext, plaintext)
return append(iv, ciphertext...), nil优先选 crypto/aes + crypto/cipher.NewGCM 而不是手搓 CBC
GCM 模式自带认证,防篡改,且 Go 标准库完整支持,不用管填充、IV 长度、MAC 计算——只要密钥够长、nonce 别重复:
- 密钥必须是 16/24/32 字节(对应 AES-128/192/256),
aes.NewCipher才不报错 - nonce 长度推荐 12 字节(GCM 最佳实践),太短不安全,太长效率低;
gcm.Seal会自动把 nonce 和认证标签(tag)拼在密文末尾 - 解密时用
gcm.Open,它会同时校验 tag 和解密,失败直接返回 error,不会吐出错乱明文 - 千万别把同一个 nonce 用两次,否则密钥流可被恢复
别忽略密钥来源和错误处理细节
生产环境最容易翻车的地方不在算法本身,而在周边:
- 从字符串转密钥别用
[]byte("mykey")——ASCII 字符串长度往往不对,要用sha256.Sum256摘要后截取,或明确指定32字节密钥 - 所有
io.ReadFull、aes.NewCipher、gcm.Open的 error 都必须检查,尤其gcm.Open失败时不能当普通解密错误吞掉 - 如果要和其他语言(如 Python 的
pycryptodome)互通,确认双方用的 mode、padding、IV/nonce 生成方式、tag 长度(GCM 默认 16 字节)完全一致
密钥长度、IV/nonce 随机性、填充与认证逻辑——这三个点漏掉任何一个,加密就只是看起来像加密。


















