Laravel database data encryption with full text search
paulodiff/rainbow-table-index is a Laravel package for laravel database data encryption with full text search.
It currently has 0 GitHub stars and 26 downloads on Packagist.
Install it with composer require paulodiff/rainbow-table-index.
Discover more Laravel packages by paulodiff
or browse all Laravel packages to compare alternatives.
Last updated
Database data encryption is a must-have and it's very simple to encrypt data in database using Laravel Eloquent ORM casts:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
protected $casts = [
'title' => 'encrypted',
];
}
With encrypted data fields, you can search only with exact field name value:
$p = Post::where('title','Beautiful Post'); // OK
but it's not possible using LIKE operator:
$p = Post::where('title','LIKE','%Bea%'); // WRONG!
because the real data are stored in database table in encrypted form:
"id";"title"
"1";"7OioIr/njEtH0fFHDoVopndh2z/yfQ4r8i40QlZjaITQ7Mh5QwnhH/Gjug=="
Laravel includes Eloquent, an object-relational mapper (ORM). By extending the Eloquent classes, and using the Laravel Traits, it is possible to build a library that allows you to perform full text searches on encrypted text using a "Rainbow Table Index".
The goal: perform a search using LIKE operator in encrypted data. Building an alternative index (Rainbow Table Index) with a library, is possible to index encrypted values and query this values.
This is a working sample, with a Post model with fields "id" and "title". We create a Post item:
Post::create(['id'=> 88, 'title' => 'Beauty']);
The resultant RainbowTableIndex (with the minimum token size is 3):
Rainbow Table Index data
post:title BEA 88
post:title EAU 88
post:title AUT 88
post:title UTY 88
post:title BEAU 88
post:title EAUT 88
post:title AUTY 88
post:title BEAUTY 88
A LIKE search on an encrypted field
$p = Post::where('title','LIKE','%BEA%');
is automatically converted first in a search on Rainbow Table Index and next the ids of token 'BEA' the query become:
$p = Post::whereIn('id', [88]);
A "Rainbow Table Index" is a index that is builded by RainbowTableService. An Eloquent model is an oject that interact with a database. This is an example:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Paulodiff\RainbowTableIndex\RainbowTableIndexTrait;
class Author extends Model
{
use RainbowTableIndexTrait;
protected $fillable = [
'name'
];
// Rainbow table configuration
public static $rainbowTableIndexConfig = [
'table' => [
'primaryKey' => 'id',
'tableName' => 'authors',
],
'fields' => [
[
'fName' => 'name',
'fType' => 'ENCRYPTED_FULL_TEXT',
'fSafeChars' => " 'à èéìòùqwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM.",
'fTransform' => 'UPPER_CASE',
'fMinTokenLen' => 3,
]
]
];
}
This model define an Author with one encrypted field: 'name'. The RainbowTableIndex configuration:
for each field to encrypt and index with RainbowTableIndex you define
When an Eloquent Model are created a row are inserted in database table end all field configured in rainbowTableIndexConfig are encrypted. Next all entries are created in RainbowTableIndex.
Author::create(['id'=> 1, 'name' => 'Billy White']);
Show the database authors data
id;name;
1ZXlKcGRpSTZJa1JEUTB4cWNuTmxTRzFOSzFZMVowNUxiazVITjFFOVBTSXNJblpoYkhWbElqb2lUMFp2V1hwRVlsSkVWSFZ3TDBGNldURklXRzVuVEZnMk16QXhVQ3RpY1dWUVowMTJha1V5WkdOeFNUMGlMQ0p0WVdNaU9pSm1NbUpqWTJSaE5HSTNORFUxTlRjME16ZGlOalEyWVRjd05tRXdORFF4T1RCbU9EZGtaREl6TlRsallqa3laR0UwTTJJelkyVTFaV1kyWWpFd00yUXpJaXdpZEdGbklqb2lJbjA9;
Show the rainbow table index data related to field 'name'.
table name: rt_6f48819d50e9b840e0c9a5e4a1375145
table data:
rt_key;rt_value
7327b0fa163b48eb9dca10700a55d717406e25f24da3223d78257bb55cbd5e77;1
3da30095b68d8df0348b86660aa9f8c33617b2f9bbecae4ef342bd708fcbf1fc;1
89aa7c63a9087d2f4da32e49d5949024d9c9a41cde32bb0c80ea3edc28c9384b;1
979d88c360ae25de60c5afdf97e48760e1dbff047c595431272d3c69c0056484;1
91024262f3055431a4aca784cca25fde500a3e6a498e12717d498667c322fec3;1
bf64a77aef0838485fff59fde2c889bea48c451ad7c157f005d5b93bf686ddb1;1
3d5834be7f15366d5a5d9c823d3e39713f932b6378f210cf3e842417bd20f81f;1
334dea03de177f5f22bfd8a571f4f1c696be6d08fa135a161680a3b0dfa1302c;1
4b0cdc4e3d3c57e606bdd14b4c69b4eb0d6535947290429fa135231b150b1173;1
0f91f40beba2d9e7400653124342097b17a05d62ddbc4fe8b4205d0a256344e2;1
b5da40127d66e01bc324557fa056c3ed89b7c44b832399c2373df3dc7ea60b29;1
fad5fc5354ddbd9adaccd041f96e7aa4c8a460d4acd94c0eb099ef637c6fb6df;1
f0647065844b606d53db1adef3d540404cc00e9ab040d14c422f6f4b9f5e4979;1
a5fd15ffb7150fb7dec15b758f3a118d724b11c67a4483ac2ec6d8b631d0320b;1
...
When a data need to be inserted in RainbowTableIndex a trasformation is applied:
An Example:
Address data : "77712 O'Conner Plain Apt. 996 nw"
If the model are updated or deleted the Rainbow Table index are updated.
RainbowTableIndex characteristics:
The library can be used in contexts where it is necessary to guarantee the privacy of sensitive data, and it is necessary to perform searches with LIKE, for example:
composer create-project laravel/laravel rainbow-table-index-app
cd rainbow-table-index-app
composer require paulodiff/rainbow-table-index
php artisan vendor:publish --provider="Paulodiff\RainbowTableIndex\RainbowTableIndexServiceProvider" --tag="config"
php artisan RainbowTableIndex:checkConfig
#to enable debug information DEBUG_LEVEL = debug
#for debugging RainbowIndexTable without encryption with this options: RAINBOW_TABLE_INDEX_ENCRYPT=true
I'ts all ok! you are ready to go!
Warning! This procedure build a table in the datbase:
and seed this tables with 1000 rows of data for testing.
in app/models copy Author.php and Posts.php from ...
composer create-project laravel/laravel rainbow-table-index-app
cd rainbow-table-index-app
composer require paulodiff/rainbow-table-index
php artisan RainbowTableIndex:keyGenerator
.env
RAINBOW_TABLE_INDEX_ENCRYPT=true
RAINBOW_TABLE_INDEX_PREFIX=rti // TODO
php artisan RainbowTableIndex:checkConfig
php artisan make:migration create_authors_table
edit migration file
public function up()
{
Schema::create('authors', function (Blueprint $table) {
$table->increments('id');
$table->text('name');
$table->text('name_enc'); // for test only
$table->text('card_number');
$table->text('card_number_enc'); // for test only
$table->text('address');
$table->text('address_enc'); // for test only
$table->text('role');
$table->text('role_enc'); // for test only
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('authors');
}
migrate database
php artisan migrate
create Author model
edit App/Models/Author.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Paulodiff\RainbowTableIndex\RainbowTableIndexTrait;
class Author extends Model
{
use HasFactory;
use RainbowTableIndexTrait;
public static $rainbowTableIndexConfig = [
'table' => [
'primaryKey' => 'id',
'tableName' => 'authors',
],
'fields' => [
[
'fName' => 'name_enc',
'fType' => 'ENCRYPTED_FULL_TEXT',
'fSafeChars' => " 'à èéìòùqwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM.",
'fTransform' => 'UPPER_CASE',
'fMinTokenLen' => 3,
],
[
'fName' => 'address_enc',
'fType' => 'ENCRYPTED_FULL_TEXT',
'fSafeChars' => " 'à èéìòùqwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM.",
'fTransform' => 'UPPER_CASE',
'fMinTokenLen' => 4,
],
[
'fName' => 'card_number_enc',
'fType' => 'ENCRYPTED_FULL_TEXT',
'fSafeChars' => '1234567890',
'fTransform' => 'NONE',
'fMinTokenLen' => 4,
],
[
'fName' => 'role_enc',
'fType' => 'ENCRYPTED',
'fSafeChars' => ' à èéìòùqwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM.',
'fTransform' => 'ENCRYPTED_FULL_TEXT',
'fMinTokenLen' => 0,
],
]
];
}
run db seed (with 100 rows)
php artisan RainbowTableIndex:dbSeed 100
run search test and metrics (with 100 )
php artisan RainbowTableIndex:dbCrud 100
This is a web app demo crud with 5 tables:
Player
'player_id'
'player_name', // ENCRYPTED
'player_address',
'player_credit_card_no', // ENCRYPTED
'player_phone',
PlayerRole
'player_role_id'
'player_role_description', // ENCRYPTED
'player_role_salary',
Team
'team_id', // ENCRYPTED
'team_name',
'team_type_id', (1-1 with TeamType)
TeamType
'team_type_id'
'team_type_description', // ENCRYPTED
'team_type_rules',
Roster
'roster_id'
'roster_description', // ENCRYPTED
'roster_player_id',
'roster_team_id',
'roster_player_role_id',
'roster_amount', // ENCRYPTED
Livewire installation composer require livewire/livewire
Set Mysql Set .env Copy Model and rename path foreach model Copy resource Copy web.app
php artisan RainbowTableIndex:dbCrud 100
php artisan optmize
php artisan serve
composer require livewire/livewire
php artisan make:livewire posts
app/Http/Livewire/Authors.php
resources/views/livewire/authors.blade.php
# Security customization
Laravel Encrypt/Decrypt functions
Hash
# Service operation
You can rebuild all indexed data for an intaciated model:
```php
$a = Author::where('id', 3)->first();
$r1 = $a->rebuildRainbowIndex();
or you can rebuild ALL indexes for ALL instances of a model
$r2 = Author::rebuildFullRainbowIndex();
Performance system characterirtcs
Access time to an instance is different. My test:
It is possible to calculate the number of rows of an index for each data to be indexed ($w is token length and $s is a string to tokenize)
for ($w=3;$w<= strlen($s); $w++) $numOfEntries += (strlen($s) + 1 - $w);
sanitize_string
Cons
