最稳妥的文件加密传输方案是用 cipher.NewGCM 加密后走 HTTPS;必须使用 12 字节 nonce(非 16 字节 IV),每次随机生成并前置存储,salt 与 nonce 分开且均需随机;大文件需分块加密、避免 io.Copy 和 bufio.Reader;密文结构固定为 salt(16B)+nonce(12B)+ciphertext+tag;解密必须显式校验 error 并清零明文。

直接用 cipher.NewGCM 加密文件再走 HTTPS 传输,是当前最稳妥的组合;别碰 CBC + 手动 HMAC,也别在 HTTP 层自己加解密。
为什么必须用 12 字节 nonce 而不是 16 字节 IV
NewGCM 要求 nonce 长度严格等于 gcm.NonceSize() 返回值(固定 12),传 16 字节会 panic:“invalid nonce size”,不是“能跑就行”的兼容问题。GCM 的安全模型依赖 nonce 唯一性,复用即失效——哪怕只复用一次,攻击者就能恢复密钥。
- 每次加密前调用
crypto/rand.Read(nonce[:]),必须检查 error - 不能从时间戳、PID 或计数器生成 nonce——这些都可能重复
- 写入文件时,nonce 必须前置,且长度硬编码为 12 字节,读取时按偏移切:
buf[0:12] - 别把 nonce 和 salt 混用:salt 用于密钥派生(如 PBKDF2),nonce 仅用于本次加密,两者都要随机、都要存、但用途不同
大文件不能用 io.Copy 直接套 cipher.Stream
看到 cipher.Stream(如 CFB)支持流式接口,就写 io.Copy(dst, cipher.StreamWriter{}),结果小文件看似正常,大文件末尾乱码或解密失败——因为 io.Copy 不保证块对齐,而 AES 分组密码内部仍按块处理,最后一块若未填满会导致填充错乱。
- 正确做法:用固定缓冲区(如
64 * 1024)分块读取,每块单独调用gcm.Seal()加密后写入 - 缓冲区大小避开 16 的倍数陷阱(如 65535),推荐 65536 或 131072
- 最后一块不足缓冲区大小?
gcm.Seal()仍可安全处理,无需手动补零或 PKCS#7 填充 - 别用
bufio.Reader包裹文件句柄再流式加密——它会干扰底层Read的字节边界
密文结构必须固化 salt + nonce + ciphertext+tag
密文不能光存数据,解密端根本不知道怎么拆。常见错误是只写 nonce、漏 salt,或把 salt 和 nonce 长度写死但没对齐——比如 salt 写了 16 字节,读的时候却从 buf[0:8] 开始取。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- 推荐结构:
[salt(16B)][nonce(12B)][ciphertext+tag],总长 = 16 + 12 + len(明文) + 16 - 生成 salt 用
crypto/rand.Read(salt),不能复用,不能用时间戳 - 写入顺序必须严格:先 salt,再 nonce,最后密文;读取时按偏移硬切,不依赖 magic 字节或分隔符
- 别用
os.Create()直接覆盖原文件——应写临时文件(如dst + ".tmp"),成功后再os.Rename()
解密失败时别靠返回切片长度判断
aesgcm.Open() 认证失败不会 panic,而是返回 cipher.ErrAuthentication 或 cipher.ErrInvalidLength。如果忽略 error,直接使用返回切片,可能拿到乱码、部分解密内容,甚至看似“成功”但实际已篡改的数据。
- 必须显式判断
if err != nil,且不要在日志中打印完整 error(可能泄露密钥错误等侧信道信息) - 解密后明文切片建议立即用
bytes.Fill(decrypted, 0)清零,尤其在处理用户身份证、手机号等强敏感字段时 - 如果密钥来自环境变量,务必
strings.TrimSpace(),否则换行符会导致cipher.ErrAuthentication且无提示 - viper 等配置库加载后,需递归遍历
map[string]interface{}定位带ENC[AES-GCM]::前缀的字段值,再解密——别 hook 文件读取器,那会破坏类型推导
真正容易出问题的不是算法本身,而是 salt 和 nonce 的生成时机、写入顺序、缓冲区大小选择,以及 error 是否被显式检查——这几个点漏掉任何一个,加密就形同虚设。

















