解密时必须先分离salt、nonce和密文三段:salt取buf[0:16],nonce取buf[16:28],剩余为密文+tag;三者顺序与长度不可错位,否则gcm.Open将静默失败或报cipher.ErrMessageTooShort。

解密时必须先分离 salt、nonce 和密文三段
Go 的 cipher.NewGCM 不会自动识别文件结构,你喂给它的必须是纯 nonce + ciphertext + tag,且 nonce 长度必须严格等于 gcm.NonceSize()(通常是 12 字节)。如果加密时把 salt(用于密钥派生)和 nonce(用于本次 GCM 加密)都写进了文件头,解密端就得硬切偏移——错一个字节,Open() 就直接返回 nil, cipher.ErrMessageTooShort 或静默失败。
-
salt通常 16 字节,nonce固定 12 字节,两者顺序不能颠倒;读取时用buf[0:16]取 salt,buf[16:28]取 nonce,buf[28:]是完整密文(含 16 字节 tag) - 别依赖文件扩展名或 magic bytes 判断结构——加密文件就是二进制 blob,结构全靠约定
- 如果解密报
cipher: message authentication failed,90% 是nonce拿错了,而不是密钥问题
大文件不能用 io.Copy 直接喂给 GCM 解密器
io.Copy 是流式搬运工,不关心块边界。但 gcm.Open() 要求输入数据包含完整 nonce + ciphertext+tag,且 tag 必须紧贴密文末尾。你如果把整个加密文件丢给 io.Copy(dst, gcm.StreamReader{}),它会在内部缓存、拆包、重分块,导致 nonce 和密文错位,tag 被截断或粘连。
- 正确做法:用固定缓冲区(如
64 * 1024)分块读,每块单独调gcm.Open(),再写入目标文件 - 注意最后一块可能不足缓冲区大小,
gcm.Open()支持任意长度输入,只要tag在末尾 - 千万别用
bufio.Reader包裹源文件——它的预读缓存会提前吞掉部分密文,破坏nonce与后续密文的物理连续性
密钥派生必须复用加密时的 scrypt 参数
解密失败却没报错?大概率是密钥不对。Go 标准库不保存派生参数,scrypt.Key 的 N、r、p 值必须和加密时完全一致,否则生成的密钥不同,gcm.Open() 会因认证失败返回空结果而非 panic。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 推荐固定参数:
N=32768、r=8、p=1;若加密端用了其他值(比如N=16384),解密端也得照搬 -
salt是从文件头读出来的,不是随机生成的——它和密钥派生强绑定,换 salt 就等于换密钥 - 别用
sha256.Sum256([]byte(password))这类快速哈希替代scrypt.Key,它们无法抵抗暴力破解,且输出长度也不一定匹配 AES-256 的 32 字节要求
配置文件解密后才能交给 viper 或 json.Unmarshal
加密的 YAML/JSON 文件不是“带密码的配置文件”,而是二进制密文。viper 不认识 GCM 标签,json 包只认 { 开头的明文。直接 viper.ReadInConfig() 会报 invalid character '\x00' looking for beginning of value 这类错误,本质是解析器在读密文首字节(通常是 salt 的第一个字节)。
立即学习“go语言免费学习笔记(深入)”;
- 流程只能是:读文件 → 提取
salt/nonce→scrypt.Key派生密钥 →gcm.Open()解密 → 得到明文字节 →viper.ReadConfig(bytes.NewReader(decrypted)) - 不要试图封装一个 “支持加密的 viper”,viper 的设计不处理加解密,强行 patch 容易破坏 reload 逻辑
- 如果配置频繁变更,考虑解密后缓存明文内容,避免每次读都重复密钥派生和解密开销
解密流处理最难绷的点不在算法本身,而在结构约定的严格执行:salt 长度、nonce 长度、写入顺序、缓冲区对齐、派生参数一致性——少对齐一个字节,或参数差一个,结果都是静默失败,而不是明确报错。

















