使用aes-gcm加密本地文件时,必须手动确保nonce全局唯一(推荐12字节)、每次加密生成新nonce并存入文件头部,同时用scrypt或pbkdf2派生密钥、随机salt、校验open()返回值、用io.readfull完整读取密文,否则必然导致安全失效。

用 aes.GCM 加密本地文件前,必须手动处理 nonce 长度和重复风险
Go 标准库的 aes.GCM 不会自动生成或管理 nonce,直接复用同一个 nonce 会导致密文可被完全破解——这不是“可能不安全”,而是“一定被破解”。GCM 要求 nonce 全局唯一,且推荐长度为 12 字节(96 位),不是越长越好。
- 每次加密必须生成新 nonce,建议用
crypto/rand.Read()填充 12 字节切片 - 把 nonce 和密文一起存入文件(比如前 12 字节),解密时先读出再传给
Seal()的nonce参数 - 别用时间戳、计数器或文件名哈希当 nonce:它们在并发或重跑场景下极易重复
- 如果用
golang.org/x/crypto/chacha20poly1305,同样要自己管 nonce,但它的推荐长度是 24 字节
os.OpenFile 以 O_CREATE|O_TRUNC 模式写密文,会清空原文件但不校验密码正确性
密码保管箱最危险的误操作之一:用户输错密码,程序仍成功“解密”出乱码并覆盖原文件。这是因为 AES-GCM 的 Open() 在认证失败时只返回错误,而很多实现没检查这个错误就直接写盘。
- 务必在调用
blockMode.Open()后判断返回值是否为nil;非 nil 表示密钥错误或密文被篡改 - 写入前先写到临时文件(如
data.json.enc.tmp),确认加密/解密无误再os.Rename()覆盖原文件 - 不要用
O_APPEND拼接密文——GCM 不支持流式追加,会破坏认证标签 - 明文 JSON 文件若含 UTF-8 BOM,解密后需手动 strip,否则解析会报
invalid character ''
密码派生必须用 scrypt.Key 或 pbkdf2.Key,不能直接 sha256([]byte(pwd))
用户密码通常短、弱、有规律,直接哈希得到的密钥熵极低,暴力破解只要几秒。标准库没有 scrypt 实现,得用 golang.org/x/crypto/scrypt。
宁志NZCMS网站管理系统是国内知名建站软件,它是由宁志公司自主研发的一款自助建站系统软件。系统操作简单,无复杂的安装设置要求,适合广大工作人员使用。 产品特点: 安全、稳定、美观、实用、易操作,内部局域网和互联网均可安装使用! 面向用户:中小企业,公司,个人,外贸公司网站建设平台。 安全机制:NZCMS采用宁志公司自主研的核心框架,代码开发严谨,数据库加密保护,防
- 参数至少设为
N=32768, r=8, p=1(内存约 256MB,单次耗时 ~100ms),低于这值防护意义很小 - 盐(salt)必须随机且每用户不同,长度不少于 16 字节,和密文一起存(比如开头 16 字节)
- 别把 salt 硬编码或固定为用户名——攻击者预计算彩虹表就白忙了
- 如果选
pbkdf2,迭代次数不低于 100000,且必须用hmac.New(sha256.New, ...),不能用sha256.Sum256
读取加密文件时,io.ReadFull 比 io.Read 更可靠
本地文件读取看似简单,但密文长度固定(nonce + salt + 认证标签 + 密文),少读一个字节就会让 GCM.Open() 返回 crypto/aes: invalid ciphertext,而错误信息完全不提示是读少了。
立即学习“go语言免费学习笔记(深入)”;
- 用
io.ReadFull(file, buf)替代file.Read(buf):前者确保填满整个buf,后者可能只读部分就返回EOF - 提前算好预期长度:比如 12 字节 nonce + 16 字节 salt + 16 字节 tag + 明文长度,全部读完再拆分
- 如果文件被截断(比如磁盘满导致上次写入失败),
ReadFull直接返回io.ErrUnexpectedEOF,比静默错解更易定位 - 别用
json.Unmarshal直接解密后数据——先验证 GCM 成功,再解析 JSON,否则错误堆栈会掩盖真实问题
真正难的不是加解密逻辑,是把 nonce、salt、tag 这三段二进制数据和用户密码、文件路径之间的绑定关系理清楚。少一个环节对齐,就变成“能运行但不可靠”的状态。









