Yii模型中加密字段需在beforeSave加密、afterFind解密,避开getter/setter因绕过验证和查询逻辑;密钥须配置化,用sodium或openssl加密后base64存储;IV应随记录保存,字段类型设为TEXT防截断。

加密字段在 Yii 模型里怎么自动加解密
Yii 本身不内置字段级加密,得靠模型的 beforeSave 和 afterFind 手动介入。核心思路是:存库前加密、查出后解密,让业务代码像操作普通字段一样用。
- 别在数据库层面做透明加密(如 MySQL 的 AES_ENCRYPT),那会破坏 Yii 的查询构建器和验证逻辑
- 加密密钥必须从配置或环境变量读取,绝不能硬编码在模型里
- 推荐用 PHP 7.2+ 的
sodium_crypto_secretbox(更安全)或openssl_encrypt(兼容性好),别用mcrypt(已废弃) - 加密后结果是二进制,必须
base64_encode再存入字符串字段,否则入库失败或乱码
为什么不能只在 get* / set* 里做加解密
单纯重写 getter/setter 看似简单,但会漏掉关键路径:批量赋值(load())、规则验证(rules())、查询条件拼接(where())都绕过它们。
-
load()时原始值直接塞进属性,如果 setter 里加密,那验证器看到的就是密文,校验长度/格式全错 -
where(['encrypted_field' => $value])会拿明文去比密文,永远查不到 - JSON 序列化、日志打印、调试输出也会暴露明文,失去加密意义
- 正确做法是把加解密逻辑下沉到
beforeSave和afterFind,覆盖所有数据进出模型的入口
beforeSave 里加密要避开哪些坑
加密动作必须严格限定在新增或修改该字段时触发,否则会导致重复加密(比如模型 save 多次)。
- 用
$this->isAttributeChanged('field_name')判断字段是否真变了,而不是无脑加密 - 注意脏字段检测失效场景:如果字段初始值就是空字符串,改为空格再改回空字符串,
isAttributeChanged可能返回 false - 加密前先
trim()或标准化输入,避免前后空格导致解密后多出空白 - 加密失败(如密钥为空、OpenSSL 报错)必须抛异常,不能静默跳过,否则数据永久丢失
- 示例片段:
public function beforeSave($insert) { if ($this->isAttributeChanged('api_token') && $this->api_token !== null) { $key = Yii::$app->params['encrypt_key']; $this->api_token = base64_encode(openssl_encrypt($this->api_token, 'AES-128-CBC', $key, 0, $this->getIv())); } return parent::beforeSave($insert); }
查询时解密失败常见原因
解密失败通常不是算法问题,而是上下文不一致:IV 不匹配、密钥不同、base64 解码失败或字段被截断。
- IV 必须和加密时完全一致,且不能写死——建议存进数据库同条记录的另一个字段(如
api_token_iv),或用固定盐 + 字段值生成 - 数据库字段类型至少设为
TEXT,VARCHAR(255)容易截断 base64 密文(AES-128-CBC 加密后 base64 长度约 172 字符,但有 padding 波动) -
afterFind中解密前先检查字段是否为空、是否 base64 编码有效(base64_decode($v, true) === false),避免崩溃 - 关联查询(
with())时,如果关联模型也加密,确保每个模型的afterFind独立执行,不要互相干扰










