因为chacha20poly1305.new要求传入非nil且长度为32字节的密钥,若传入空切片、长度错误的字节切片(如[]byte("my-pass"))会导致panic: non-nil key。

为什么直接用 chacha20poly1305.New 会 panic:non-nil key?
因为 chacha20poly1305.New 接收的是已派生的 32 字节密钥,不是原始密码字符串。常见错误是传入空切片或长度不对的 key(比如用 []byte("my-pass")),导致运行时 panic:panic: non-nil key。
正确做法是先用密钥派生函数(如 scrypt.Key 或 hkdf.Extract)把密码转成 32 字节密钥,或者直接安全生成随机密钥:
-
key := make([]byte, chacha20poly1305.KeySize)+rand.Read(key) - 若必须从密码出发,用
scrypt.Key(password, salt, N, r, p, 32),其中N=1、<code>r=8、p=1是较稳妥的参数 - 注意:salt 必须每次加密都不同,且需和密文一起保存(通常前置在输出中)
如何正确生成 nonce 并避免重用?
chacha20poly1305.NonceSize 是 12 字节,但 chacha20poly1305.NonceSizeX(24 字节)仅用于 XChaCha20 变种 —— 多数场景用前者即可。nonce 绝对不能重复,否则密钥流复用会导致明文可被完全恢复。
推荐做法:
- 用
rand.Read(nonce)每次生成全新 nonce(12 字节) - 将 nonce 明文拼接在密文前(例如
append(nonce[:], ciphertext...)),解密时先切出前 12 字节 - 不要用计数器(如 i++)—— 服务重启或并发写入易冲突;也不要硬编码或复用同一变量
加密时要不要传 AAD?什么时候必须用?
AAD(Additional Authenticated Data)不加密但参与认证,适合放「需要校验但不保密」的元数据,比如文件名、创建时间、用户 ID。如果 AAD 不匹配,Open 会直接返回 nil, cipher.ErrAuthentication,不会泄露明文。
典型使用场景:
- HTTP 响应加密:把
Content-Type和Status作为 AAD,防止篡改响应类型 - 数据库字段加密:把行 ID 或表名作为 AAD,避免密文被错放到其他记录中
- 不传 AAD 就传
nil,但别传空切片[]byte{}—— 两者语义不同,nil表示无 AAD,[]byte{}是长度为 0 的有效 AAD
解密失败只返回 nil, error,怎么区分是密钥错还是密文损毁?
底层统一返回 cipher.ErrAuthentication,不会告诉你具体原因。这是设计使然:避免攻击者通过错误类型侧信道判断密钥是否接近正确。
实操中能做的只有:
- 确保密钥、nonce、AAD 三者与加密时完全一致(包括字节顺序、是否 nil)
- 检查密文是否被截断(长度应 ≥
NonceSize + 16,16 是认证标签长度) - 验证 nonce 长度是否为 12(不是 16 或 24);用
len(ciphertext) 提前拦截 - 如果业务允许,可在加密前加简单明文头(如
[]byte("ENCv1")),解密后校验,快速排除“根本不是密文”的情况











