我正在使用 AWS KMS 为每个用户的会话生成唯一的密钥对,然后对定义为使用开放 ssl 加密的每一列进行加密。因此,我实现了以下特征,它加密和解密getEncryptAttributes
在保存/检索时定义的字段:
trait HasEncryption
{
/**
* Set the array of encrypted attributes which will be stored in the database as encrypted text. Ensure the database
* column type to be binary. If you know a precise size, set it like in the respective database migration linked below. Else, use laravel's builtin $table->binary()
* @return string[]
* @see \AddCipherKeyToUsers::up()
*/
abstract public function getEncryptAttributes(): array;
/**
* Init the model event-handlers
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public static function bootHasEncryption()
{
// On save: Encrypt the data and store the keys to decrypt in the database
static::saving(function (Model $model) {
// For all encrypt-attributes, encrypt the data
foreach ($model->getEncryptAttributes() as $attribute) {
if (!is_null($model->getAttribute($attribute))) {
$res = $model->encryptionService->encrypt($model->getAttribute($attribute));
$model->setAttribute($attribute, $res['encrypted']);
}
}
// If something was encrypted, store the encryption keys to the database to use for decryption
if (isset($res)) {
$model->setAttribute('encrypted_key', $res['key']);
$model->setAttribute('encryption_iv', $res['iv']);
}
return $model;
});
// On retreive: Decrypt all attributes using the keys stored above
static::retrieved(function (Model $model) {
// For all encrypt-attributes, decrypt the data with the encryption keys stored on encrypt method
foreach ($model->getEncryptAttributes() as $attribute) {
if (!is_null($model->getAttribute($attribute)) && !is_null($model->getAttribute('encrypted_key')) && !is_null($model->getAttribute('encryption_iv'))) {
$decrypted = $model->encryptionService->decrypt($model->getAttribute($attribute), $model->getAttribute('encrypted_key'), $model->getAttribute('encryption_iv'));
$model->setAttribute($attribute, $decrypted);
}
}
return $model;
});
}
/**
* Get the app's encryption service singelton through magic attribute accessor
*
* @return EncryptionService
*/
public function getEncryptionServiceAttribute(): EncryptionService
{
return app()->make(EncryptionService::class);
}
}
这EncryptionService
是一个单例,它将在会话的整个生命周期中使用相同的密钥。如果会话中没有可用的密钥,它将连接到 AWS STS 并为此会话获取新令牌。
我们不能使用 aurora DB 的默认 AWS 加密,因为数据必须以加密状态通过网络,并且每个用户的数据必须独立加密。
现在在实践中,加密和解密在存储某些东西时起作用,然后再次重新加载和读取数据。但是当我有这样的事情时:
$thing->setAttribute('encrypted_attribute', 'value');
$thing->save();
dump($thing->getAttribute('encrypted_attribute'); // will print ú©N╬B═Ø©ªx\rÒ┤Ò·&
观察者显然会修改加密的saving
属性,但我只想加密实际进入数据库的内容,但保留本地状态。否则,例如多次保存时,它也会再次加密二进制数据,而不是使用实际属性。
有没有办法按照我前进的方向实现这一目标,或者您是否建议采用完全不同的设计来实现部分加密的数据库?
编辑:有时,提问可以帮助我的大脑。我认为雄辩的模型具有finishSave
触发saved
事件的方法。所以我添加了static::saved
trait 并在那里解密。似乎工作。不过,您是否发现此设计存在任何潜在缺陷,或者您是否有经验通常如何实现这一点?