A record delegates its encryption context to a parent so all of a user's data across multiple tables shares one DEK. This is the HIPAA primitive for one-shot crypto-shred.
Pattern
use Crumbls\Sealcraft\Casts\Encrypted;
use Crumbls\Sealcraft\Concerns\HasEncryptedAttributes;
use Crumbls\Sealcraft\Values\EncryptionContext;
class OwnedUser extends Model
{
use HasEncryptedAttributes;
protected string $sealcraftStrategy = 'per_row';
protected $casts = ['ssn' => Encrypted::class, 'dob' => Encrypted::class];
}
class OwnedRecord extends Model
{
use HasEncryptedAttributes;
protected $casts = ['body' => Encrypted::class];
public function owner()
{
return $this->belongsTo(OwnedUser::class);
}
public function sealcraftContext(): EncryptionContext
{
return $this->owner->sealcraftContext();
}
}
The OwnedUser is the root -- its per-row DEK encrypts its own columns. OwnedRecord overrides sealcraftContext() to return the owner's context. Every related row encrypts under the same DEK.
Right-to-be-forgotten in one shred
php artisan sealcraft:shred \
"App\\Models\\OwnedUser" \
<sealcraft_key>
One shred destroys the DEK that protected the user's columns and every dependent table that delegates to them. See Crypto-shred for the full story.
Loading the parent efficiently
Eager load the owner relationship when you read delegated rows, otherwise every sealcraftContext() call triggers a lazy-load:
OwnedRecord::with('owner')->get();
The DEK cache absorbs repeated unwraps within a request, but the extra query round-trips are pure waste.
Contributors
Thank you to everyone who has contributed to this package. Every pull request, bug report, and idea makes a difference.