aes-gcm是文件加密传输与本地存储的默认选择,因其是go标准库唯一开箱即用、免依赖且同时满足机密性、完整性与真实性的aead模式;密钥必须32字节、nonce必须12字节且唯一,输出为nonce||ciphertext||tag三段拼接。

为什么 AES-GCM 是文件加密传输与本地存储的默认选择
因为它是 Go 标准库唯一开箱即用、无需第三方依赖、同时满足机密性 + 完整性 + 真实性的 AEAD 模式。别碰 cipher.NewCBCEncrypter + 手动 HMAC 组合——容易漏校验、拼接错顺序、nonce 复用,出问题难定位。
关键约束必须严格遵守:cipher.NewGCM 要求密钥长度 **必须是 32 字节**(AES-256),nonce 长度 **必须是 12 字节**(aead.NonceSize() 返回值),不能传 16 字节 IV,否则运行时 panic;Seal() 输出是 nonce || ciphertext || tag 三段拼接,总长 = 12 + len(明文) + 16。
- 密钥若来自口令,必须用
scrypt.Key或pbkdf2.Key派生,带随机salt,迭代轮数 ≥ 100000 -
nonce必须每次加密都调用crypto/rand.Read()生成,绝不能复用、不能用时间戳或计数器 - 本地存储时,
salt和nonce都要写进文件头,推荐结构:[salt(16B)][nonce(12B)][ciphertext+tag]
加密文件时怎么避免内存爆掉和末尾乱码
直接用 io.Copy(dst, cipher.StreamWriter{}) 是常见陷阱:它不保证块对齐,而 AES-GCM 内部仍按分组处理,大文件最后一块易出错;os.ReadFile 一次性加载也不行,几百 MB 就 OOM。
正确做法是固定缓冲区流式分块,每块独立加解密:
- 缓冲区大小设为
64 * 1024或128 * 1024,避开 16 的倍数(如 65535)以防填充边界错位 - 读一块 → 调用
aead.Seal()加密 → 写入目标文件;最后一块不足缓冲区大小,Seal()仍可安全处理,无需手动补零或填充 - 写入前先写
salt(16B),再写nonce(12B),最后写密文;读取时硬切偏移:buf[0:16]取 salt,buf[16:28]取 nonce - 别用
os.Create()直接覆盖原文件;应写临时文件(如dst + ".tmp"),成功后os.Rename()
HTTP 上传加密文件时哪些字段绝对不能放 URL 或 Header 里
密钥、nonce、salt 这三样东西,**绝不能**出现在 URL 参数、Cookie、X-API-Key 或任何 HTTP header 中——它们会被代理日志、CDN 缓存、浏览器历史完整记录,等于公开广播。
正确传输方式只有两种:
- 走 HTTPS,把加密后的二进制数据作为
multipart/form-data的一个 part 上传,Content-Type: application/octet-stream - 或 POST 原始字节流,服务端用
io.Copy接收后,再按文件头协议(salt+nonce+密文)解析
如果客户端没预置密钥,需用非对称加密封装:用接收方 RSA 公钥加密随机生成的 AES 密钥,再一起传过去;Go 的 crypto/rsa 支持,但注意密钥长度 ≥ 2048 位。
解密失败时为什么不能只看 err == nil 就认为成功
aead.Open() 验证失败时一律返回 cipher: message authentication failed,不区分是 nonce 错、密钥错还是密文损坏——这是防旁路攻击的设计,但也意味着你不能只靠 error 判断结果是否可信。
必须叠加验证手段:
- 解密后立即计算明文 SHA-256,比对发送方附带的摘要(该摘要也应随密文加密或签名保护)
- 检查文件大小是否合理:比如解密出 0 字节或超大异常值,大概率是头部切片错位(
salt或nonce长度没对齐) - 若用 KDF 派生密钥,确保解密端使用的
salt、迭代次数、哈希算法和加密端完全一致,差一个参数就全错
最容易被忽略的是:salt、nonce、认证标签(tag)三者的长度和位置必须在所有实现中固化协议,换语言、换版本、换团队都得按同一偏移读写——这不是“能跑就行”的事,而是互通底线。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











