ThinkPHP 8.0 数据加密需分场景:密码必须用 Hash::make() 哈希(bcrypt),禁用可逆加密;身份证等敏感字段用 AES-256-GCM 可逆加密,密钥须由 sodium_crypto_aead_aes256gcm_keygen() 生成并从环境变量加载,模型层自动加解密,数据库查询做字段脱敏。

ThinkPHP 8.0 实现数据加密存储,核心不是“加个函数就完事”,而是要分清场景、选对层级、避免硬编码密钥——明文存密码、用 base64 当加密、把密钥写死在 config.php 里,这些都属于典型误用。
区分加密与哈希:密码类字段必须用哈希
用户密码、支付 PIN 等不可逆凭证,绝不能使用可解密的加密算法(如 AES),而应严格使用带盐哈希:
- TP8 内置 think\facade\Crypt 是为可逆加密设计的,不适用于密码;
- 密码必须走 think\facade\Hash::make($password),它默认使用 bcrypt(兼容 PHP 8.0+),自动加盐且防爆破;
- 验证时用 Hash::check($input, $hashed),不要自己拼接 salt 或调用 password_verify。
敏感业务字段用可逆加密:AES-256-GCM 最稳妥
身份证号、手机号、银行卡号等需后续解密使用的字段,推荐 AES-256-GCM 模式(认证加密,防篡改):
- 先确保已安装 ext-sodium(PHP 7.2+ 默认启用),比 mcrypt 更安全可靠;
- 密钥必须由 sodium_crypto_aead_aes256gcm_keygen() 生成,长度 32 字节,严禁手写或 base64 编码字符串充数;
- 加密示例:注意 nonce 必须每次唯一
$key = sodium_crypto_aead_aes256gcm_keygen();
$nonce = random_bytes(SODIUM_CRYPTO_AEAD_AES256GCM_NPUBBYTES);
$ciphertext = sodium_crypto_aead_aes256gcm_encrypt($data, '', $nonce, $key);
// 存库时需同时保存 $ciphertext 和 $nonce(建议拼接或分字段)
立即学习“PHP免费学习笔记(深入)”;
- 解密时严格校验返回值,sodium_crypto_aead_aes256gcm_decrypt() 失败直接返回 false,不可忽略;
- 密钥管理:生产环境务必使用独立密钥管理系统(如 HashiCorp Vault),至少也要将密钥存在环境变量中,通过 $_ENV['APP_ENCRYPT_KEY'] 读取,禁止写入代码或配置文件。
模型层自动加解密:避免手动重复操作
在模型中封装逻辑,让敏感字段读写透明化:
- 重写模型的 setAttr 方法处理写入加密:
public function setIdCardAttr($value)
{
if (empty($value)) return null;
$key = $this->getEncryptKey();
$nonce = random_bytes(SODIUM_CRYPTO_AEAD_AES256GCM_NPUBBYTES);
$cipher = sodium_crypto_aead_aes256gcm_encrypt($value, '', $nonce, $key);
return base64_encode($nonce . $cipher); // 合并存储
}
- 重写 getAttr 处理解密:
public function getIdCardAttr($value, $data)
{
if (empty($value)) return null;
$raw = base64_decode($value);
$nonce = mb_substr($raw, 0, SODIUM_CRYPTO_AEAD_AES256GCM_NPUBBYTES, '8bit');
$cipher = mb_substr($raw, SODIUM_CRYPTO_AEAD_AES256GCM_NPUBBYTES, null, '8bit');
$key = $this->getEncryptKey();
return sodium_crypto_aead_aes256gcm_decrypt($cipher, '', $nonce, $key) ?: null;
}
- 密钥获取方法 getEncryptKey() 应从环境变量或容器中安全加载,不缓存明文密钥到静态属性。
数据库字段级脱敏:查询时不暴露原文
即使加密了,某些接口也不该返回原始敏感字段。可用全局作用域 + 访问器控制:
- 在模型中定义访问器,配合 hidden 属性屏蔽原始字段:
protected $hidden = ['id_card_encrypted'];
protected $appends = ['id_card'];
public function getIdCardAttribute()
{
return $this->id_card_encrypted ? '[已加密]' : null;
}
- 若需前端展示部分脱敏(如 110101******1234),可在访问器中解密后截取,但注意性能和权限校验——仅限授权角色可见完整信息。



















