不能用sodium_crypto_secretbox做API密钥存储,因其nonce易拼接出错、无附加数据绑定能力,且PHP 8.2推荐改用支持AD和IETF标准的sodium_crypto_aead_xchacha20poly1305_ietf_*函数。

PHP 8.2 默认内置 sodium 扩展,无需额外安装,但必须用 AEAD(认证加密)函数才能安全加解密——别碰 sodium_crypto_pwhash_* 系列,它们是哈希,不是加密。
为什么不能用 sodium_crypto_secretbox 做 API 密钥存储?
它能用,但不推荐用于长期存储的高敏数据(如用户 API key、数据库密码),因为:
– sodium_crypto_secretbox 的 nonce 是 24 字节,靠拼接存库易出错(比如 substr 截断越界)
– 没有附加数据(AD)支持,无法绑定上下文(例如“该密钥仅用于 service_x”)
– PHP 8.4+ 已提供更现代的 sodium_crypto_aead_aegis256_*,而 8.2 虽不原生支持 AEGIS-256,但可用 sodium_crypto_aead_xchacha20poly1305_ietf_* 替代,它支持 12 字节 nonce、带 AD、且是 IETF 标准格式,互操作性更好
sodium_crypto_aead_xchacha20poly1305_ietf_encrypt 怎么调用?
这是 PHP 8.2 中最推荐的 AEAD 函数,比 _secretbox 更灵活、更标准。关键点:
- 密钥长度固定为
sodium_crypto_aead_xchacha20poly1305_ietf_KEYBYTES(32 字节),必须用sodium_crypto_aead_xchacha20poly1305_ietf_keygen()生成,不可手写或截取 - nonce 长度是 12 字节(不是 24),用
sodium_randombytes_buf(12)生成,每次加密唯一 -
$additional_data可为空字符串,但建议传入业务标识(如"api_key:service_payment"),解密时必须传完全相同的值,否则_open直接返回false - 加密输出 = 密文 + 16 字节认证标签(共多出 16B),不需要 base64 编码——直接存二进制字段(如 MySQL 的
MEDIUMBLOB)
示例:
立即学习“PHP免费学习笔记(深入)”;
$key = sodium_crypto_aead_xchacha20poly1305_ietf_keygen();
$nonce = sodium_randombytes_buf(12);
$ad = 'api_key:stripe';
$plaintext = 'sk_live_abcd1234...';
$ciphertext = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt(
$plaintext,
$ad,
$nonce,
$key
);
// 存库:$nonce . $ciphertext(共 12 + len($plaintext) + 16 字节)
解密失败只返回 false,怎么定位问题?
sodium_crypto_aead_xchacha20poly1305_ietf_decrypt 不抛异常,只在验证失败或参数错误时静默返回 false。常见踩坑点:
- 从数据库读出的二进制数据被自动 trim 或转义(尤其用 PDO +
PDO::ATTR_EMULATE_PREPARES = true时)——确认字段类型是 BLOB,fetch 时用PDO::FETCH_ASSOC并禁用 emulate - nonce 长度不对:必须严格 12 字节,
substr($stored, 0, 12)后要检查strlen($nonce) === 12 -
$additional_data内容与加密时不一致(含空格、大小写、换行) - 密钥被意外修改(如用了
base64_encode($key)存库,解密时忘了base64_decode)
调试建议:加一层封装,显式校验输入长度,并记录失败时的 bin2hex($nonce) 和 bin2hex(substr($stored, 12, 16))(即认证标签前 16B),比对是否和加密输出一致。
密钥千万别硬编码或存在配置文件里
PHP 8.2 运行时内存可能被 dump,明文密钥等于裸奔。正确做法:
- 密钥由运维通过环境变量注入(如
APP_ENCRYPTION_KEY),用getenv('APP_ENCRYPTION_KEY')读取后立即sodium_hex2bin()或sodium_base642bin()转为二进制 - 若必须存文件,把密钥放在 Web root 外 + 严格权限(
chmod 600),且用sodium_memzero()在使用后清零缓冲区 - 绝对不要用
md5('my-secret') . 'salt'这类方式生成密钥——它没抗暴力能力,也不满足密钥熵要求
最后提醒:AEAD 加密本身不解决密钥轮换问题。一旦密钥泄露或需升级,所有已加密数据必须用旧密钥解密、再用新密钥重加密——这个过程没有捷径,得在业务层设计好迁移路径。



















