不能用 Crypt 加密用户密码,因其可逆且无盐无迭代,APP_KEY 泄露则密码全暴露;应使用 Hash::make()(基于 bcrypt)哈希密码,自动加盐和自适应迭代,验证用 Hash::check()。

为什么不能用 Crypt 加密用户密码
直接用 Crypt::encrypt() 存密码是危险操作。它可逆,一旦 APP_KEY 泄露,所有密码瞬间解出;且不带盐、不加迭代,扛不住彩虹表和暴力破解。
正确做法永远是用 Hash::make() —— 它底层调用 bcrypt(PHP 的 password_hash()),自动加盐、自适应迭代次数,验证时用 Hash::check():
use Illuminate\Support\Facades\Hash;
$hashed = Hash::make('user_password_123'); // 生成 $2y$... 开头的哈希
Hash::check('user_password_123', $hashed); // true
Hash::check('wrong', $hashed); // false
-
APP_KEY泄露不影响已哈希的密码安全 - 不要手动拼接盐或调用
md5()/sha1()—— 这些已被证明不安全 -
Hash::needsRehash($hashed)可检测是否需因算法升级重新哈希
Crypt 适合加密哪些数据
Crypt 是对称加密(AES-256-CBC),依赖 APP_KEY,适用于「需要后续解密」的敏感字段,比如:用户身份证号(合规脱敏存储)、支付渠道返回的 token、第三方 API 密钥等。
注意:它不校验完整性,Crypt::decrypt() 解密失败会抛出 Illuminate\Contracts\Encryption\DecryptException,必须捕获:
use Illuminate\Support\Facades\Crypt;
use Illuminate\Contracts\Encryption\DecryptException;
try {
$decrypted = Crypt::decrypt($encrypted_value);
} catch (DecryptException $e) {
// 记录日志,返回默认值或报错
}
- 加密后数据是 base64 编码字符串,长度远大于原文,别存进定长字段(如
VARCHAR(20)) - 数据库迁移时若改过
APP_KEY,旧数据将永久无法解密 —— 生产环境换 key 前必须批量解密重加密 -
Crypt::encryptString()和Crypt::decryptString()专用于字符串,避免序列化开销
如何安全地在数据库中加解密字段
不要裸写 Crypt::encrypt() 在模型里。用 Laravel 的 casts + 自定义 cast 更可控:
php artisan make:cast EncryptedStringCast
然后在 app/Casts/EncryptedStringCast.php 中:
bee餐饮点餐外卖小程序是针对餐饮行业推出的一套完整的餐饮解决方案,实现了用户在线点餐下单、外卖、叫号排队、支付、配送等功能,完美的使餐饮行业更高效便捷!功能演示:1、桌号管理登录后台,左侧菜单 “桌号管理”,添加并管理你的桌号信息,添加以后在列表你将可以看到 ID 和 密钥,这两个数据用来生成桌子的二维码2、生成桌子二维码例如上面的ID为 308,密钥为 d3PiIY,那么现在去左侧菜单微信设置
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Contracts\Encryption\DecryptException;
class EncryptedStringCast implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes)
{
if (is_null($value)) return null;
try {
return Crypt::decrypt($value);
} catch (DecryptException) {
return null; // 或 throw new RuntimeException
}
}
public function set($model, string $key, $value, array $attributes)
{
return is_null($value) ? null : Crypt::encrypt($value);
}
}
在模型中使用:
protected $casts = [
'id_number' => \App\Casts\EncryptedStringCast::class,
];
- 这样读写都透明,
$user->id_number拿到的是明文,存库自动加密 - 避免在
accessor/mutator中硬编码加密逻辑,不易复用也不易测试 - 如果字段可能为空,
get()方法里必须判空,否则Crypt::decrypt(null)报错
常见错误:DecryptException 怎么排查
典型报错信息:Illuminate\Contracts\Encryption\DecryptException: The payload is invalid.
90% 是以下原因:
-
APP_KEY不一致:本地开发用一个 key,部署时用了另一个(尤其 Docker 或 Forge 部署漏配) - 数据被截断:MySQL 字段类型太小(如
TEXT足够,但误设为VARCHAR(100)),入库时被砍掉末尾 - 多环境共享加密数据但未同步 key:例如 staging 和 production 共用数据库,但 key 不同
- 从 JSON 字段里取值后未 trim 空格或换行 ——
Crypt::decrypt()对空白敏感
快速验证方法:在相同环境下执行 dd(Crypt::encrypt('test'), Crypt::decrypt(Crypt::encrypt('test')));,看是否闭环。不通过就先查 APP_KEY。
加密不是银弹。该脱敏展示的字段(如手机号中间四位),优先用前端掩码或数据库 CONCAT 处理;真正需要可逆加解密的场景才动 Crypt —— 每多一层解密,就多一个密钥管理负担。









