aes加密失败或解密乱码主因是密钥长度(必须16/24/32字节)、iv管理(cbc需随机16字节iv并前置,gcm需唯一12字节nonce)和填充逻辑(cbc需pkcs#7,gcm自动处理)未对齐;密文须用base64编码,密钥与iv严禁硬编码。

aes.NewCipher 报错或解密乱码,基本都是密钥长度、IV 管理、填充逻辑三处没对齐——不是 Go 的 AES 不好用,是它不替你做决定。
密钥长度必须是 16/24/32 字节,否则 aes.NewCipher 直接 panic
Go 的 aes.NewCipher 对密钥长度极其严格:传入 15 字节或 17 字节都会触发 crypto/aes: invalid key size。常见错误包括:
- 直接用字符串字面量当密钥,比如
[]byte("mykey")(5 字节)或[]byte("password123")(11 字节) - 误以为“AES-128”只要 128 位(16 字节)就行,却用了 UTF-8 编码的中文密钥,导致字节数超标
- 从配置读取密钥后没 trim 空格或换行符,实际长度多出 1–2 字节
正确做法是固定截取或安全派生:
// 推荐:用 SHA256 派生并截取 32 字节(适配 AES-256)
key := sha256.Sum256([]byte(rawKey)).Sum(nil)[:32]
// 或者明确指定长度(如 AES-128)
key := []byte("1234567890123456") // 必须恰好 16 字节
别硬编码密钥;生产环境应从环境变量读取后派生,且密钥本身不能含可读语义。
CBC 模式下必须手动 PKCS#7 填充 + 随机 IV,且 IV 要和密文一起存
CBC 不是“拿明文一塞就加密”,它要求:
- 明文长度必须是
aes.BlockSize()(即 16)的整数倍,否则cipher.NewCBCEncrypter(...).CryptBlocks会 panic - IV 必须是 16 字节、每次加密随机生成、不可复用,且必须随密文传输(通常前置)
- 解密时必须先拆出前 16 字节作 IV,再解剩余部分,最后去掉填充
典型错误写法:iv := key[:16](密钥派生 IV)或 iv := []byte("fixed-iv-12345678")(硬编码),这会让相同明文每次加密结果一致,丧失语义安全。
安全做法:
iv := make([]byte, aes.BlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
blockMode := cipher.NewCBCEncrypter(block, iv)
encrypted := make([]byte, len(padded))
blockMode.CryptBlocks(encrypted, padded)
return append(iv, encrypted...), nil // IV 明文前置
GCM 比 CBC 更适合新项目,但 cipher.NewGCM 的 nonce 不能重复
cipher.NewGCM 返回的是 AEAD 接口,自动带完整性校验,无需手动填 PKCS#7,也不需要单独加 HMAC。但它对 nonce(即 IV)更敏感:
- nonce 长度建议用 12 字节(Go 默认),而非 16 字节;太短易碰撞,太长浪费空间
- 同一密钥下,两次使用相同 nonce → 攻击者可完全恢复密钥
- nonce 不需要保密,但必须唯一且不可预测,推荐用
crypto/rand.Read生成
示例结构:
nonce := make([]byte, 12)
if _, err := rand.Read(nonce); err != nil {
return nil, err
}
aesgcm, _ := cipher.NewGCM(block)
sealed := aesgcm.Seal(nil, nonce, plaintext, nil)
// sealed = [nonce][ciphertext][auth tag]
注意:GCM 的密文比原文略长(+12 字节 nonce + 16 字节 auth tag),且 Seal 和 Open 必须用完全相同的附加数据(aad)参数。
密文编码选 base64.StdEncoding,别用 hex.EncodeToString
二进制密文不能直接当字符串处理。两者区别很实际:
-
hex.EncodeToString输出全是 0–9a–f,长度翻倍,适合调试看日志 -
base64.StdEncoding.EncodeToString输出长度只增约 33%,且是 HTTP/JSON 友好格式,**生产环境必须选它**
错误示例:string(encrypted) —— 这会产生非法 UTF-8 字符串,存数据库或传 JSON 时出错。
正确写法:
encoded := base64.StdEncoding.EncodeToString(sealed) // 解码时: decoded, _ := base64.StdEncoding.DecodeString(encoded)
最后一句提醒:无论选 CBC 还是 GCM,IV/nonce 和密钥都绝不能出现在代码里或配置文件明文中;它们必须运行时注入、安全派生,并在内存中及时清零。这点被忽略,前面所有加密都白做。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











