cipher.newgcm是默认首选,因其天然支持aead认证,一次调用完成加密与完整性校验,避免cbc+hmac手动拼接导致的逻辑错位和校验遗漏。

cipher.NewGCM 是当前最稳妥的文件加解密入口,不是“可选”,而是默认应优先采用。它天然带认证(AEAD),避免 CBC + HMAC 手动拼接导致的逻辑错位、标签校验遗漏等高频事故。
为什么不能用 cipher.NewCBCEncrypter 直接套 io.Copy
常见错误现象是小文件能解,大文件末尾乱码或 cipher.NewCBCEncrypter().CryptBlocks panic 报 “len(src) not multiple of block size”。原因很直接:io.Copy 不保证每次读取长度是 16 字节倍数,而 CryptBlocks 要求输入严格对齐块大小。
- 必须用固定缓冲区(如
64 * 1024)分块读取,每块单独填充(PKCS#7)、加密、写入 - 最后一块不足缓冲区大小时,仍需完整填充——不能跳过或截断
-
cipher.BlockMode实例不能复用:CBC 模式内部维护链式状态,跨块复用会污染前一块密文依赖 - 别用
bufio.Reader包裹源文件句柄:它内部缓存会破坏块边界,让 IV 对齐失效
cipher.NewGCM 的 nonce 长度写死为 12 字节,不是“建议”
调用 aesgcm, _ := cipher.NewGCM(block) 后,aesgcm.NonceSize() 固定返回 12。传入 16 字节切片会直接 panic,不是运行时报错,而是启动就崩。
- 生成方式必须是:
nonce := make([]byte, aesgcm.NonceSize()),再io.ReadFull(rand.Reader, nonce) - nonce 绝不能复用:哪怕同一密钥、同一文件重加密一次,也得换新 nonce
- 密文结构严格为:
nonce(12B) +ciphertext+tag(16B),解密端必须按此偏移硬切,不能靠 magic 字符串识别 - 别把 nonce 和 salt 混用:salt 用于密钥派生(如
scrypt.Key),nonce 仅用于本次 GCM 加密,两者随机源、生命周期、存储位置都不同
密钥不能从字符串硬转 []byte,必须用 KDF 派生
用户输的密码(如 "myPass123")熵太低,直接 []byte("myPass123") 当密钥,等于裸奔。标准库不提供 PBKDF2 或 scrypt,得引入 golang.org/x/crypto/scrypt 或 pbkdf2。
-
scrypt.Key参数推荐:N=32768,r=8,p=1;salt 必须每次随机生成(crypto/rand.Read(salt)),且和 nonce 一样写入文件头 - 密钥长度必须严格 32 字节(AES-256):
scrypt.Key输出后要截断或扩展,不能凑合用 16 字节 - 从环境变量读密钥时,务必
strings.TrimSpace—— 换行符、空格会导致cipher.ErrAuthentication且无明确提示 - 临时密钥切片(如
derivedKey)用完立即bytes.Fill(derivedKey, 0),防止内存 dump 泄露
文件头结构没对齐,解密端根本无法启动
不是“读不出来”,而是连 nonce 都切错位置,aesgcm.Open 直接返回 cipher.ErrAuthentication,你以为密钥错了,其实是偏移算错了。
- 推荐头部布局:
[salt(16B)][nonce(12B)][ciphertext+tag]—— salt 长度和 nonce 长度都不能靠经验猜,必须用len(salt)和aesgcm.NonceSize()硬编码 - 写入顺序必须严格:先
file.Write(salt),再file.Write(nonce),最后file.Write(sealed) - 读取时按字节偏移硬切:
buf[0:16]取 salt,buf[16:28]取 nonce,buf[28:]剩余全喂给aesgcm.Open - 别用
os.Create()覆盖原文件:应写dst + ".tmp",成功后os.Rename(),避免加密中断留下半截损坏文件
真正卡住人的从来不是算法本身,而是 salt、nonce、密文三者在文件头里谁先谁后、各占几字节、读的时候怎么切——这些细节一旦写死就难改,上线后出问题连 debug 日志都看不出哪块偏了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











