Laravel怎么实现数据加解密_Laravel使用Crypt和Hash加密解密【安全】

2次阅读

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

Laravel怎么实现数据加解密_Laravel使用Crypt和Hash加密解密【安全】

为什么不能用 Crypt 加密用户密码

直接用 Crypt::encrypt() 存密码是危险操作。它可逆,一旦 APP_KEY 泄露,所有密码瞬间解出;且不带盐、不加迭代,扛不住彩虹表和暴力破解。

正确做法永远是用 Hash::make() —— 它底层调用 bcrypt(PHP 的 password_hash()),自动加盐、自适应迭代次数,验证时用 Hash::check()

use IlluminateSupportFacadesHash;  $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() 解密失败会抛出 IlluminateContractsEncryptionDecryptException,必须捕获:

use IlluminateSupportFacadesCrypt; use IlluminateContractsEncryptionDecryptException;  try {     $decrypted = Crypt::decrypt($encrypted_value); } catch (DecryptException $e) {     // 记录日志,返回默认值或报错 }
  • 加密后数据是 base64 编码字符串,长度远大于原文,别存进定长字段(如 VARCHAR(20)
  • 数据库迁移时若改过 APP_KEY,旧数据将永久无法解密 —— 生产环境换 key 前必须批量解密重加密
  • Crypt::encryptString()Crypt::decryptString() 专用于字符串,避免序列化开销

如何安全地在数据库中加解密字段

不要裸写 Crypt::encrypt() 在模型里。用 laravelcasts + 自定义 cast 更可控:

php artisan make:cast EncryptedStringCast

然后在 app/Casts/EncryptedStringCast.php 中:

namespace AppCasts;  use IlluminateContractsDatabaseEloquentCastsAttributes; use IlluminateSupportFacadesCrypt; use IlluminateContractsEncryptionDecryptException;  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' => AppCastsEncryptedStringCast::class, ];
  • 这样读写都透明,$user->id_number 拿到的是明文,存库自动加密
  • 避免在 accessor/mutator 中硬编码加密逻辑,不易复用也不易测试
  • 如果字段可能为空,get() 方法里必须判空,否则 Crypt::decrypt(null) 报错

常见错误:DecryptException 怎么排查

典型报错信息:IlluminateContractsEncryptionDecryptException: 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 —— 每多一层解密,就多一个密钥管理负担。

text=ZqhQzanResources