大文件不能用 os.readfile 全量加载,否则易导致 oom;go 标准库无流式 aes 加密原语,io.copy 直接套 cipher.blockmode 会因读取长度不满足块对齐而引发填充和解密错误。

大文件不能用 os.ReadFile 全量加载
超过几十 MB 的文件直接 os.ReadFile 会吃光内存,尤其在容器或低配机器上容易 OOM。Go 标准库不提供“流式 AES 加密”原语,io.Copy 直接套 cipher.BlockMode 也不行——CBC/GCM 都依赖块对齐和上下文状态,而 io.Copy 不保证每次读取长度是 16 字节倍数,填充和解密都会错乱。
- 小文件([]byte,用
mode.CryptBlocks或aesgcm.Seal一次性处理 - 大文件(≥ 1MB):必须分块读取,每块大小设为 64KB 或 128KB(需是 16 字节倍数),手动做 PKCS#7 填充/去填充(仅 CBC)或缓冲完整密文再追加 tag(GCM)
- 别用
bufio.Reader包裹源文件——它内部缓存会破坏块边界;用原始*os.File+make([]byte, blockSize)控制每次Read
选 AES-GCM 还是 AES-CBC?性能与安全怎么权衡
AES-GCM 虽比 CBC 多一次认证计算,但现代 CPU 的 AES-NI 指令集下,实际吞吐差距不到 10%;而 GCM 自带完整性校验,省掉额外 HMAC 计算,代码更简、出错面更小。CBC 看似轻量,但漏掉 HMAC 就等于裸奔——攻击者翻转密文第 N 块,解密后只会损坏第 N 和 N+1 块明文,且完全不报错。
- CBC 场景:仅限遗留系统兼容、或你已额外实现
hmac.New+Sum校验 - GCM 场景:推荐作为默认选择;注意
nonce长度必须是 12 字节(非 16),否则cipher.NewGCM会 panic - 两者密钥都必须是 32 字节(AES-256),别用
sha256.Sum256直接截取——要用pbkdf2.Key或scrypt.Key派生
流式加密时 IV/nonce 和 tag 怎么写进文件头尾
加密输出必须有确定结构,否则解密端无法拆分。GCM 模式下,Seal 返回的是 “密文 + 16 字节 tag”,但 nonce 不能混在里面——它必须前置,且长度固定(12 字节)。所以最终文件布局是:nonce(12) + ciphertext + tag(16)。解密时先读前 12 字节为 nonce,再读剩余全部数据,把最后 16 字节切出来当 tag,中间部分才是密文。
- CBC 模式则是:
iv(16) + padded-ciphertext,解密时只读前 16 字节,其余全当密文 - 写文件务必用
os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, 0600),权限 0600 防止密钥材料被其他用户读取 - 千万别在加密前
os.Stat判断文件大小再分支——竞态条件下文件可能被篡改;统一走流式路径更稳
密钥派生和内存清零容易被忽略的细节
用户输的密码如 "p@ssw0rd" 不能直接当密钥用。即使用了 pbkdf2.Key,若迭代次数低于 100000,GPU 暴力破解仍可在几小时内完成。派生出的密钥切片([]byte)用完后不清零,GC 前可能残留内存页中,被 core dump 或调试器捕获。
-
pbkdf2.Key推荐参数:hash: sha256.New,iterations: 100000,keyLen: 32 -
scrypt.Key更优但稍慢:参数N=32768, r=8, p=1,salt 必须每次加密新生成(crypto/rand.Read(salt)) - 密钥、IV、临时明文缓冲区,加密完成后立刻调用
bytes.Fill(key, 0)清零,别依赖 GC
cipher.ErrAuthFailed,不是 panic;CBC 解密错 IV 或密钥则输出全乱码,毫无提示——这种静默失败最容易被忽略,务必检查 error 并拒绝写入解密结果。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











