结论:sodium_crypto_pwhash_scryptsalsa208sha256_str是不可逆密码哈希函数,仅用于验证,不能解密;可逆加密应使用sodium_crypto_secretbox与sodium_crypto_secretbox_open配合nonce和密钥实现。

直接说结论:别用 sodium_crypto_pwhash_scryptsalsa208sha256_str 做“加密/解密”,它根本不是为可逆加解密设计的 —— 这是密码哈希函数,只能验证,不能还原明文。你看到的“解密失败”,本质是方向错了。
为什么 crypto_pwhash_* 系列函数不能用于存储后还原?
这类函数(如 sodium_crypto_pwhash_scryptsalsa208sha256_str、sodium_crypto_pwhash_scryptsalsa208sha256)属于**密码派生(key derivation)+ 哈希封装**,目标是把密码变成不可逆的字符串,专用于比对验证。它们内部做了盐值混入、多次迭代、内存硬化等操作,但没有保留任何可逆信息。
- 调用
sodium_crypto_pwhash_scryptsalsa208sha256_str_verify()返回true或false,不是解密结果 - 你代码里试图用
substr(md5(...), 0, $this->ks)手搓密钥,既不安全也不匹配 libsodium 的密钥派生逻辑 - 所谓“加密得到哈希”这个认知本身就有问题 —— 它压根没输出密文,只输出一个带盐的哈希字符串
该用哪个函数做可逆加密存储?
要实现“存进数据库、读出来还能还原”的场景,必须用认证加密(AEAD)函数:sodium_crypto_secretbox(加密)和 sodium_crypto_secretbox_open(解密)。它基于 XChaCha20-Poly1305,自带完整性校验,且 API 简洁安全。
- 密钥必须用
sodium_crypto_secretbox_keygen()生成,32 字节,不可手造 - nonce(非重复数)必须每次加密都新生成,24 字节,用
sodium_randombytes_buf(24) - nonce 必须和密文一起存入数据库(例如拼成
$nonce . $ciphertext,或分字段存),否则无法解密 - 解密失败时
sodium_crypto_secretbox_open()返回false,不是抛异常,需显式判断
一个能跑通的最小可逆加密示例
// 加密
$key = sodium_crypto_secretbox_keygen();
$nonce = sodium_randombytes_buf(24);
$plaintext = 'my_secret_api_key_123';
$ciphertext = sodium_crypto_secretbox($plaintext, $nonce, $key);
<p>// 存库:把 $nonce 和 $ciphertext 拼起来(注意 nonce 固定 24 字节,可直接前置)
$stored = $nonce . $ciphertext;</p><p>// 解密
$nonce = substr($stored, 0, 24);
$ciphertext = substr($stored, 24);
$plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
if ($plaintext === false) {
throw new Exception('Decryption failed — bad key, corrupted data, or reused nonce');
}
</p>
注意:密钥 $key 绝不能存在数据库里,必须由服务端安全保管(如环境变量、密钥管理服务)。nonce 可公开,但绝不可复用 —— 同一密钥下重复使用 nonce 会直接破坏安全性。
最容易被忽略的点:nonce 复用比密钥泄露更致命,且毫无征兆;而很多人把密钥硬编码在 config.php 里,还觉得“只要不 git commit 就安全”。这两处,才是真实线上事故的高发区。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











