用 aes.Encrypt 加密文件前必须用 PKCS#7 填充明文至 16 字节整数倍,且每次加密需生成唯一 nonce(GCM)或 IV(CBC),并将其与密文一同写入文件头部。

用 aes.Encrypt 做文件加密前必须补全明文长度
Go 的 crypto/aes 只提供底层分组密码操作,不自动处理填充(padding)。直接对任意长度文件调用 aes.Encrypt 会 panic:「crypto/aes: invalid buffer size」。这是因为 AES 是块密码(block cipher),要求输入长度必须是 16 字节的整数倍。
常见做法是使用 PKCS#7 填充(注意:Go 标准库没内置,需手动实现):
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...)
}- 填充字节值等于填充长度(如缺 3 字节,则填
\x03\x03\x03) - 即使原文长度刚好是 16 的倍数,也要额外填充一整块(16 个
\x10),否则解密时无法区分“末尾是真实数据还是填充” - 读取大文件时别一次性读进内存,应分块填充 + 加密,避免 OOM
crypto/cipher.BlockMode 的两种典型模式:CBC 和 GCM
CBC(Cipher Block Chaining)和 GCM(Galois/Counter Mode)是 Go 中最常用的两种封装方式。它们不是互斥选项,而是安全模型的根本差异:
-
cipher.NewCBCEncrypter只提供机密性,**不校验完整性**;攻击者可篡改密文导致解密后乱码甚至执行恶意代码 -
cipher.NewGCM同时提供加密与认证(AEAD),解密时自动验证nonce+ 密文未被篡改,失败直接返回 error - GCM 要求
nonce绝对唯一(推荐用随机 12 字节),而 CBC 的 IV 只需不可预测(可用crypto/rand.Read生成)
生产环境强烈优先选 GCM。示例初始化:
立即学习“go语言免费学习笔记(深入)”;
block, _ := aes.NewCipher(key) gcm, _ := cipher.NewGCM(block) nonce := make([]byte, gcm.NonceSize()) rand.Read(nonce) // 注意:此处应检查 error
文件加密时,IV/nonce 和密钥绝不能硬编码或复用
把 iv 或 nonce 写死在代码里,等于把锁芯图纸贴门上。同样,同一密钥加密多个文件时若重复使用 nonce(GCM)或 IV(CBC),会导致密文可被分析破解。
- GCM 下重复 nonce:直接泄露明文异或结果,可能恢复出全部内容
- CBC 下重复 IV:首块密文失去随机性,且相同明文首块产生相同密文,暴露结构
- 正确做法:每次加密生成新随机
nonce(GCM)或iv(CBC),并将其**和密文一起写入输出文件头部**(如前 12/16 字节),解密时先读出再使用 - 密钥本身不应出现在代码中;建议用 KMS、环境变量(
os.Getenv("ENC_KEY"))或派生函数(scrypt.Key)生成
解密失败时,gcm.Open 返回的 error 不含具体原因
gcm.Open(dst, nonce, ciphertext, additionalData) 在认证失败(如 nonce 错、密文被改、密钥不对)时统一返回 cipher: message authentication failed。它**故意不说明是哪部分出错**,防止侧信道泄露信息(比如通过 error 类型判断密钥是否正确)。
- 不要靠 error 字符串做逻辑分支(如
strings.Contains(err.Error(), "authentication")) - 确保传入的
nonce长度等于gcm.NonceSize()(通常是 12),否则会 panic - 如果解密后数据为空但无 error,检查是否误把密文当成了明文——GCM 输出不含原始 IV/nonce,你得自己从文件头剥离后再传给
Open
最常被忽略的是:加密时写入了 nonce,解密时却忘了读出来,直接拿整个文件当密文传给 gcm.Open,结果必然失败。










