Skip to content

Commit

Permalink
feat: prepare version 2.0
Browse files Browse the repository at this point in the history
- Add eloquent builder support
- Add datetime support, refactor casts
- Update encryption to always have the same result for the same key + string
- Add tests
- Update README
  • Loading branch information
joelwmale committed Nov 2, 2023
1 parent b8e6fec commit d34bebe
Show file tree
Hide file tree
Showing 18 changed files with 447 additions and 58 deletions.
4 changes: 0 additions & 4 deletions .styleci.yml

This file was deleted.

64 changes: 44 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
[![Total Downloads](https://img.shields.io/packagist/dt/joelwmale/laravel-encryption.svg?style=flat-square)](https://packagist.org/packages/joelwmale/laravel-encryption)
![GitHub Actions](https://github.com/joelwmale/laravel-encryption/actions/workflows/main.yml/badge.svg)

A package to easily encrypt & decrypt database data in Laravel using built in Laravel functions.
A package to easily encrypt & decrypt model fields in Laravel using OpenSSL.

## Key Features

- Encrypt and decrypt module attributes easily
- Minimal configuration
- Uses in-built Laravel functions for encrypting and decrypting
- Easily select encrypted fields and their casts
- Comes with a selection of eloquent builders for easy queries
- Growing support for casting encryptable data

## Installation

Expand All @@ -31,31 +31,52 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Joelwmale\LaravelEncryption\Traits\EncryptsAttributes;

class SensitiveModel extends Model
class User extends Model
{
use EncryptsAttributes;

protected $encryptableAttributes = [
'field_one',
'field_two',
'date_one',
'first_name',
'last_name',
'date_of_birth',
'email_verified_at',
];

protected $encryptableCasts = [
'date_one' => 'date',
'date_of_birth' => 'date',
'email_verified_at' => 'datetime'
];
}
```

## Configuration

### Publshing configuration
### Publshing The Configuration

The configuration file looks like this:

```php
return [
'laravel_encryption_enabled' => env('LARAVEL_ENCRYPTION_ENABLED', true),
/**
* Enable or disable the encryption.
*/
'enabled' => env('LARAVEL_ENCRYPTION_ENABLED', true),

/**
* The encryption key.
*
* Default: your app key.
*/
'key' => env('LARAVEL_ENCRYPTION_KEY', null),

/**
* The encryption cipher.
*
* Supports any cipher method supported by openssl_get_cipher_methods().
*
* Default: AES-256-CBC.
*/
'cipher' => env('LARAVEL_ENCRYPTION_CIPHER', 'AES-256-CBC'),
];
```

Expand All @@ -65,36 +86,39 @@ If you need to make any changes to the configuration, feel free to publish the c
php artisan vendor:publish --provider="Joelwmale\LaravelEncryption\LaravelEncryptionServiceProvider"
```

### Configure Attributes to be Encryped/Decrypted
### Configure Model Attributes To Be Encrypted

Add all your database columns that require this functionality into this array.
This package will only encrypt fields within the `$encryptableAttributes` array, leaving the rest of the model unencrypted and untouched.

```php
protected $encryptableAttributes = [
'field_one',
'field_two',
'date_one',
'first_name',
'last_name',
];
```

This is useful for scenarios where compliance only requires you encrypting specific values and not your entire database.

### Casting Encrypred/Decrypted values
### Casting Encrypted Values

Due to the fact that encryped values can be quite long, you will need to store them as `text` fields, meaning you have to give way to native column types.
Due to the fact that encrypted values can be quite long, you will need to store them as `text` fields, meaning you have to give way to native column types.

Also due to the way Laravel handles casting (and the priority it takes) you cannot right now use native casts with encrypted fields (open to a PR if someone can solve this)
Also due to the way Laravel handles casting (and the priority it takes) you cannot right now use native casts with encrypted fields.

So we use our own array to determine what fields should be casted to what:

```php
protected $encryptableCasts = [
'date_one' => 'date',
'date_of_birth' => 'date',
];
```

#### Supported casts

Not all casts are supported right now, but again, open to PRs to add more casts
Cast support is still growing, and will be added as time goes on.

- date
- datetime

### Testing

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "joelwmale/laravel-encryption",
"description": "A package to easily encrypt and decrypt database data in Laravel",
"description": "A package to easily encrypt & decrypt model fields in Laravel using OpenSSL.",
"keywords": [
"joelwmale",
"laravel-encryption"
Expand Down
21 changes: 20 additions & 1 deletion config/config.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
<?php

return [
'laravel_encryption_enabled' => env('LARAVEL_ENCRYPTION_ENABLED', true),
/**
* Enable or disable the encryption.
*/
'enabled' => env('LARAVEL_ENCRYPTION_ENABLED', true),

/**
* The encryption key.
*
* Default: your app key.
*/
'key' => env('LARAVEL_ENCRYPTION_KEY', null),

/**
* The encryption cipher.
*
* Supports any cipher method supported by openssl_get_cipher_methods().
*
* Default: AES-256-CBC.
*/
'cipher' => env('LARAVEL_ENCRYPTION_CIPHER', 'AES-256-CBC'),
];
12 changes: 12 additions & 0 deletions src/Builders/EloquentBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Joelwmale\LaravelEncryption\Builders;

use Illuminate\Database\Eloquent\Builder;

class EloquentBuilder extends Builder
{
use OrderByEncrypted;
use WhereEncrypted;
use WhereInEncrypted;
}
17 changes: 17 additions & 0 deletions src/Builders/OrderByEncrypted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Joelwmale\LaravelEncryption\Builders;

trait OrderByEncrypted
{
public function orderByEncrypted($column, $direction = 'desc')
{
$results = $this->get();

$results = $results->sortBy(function ($result) use ($column) {
return $result->$column;
}, SORT_REGULAR, $direction === 'desc');

return $results;
}
}
22 changes: 22 additions & 0 deletions src/Builders/WhereEncrypted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Joelwmale\LaravelEncryption\Builders;

use Joelwmale\LaravelEncryption\Services\EncryptService;

trait WhereEncrypted
{
public function whereEncrypted($column, $value = null, $operator = '=')
{
$encryptedValue = EncryptService::encrypt($value);

return self::where($column, $operator, $encryptedValue);
}

public function orWhereEncrypted($column, $value = null, $operator = '=')
{
$encryptedValue = EncryptService::encrypt($value);

return self::orWhere($column, $operator, $encryptedValue);
}
}
52 changes: 52 additions & 0 deletions src/Builders/WhereInEncrypted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace Joelwmale\LaravelEncryption\Builders;

use Joelwmale\LaravelEncryption\Services\EncryptService;

trait WhereInEncrypted
{
public function whereInEncrypted($column, $values = [])
{
$encryptedValues = [];

foreach ($values as $value) {
$encryptedValues[] = EncryptService::encrypt($value);
}

return self::whereIn($column, $encryptedValues);
}

public function orWhereInEncrypted($column, $values = [])
{
$encryptedValues = [];

foreach ($values as $value) {
$encryptedValues[] = EncryptService::encrypt($value);
}

return self::orWhereIn($column, $encryptedValues);
}

public function whereNotInEncrypted($column, $values = [])
{
$encryptedValues = [];

foreach ($values as $value) {
$encryptedValues[] = EncryptService::encrypt($value);
}

return self::whereNotIn($column, $encryptedValues);
}

public function orWhereNotInEncrypted($column, $values = [])
{
$encryptedValues = [];

foreach ($values as $value) {
$encryptedValues[] = EncryptService::encrypt($value);
}

return self::orWhereNotIn($column, $encryptedValues);
}
}
15 changes: 15 additions & 0 deletions src/Casts/Date.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Joelwmale\LaravelEncryption\Casts;

class Date
{
public static function handle($value)
{
if (class_exists('Carbon\Carbon')) {
return \Carbon\Carbon::parse($value);
}

return new \DateTime($value);
}
}
15 changes: 15 additions & 0 deletions src/Casts/DateTime.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Joelwmale\LaravelEncryption\Casts;

class DateTime
{
public static function handle($value)
{
if (class_exists('Carbon\Carbon')) {
return \Carbon\Carbon::parse($value);
}

return new \DateTime($value);
}
}
40 changes: 40 additions & 0 deletions src/Services/EncryptService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace Joelwmale\LaravelEncryption\Services;

class EncryptService
{
public static function getKey()
{
return config('laravel_encryption.key', config('app.key'));
}

public static function getCipher()
{
$cipher = strtolower(config('laravel_encryption.cipher', 'AES-256-CBC'));

if (! in_array($cipher, openssl_get_cipher_methods())) {
throw new \Exception('The cipher method "'.$cipher.'" is not supported.');
}

return $cipher;
}

public static function encrypt($string): string
{
$iv = substr(md5(self::getKey()), 0, 16);

$encrypted = openssl_encrypt($string, self::getCipher(), self::getKey(), 0, $iv);

return base64_encode($encrypted);
}

public static function decrypt($encryptedString): string
{
$iv = substr(md5(self::getKey()), 0, 16);

$decrypted = openssl_decrypt(base64_decode($encryptedString), self::getCipher(), self::getKey(), 0, $iv);

return $decrypted;
}
}
28 changes: 5 additions & 23 deletions src/Support/HandleCastableAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,18 @@

namespace Joelwmale\LaravelEncryption\Support;

use Joelwmale\LaravelEncryption\Casts\Date;

class HandleCastableAttributes
{
public static function handle(array $encryptableCasts, string $attribute, $value)
{
$castType = $encryptableCasts[$attribute] ?? null;

if ($castType === 'datetime') {
if (class_exists('Carbon\Carbon')) {
return \Carbon\Carbon::parse($value);
}

return new \DateTime($value);
}

if ($castType === 'date') {
if (class_exists('Carbon\Carbon')) {
return \Carbon\Carbon::parse($value);
}

return new \DateTime($value);
}

if ($castType === 'time') {
if (class_exists('Carbon\Carbon')) {
return \Carbon\Carbon::parse($value);
}

return new \DateTime($value);
if ($castType === 'date' || $castType === 'datetime') {
return Date::handle($value);
}

return $value;
throw new \Exception('The cast type "'.$castType.'" is not supported.');
}
}
Loading

0 comments on commit d34bebe

Please sign in to comment.