sodium_crypto_box密钥对不可直接serialize(),须分离公私钥并base64编码;私钥必须额外加密或交kms管理;sodium_crypto_box_open()静默失败需显式判断返回值;密钥长度严格为32字节且拼装顺序不可错。

sodium_crypto_box 生成的密钥对不能直接 serialize()
PHP 的 sodium_crypto_box_keypair() 返回的是二进制字符串,不是普通数组或对象。直接用 serialize() 或 json_encode() 处理会丢失数据或产生乱码,解序列化后无法用于 sodium_crypto_box_open() —— 这是最常见的“密钥存下来却解不开”的原因。
正确做法是分离公钥和私钥,分别用 sodium_crypto_box_publickey() 和 sodium_crypto_box_secretkey() 提取后再编码:
- 公钥固定 32 字节,可直接
base64_encode()存储 - 私钥也是 32 字节,同样用
base64_encode(),但必须严格保密(如存环境变量或 KMS) - 绝对不要把原始密钥对字符串(即
sodium_crypto_box_keypair()的完整返回值)直接序列化或写入数据库
存储私钥时忘记加盐或加密,导致明文泄露
很多人把 base64 后的私钥当成“安全格式”,直接写进配置文件或数据库字段。但 base64 不是加密,只是编码 —— 一旦数据库被拖库,私钥就等于裸奔。
生产环境必须额外保护私钥:
- 用
sodium_crypto_pwhash()+ 主密码派生密钥,再用该密钥调用sodium_crypto_secretbox()加密私钥本身 - 或交由外部密钥管理服务(如 HashiCorp Vault、AWS KMS),只存其引用 ID
- 本地开发可临时用
openssl_encrypt($private_key, 'aes-256-gcm', $master_key, ...),但务必确保$master_key不硬编码
sodium_crypto_box_open() 解密失败却不报错,只返回 false
这个函数设计就是静默失败:只要 nonce 错、公钥不匹配、私钥被截断、或者密文被篡改,它都只返回 false,不会抛异常。很多开发者没检查返回值,直接拿 false 当原文用,结果业务逻辑崩得无声无息。
必须显式判断:
$decrypted = sodium_crypto_box_open($ciphertext, $nonce, $key_pair);
if ($decrypted === false) {
throw new Exception('Decryption failed: invalid key, nonce, or tampered ciphertext');
}
-
$key_pair必须是由sodium_crypto_box_keypair_from_secretkey_and_publickey()拼装的完整密钥对(私钥在前、公钥在后,共 64 字节) -
$nonce必须是 24 字节且与加密时完全一致;建议存密文时一起保存,比如拼成$nonce . $ciphertext - 若用
sodium_crypto_box_seal()替代,则无需配对密钥,但只能用私钥解,且不支持自定义 nonce
PHP 7.5 中 SODIUM_CRYPTO_BOX_SECRETKEYBYTES 值仍是 32
有人查文档发现 SODIUM_CRYPTO_BOX_SECRETKEYBYTES 是 32,就以为私钥长度恒为 32 —— 实际上这是 libsodium 的底层约定,但 PHP 7.5 的 sodium 扩展仍严格遵循它。别试图用 mb_strlen() 或 strlen() 验证,必须用 mb_strlen($key, '8bit') 或直接 strlen($key)(因是二进制)。
容易忽略的细节:
- 从 base64 解码私钥后,必须校验长度是否为 32:
if (strlen($decoded) !== SODIUM_CRYPTO_BOX_SECRETKEYBYTES) { ... } - 公钥同理,必须是 32 字节;拼装
$key_pair时顺序不能反(私钥在前 + 公钥在后) - 任何截断、补零、UTF-8 转换都会让整个密钥对失效
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











