直接用 crypto/aes 加密字符串会 panic,因为 aes 是块加密算法,要求明文长度必须是 16 字节的整数倍,否则调用 encrypt 时触发 “crypto/cipher: input not full blocks” 错误。

为什么直接用 crypto/aes 加密字符串会 panic?
因为 AES 是块加密算法,要求明文长度必须是 16 字节(AES-128)的整数倍,而普通字符串长度几乎不会刚好满足。不处理填充就调用 Encrypt,会触发 panic: crypto/cipher: input not full blocks。
常见错误写法:cipher.NewCBCEncrypter(block, iv).Encrypt(dst, src) 中 src 长度不是 16 的倍数 —— 这不是 bug,是设计使然。
- 必须手动补位(padding),标准做法是 PKCS#7(Go 社区普遍称 PKCS#5,但实际一致)
- 不要用
strings.Repeat("\x00", n)补零:解密后无法区分真实 \x00 和填充字节 - 补位逻辑必须在加密前做、解密后移除,且两端严格一致
如何正确实现 PKCS#7 填充与去填充?
Go 标准库不提供内置填充函数,得自己写。核心规则:若原文长度为 l,块大小为 bs = 16,则补 n = bs - (l % bs) 个字节,每个值为 n(即 \x01 到 \x10)。
示例填充函数:
func pkcs7Pad(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padtext := make([]byte, padding)
for i := range padtext {
padtext[i] = byte(padding)
}
return append(data, padtext...)
}
<p>func pkcs7Unpad(data []byte) []byte {
length := len(data)
unpadding := int(data[length-1])
if unpadding > length || unpadding == 0 {
panic("invalid padding")
}
return data[:(length - unpadding)]
}</p>
- 注意:解密后必须校验最后一个字节是否在 1–16 范围内,且连续
unpadding个字节都等于该值(严谨实现需校验,示例简化) - 如果原始数据末尾恰好是 \x01、\x02 等,会被误判为填充 —— 这正是 PKCS#7 设计的代价,无法避免,只能依赖协议层保证不出现歧义
CBC 模式下 IV 必须随机且每次不同
重用 IV 会导致相同明文加密出相同密文,严重削弱安全性。IV 不需要保密,但必须不可预测。
- 生成方式推荐:
iv := make([]byte, block.BlockSize())+rand.Read(iv)(用crypto/rand,不是math/rand) - IV 通常和密文一起传输,比如拼接成
iv + ciphertext,解密时先切出前 16 字节作为 IV - 切记:
rand.Read可能返回 error,生产环境不能忽略 - CBC 模式下,哪怕只改一个字节的明文,整个密文块都会变化 —— 这是正常现象,不是 bug
完整加解密流程中容易漏掉的三件事
写完加解密函数后跑通单测不等于可用。以下三点漏掉一个,就会在特定输入下失败:
- 密钥必须恰好 16/24/32 字节(对应 AES-128/192/256),
md5.Sum128().Sum()得到 16 字节可直接用,但sha256输出 32 字节才能用于 AES-256 - 加密前对字符串调用
[]byte(s),解密后用string(b)—— 看似简单,但若字符串含 UTF-8 多字节字符,编码本身没问题,问题常出在填充边界上(比如 3 字节中文字符 + 13 字节填充 = 16 字节块) - 使用
cipher.BlockMode时,dst底层数组必须足够大:加密后长度 = 原长 + 填充字节数;解密前要确保dst至少和密文等长
最常被忽略的是:PKCS#7 填充后长度可能变成原长度的两倍(比如 15 字节 → 32 字节),而很多初版实现直接 make([]byte, len(src)) 分配 dst,导致越界 panic。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











