thinkphp 6 不支持数据库字段自动加解密,需手动通过访问器/修改器实现;加密字段无法直接查询,推荐“明文索引+密文存储”双写方案,并注意密钥管理、字符集、性能及兼容性问题。

ThinkPHP 6 的 encrypt 和 decrypt 方法不处理数据库字段自动加解密
直接说结论:ThinkPHP 官方从未提供「模型字段级自动加密存储」功能。所谓“数据库字段加密”,实际要靠开发者自己在存取时手动调用加解密逻辑,或借助事件钩子、访问器/修改器、中间件等机制干预。别被某些博客标题误导——think-encryption 扩展包或自定义驱动也不能让 save() 自动把 id_card 字段塞进 AES 加密再写入。
用模型的 setAttr 和 getAttr 实现字段级加解密
这是最轻量、最可控的方式,适合单个敏感字段(如手机号、身份证号)在读写时透明加解密。核心是重写模型中对应字段的访问器与修改器:
示例:对 user 表的 id_card 字段做 AES-128-CBC 加密(使用 openssl_encrypt):
class User extends Model
{
protected $type = [
'id_card' => 'string',
];
public function setIdCardAttr($value)
{
if (empty($value)) return '';
$key = config('app.aes_key', 'your-32-byte-secret-key-here-xxxxxxxxxx');
$iv = substr(hash('sha256', $key), 0, 16);
return openssl_encrypt($value, 'AES-128-CBC', $key, 0, $iv);
}
public function getIdCardAttr($value)
{
if (empty($value)) return '';
$key = config('app.aes_key', 'your-32-byte-secret-key-here-xxxxxxxxxx');
$iv = substr(hash('sha256', $key), 0, 16);
return openssl_decrypt($value, 'AES-128-CBC', $key, 0, $iv) ?: '';
}
}
注意点:
-
setIdCardAttr在save()或create()时触发,但update(['id_card' => 'xxx'])这种数组方式绕过修改器,不会加密 - 加密后的字符串含不可见字符和
/+==,必须将数据库字段设为TEXT或VARCHAR(255)并用utf8mb4_bin排序规则(避免截断或乱码) - 别把密钥硬编码在模型里;应从配置或环境变量读取,且确保
config('app.aes_key')长度符合算法要求(AES-128 要 16 字节,AES-256 要 32 字节) - IV 必须每次加密固定(否则无法解),但不能全局复用——这里用
hash('sha256', $key)衍生是常见妥协,更安全做法是单独存 IV 字段
WHERE 查询时无法直接用加密字段做条件匹配
加密后字段值是随机字符串,where('id_card', '11010119900307299X') 永远查不到数据。你得手动加密后再查:
$encrypted = openssl_encrypt('11010119900307299X', 'AES-128-CBC', $key, 0, $iv);
User::where('id_card', $encrypted)->find();
但这样有明显问题:
- 业务代码处处要重复加密逻辑,易错且难维护
- 无法利用数据库索引加速查询(加密后失去原始值顺序性,B+ 树索引失效)
- 模糊查询(
like)、范围查询(between)、NULL 判断全部失效
所以真实项目中,身份证这类需查询的字段,更推荐「明文存索引字段 + 密文存主字段」双写方案,例如:id_card_hash 存 SHA256 哈希用于精确查询,id_card_enc 存 AES 加密原文用于展示。
别忽略加解密带来的性能与兼容性代价
每行读写都多一次 OpenSSL 调用,QPS 上千时延迟会明显上升;而且加密函数依赖 PHP 的 OpenSSL 扩展是否启用、版本是否支持指定 cipher。实操前务必确认:
- 运行
php -m | grep openssl确保扩展已加载 - 测试
openssl_get_cipher_methods()是否包含AES-128-CBC - 数据库连接字符集必须是
utf8mb4,否则加密后二进制数据入库可能被截断或转义 - 如果用 Redis 缓存模型实例,加密字段会被序列化成乱码,缓存命中后解密失败——得在缓存前先解密,或改用 JSON 序列化并排除加密字段
真正要落地,还得考虑密钥轮换、加密日志审计、错误降级(比如 OpenSSL 失败时 fallback 到明文告警)这些边界情况。不是加两行 openssl_encrypt 就算完事的。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











