aes.newcipher 仅返回 aes 块加密器,需配合 pkcs#7 填充、随机 iv 和 cipher.blockmode(如 cbc)才能安全加解密;否则易 panic 或静默截断,且解密失败不报错而输出乱码。

aes.NewCipher 不能直接加密字符串——它只返回一个块加密器,必须配合填充、IV、模式封装才能安全使用,否则大概率 panic 或解密乱码。
为什么 aes.NewCipher 加密会 panic 或静默截断
aes.NewCipher 返回的是 cipher.Block,只对恰好 aes.BlockSize(16 字节)的输入做一次 AES 变换。传入 "hello" 这种非 16 字节明文会直接 panic;传入含中文的 UTF-8 字符串更危险:长度不对齐时,cipher.NewCBCEncrypter.CryptBlocks 不报错,但会静默丢弃末尾不足 16 字节的部分。
- 密钥长度必须是 16 / 24 / 32 字节:
[]byte("mypass")无效,[]byte("1234567890123456")才合法 -
block.Encrypt(dst, src)要求len(dst) == len(src) == block.BlockSize(),且dst必须已分配内存 - 不手动做 PKCS#7 填充、不生成随机 IV、不解密后校验填充 → 解密结果不可信
如何安全实现 CBC 模式加解密
CBC 是最常用也相对可控的模式,但三要素缺一不可:PKCS#7 填充 + 随机 IV + cipher.BlockMode 封装。
- 填充必须严格:末尾补
n字节,每个值都是n,其中n = 16 - len(data)%16 - IV 必须每次加密都重新生成:
iv := make([]byte, aes.BlockSize)后调用io.ReadFull(rand.Reader, iv)(不能用rand.Read(iv),可能读不满) - 密文结构推荐:
iv + encrypted(前置 IV),解密时先切出前 16 字节再初始化NewCBCDecrypter - 加密后密文字节数 =
16 + len(padded_data),不是原始字符串长度
为什么解密失败却不报错,反而全是乱码
cipher.NewCBCDecrypter.CryptBlocks 永远不会返回 error,哪怕密钥错、IV 错、密文损坏或被篡改。它只是机械执行异或和轮运算,输出完全不可控字节流。
- 解密后必须显式校验 PKCS#7 填充:取最后一个字节
n,检查是否在1..16范围内,且末尾n字节是否全等于n - 填充校验失败应返回
error,而不是直接转string()—— 否则后续传给json.Unmarshal可能 panic - 若业务允许,优先用 AEAD 模式(如
chacha20poly1305或aes/gcm),它自带完整性校验,解密失败会明确返回cipher.AEADDecryptError
大文件或流式加密最容易忽略的硬伤
小数据加密容易掩盖问题,但真实场景中大文件或流式处理会暴露底层设计缺陷。
- 仅靠
ioutil.ReadFile+ 内存加密会爆内存;盲目套io.Copy又会破坏块对齐,导致最后一块解密失败 - 流式加密必须分块处理:缓冲区大小建议设为 64KB(需是 16 的倍数),每块单独填充 → 加密 → 写入
- IV 和 salt(若用 scrypt 派生密钥)必须随密文持久化:标准做法是密文开头固定位置存 salt 和 IV
- AEAD 模式下 nonce 绝不可复用(AES-GCM 要求 12 字节),否则密钥可能被恢复
真正难的不是写通代码,而是让填充校验不绕过、IV 不复用、AEAD tag 不忽略、流式边界不越界——这些点一旦漏掉,加密就形同虚设。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











