Skip to content

Commit

Permalink
Update readme with more info + rewrite some sections [ci skip]
Browse files Browse the repository at this point in the history
  • Loading branch information
JackNoordhuis committed Mar 3, 2019
1 parent bd1b358 commit 266d04b
Showing 1 changed file with 120 additions and 42 deletions.
162 changes: 120 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,64 @@ _A package for automatically hashing Eloquent attributes!_

[![Build Status](https://travis-ci.org/JackNoordhuis/laravel-database-hashing.svg?branch=master)](https://travis-ci.org/JackNoordhuis/laravel-database-hashing)

The purpose of this project is to create a set-it-and-forget-it package that can be installed without much effort to hash Eloquent model attributes stored in your database tables.
The purpose of this library is to create a set-it-and-forget-it package that can be installed without much effort to hash
eloquent model attributes stored in your database tables.

When enabled, it automagically begins hashing data as it is stored in the model attributes so that you can still perform lookups for data in your database.
When enabled, this package will automatically hash the data assigned to attributes that you've specified as they are updated.
This allows you to hide the plain text value of attributes and maintain the ability search the database for
the value (the same input data will always provide the same hash).

All data is hashed with an application specific salt which is specified in the configuration or environment files so hashing the same data with a different salt in another application will result in a different output, this adds another layer of complexity for attackers who try to brute force your data.
All data hashed by this package will have an application specific salt which is specified in the configuration or environment
files, so hashing the same data with a different salt in another application will result in a different output. This adds
layer of complexity/protection against attackers who try to reconstruct your data by attempting to brute force a hash.
If this is not enough, this package also supports providing a secondary salt on top of the application salt, but this cannot
be configured to automatically apply to attributes out of the box.

## Installation

### Step 1: Composer

Via command line:
```bash
$ composer require jacknoordhuis/laravel-database-hashing
$ composer require jacknoordhuis/laravel-database-hashing 1.0.*
```
Or add the package to your `composer.json`:
```json
{
"require": {
"jacknoordhuis/laravel-database-hashing": "*"
"jacknoordhuis/laravel-database-hashing": "1.0.*"
}
}
```

### Step 2: Enable the package

This package implements Laravel 5.5's auto-discovery feature. After you install it the package provider and facade are added automatically.
This package implements Laravel 5.5's package auto-discovery feature. After you install it the package provider and facade
are registered automatically.

If you would like to declare the provider and/or alias explicitly, you may do so by first adding the service provider to your `config/app.php` file:
If you would like to explicitly declare the provider and/or alias, you can do so by first adding the service provider to
your `config/app.php` file:
```php
'providers' => [
//
jacknoordhuis\database\hashing\HashingServiceProvider::class,
\jacknoordhuis\database\hashing\HashingServiceProvider::class,
];
```
And then add the alias to your `config/app.php` file:
```php
'aliases' => [
//
'DatabaseHashing' => jacknoordhuis\database\hashing\HashingFacade::class,
'DatabaseHashing' => \jacknoordhuis\database\hashing\HashingFacade::class,
];
```

### Step 3: Configure the package

Publish the package config file:
```bash
$ php artisan vendor:publish --provider="jacknoordhuis\database\hashing\HashingServiceProvider"
```
You may now enable automagic hashing of Eloquent models by editing the `config/database-hashing.php` file:
You may now enable automatic hashing of eloquent models by editing the `config/database-hashing.php` file:
```php
return [
"enabled" => env("DB_HASHING_ENABLED", true),
Expand All @@ -64,60 +74,128 @@ DB_HASHING_ENABLED=true

## Usage

Use the `HasHashedAttributes` trait in any Eloquent model that you wish to apply hashing to and define a `protected $hashing` array containing a list of the attributes to hash.
Use the `HasHashedAttributes` trait in any eloquent model that you wish to apply automatic attribute hashing to and define
a `protected $hashing` array containing an array of the attributes to automatically hash.

For example:
```php
use jacknoordhuis\database\hashing\traits\HasHashedAttributes;

class User extends Eloquent {
use HasHashedAttributes;

/**
* The attributes that should be hashed on save.
*
* @var array
*/
protected $hashing = [
"username_lookup",
];
}
use jacknoordhuis\database\hashing\traits\HasHashedAttributes;

class User extends Model
{
use HasHashedAttributes;

/**
* The attributes that should be hashed when set.
*
* @var array
*/
protected $hashing = [
'username_lookup',
];
}
```

### Looking up hashed values
You can lookup hashed values in your database tables by simply hashing the value you're searching for as the resulting hash will be the same. You can also optionally provide a salt modifier when hashing data directly, which adds another level of complexity on top of the application-level salt.

You can lookup hashed values in your database tables by simply hashing the value you're searching for, as the resulting
hash will always be the same.
```php
// Assign a hashed value of the username to a users username_lookup attribute with the password
// as a salt modifier so we can only ever re-create the hash when the user provides their password.
$user->username_lookup = $user->hashAttribute($username, $password);
$user->username_lookup = $request->get('username'); //the username_lookup attribute will be automatically hashed


// And when a user provides their password when logging in we can replicate the hash and search for
// the user in the database.
User::where("username_lookup", "=", \DatabaseHashing::create($request->get("username"), $request->get("password")));
//when our user tries to login we just search the database for the hashed value of their username
$user = User::where('username_lookup', DatabaseHashing::create($request->get("username"))->first();
```

You can also optionally provide a salt modifier when hashing data directly, which adds another
level of complexity/security on top of the application-level salt.
```php
//with a salt modifier so we can only ever re-create the hash when the user provides their email or we could store an
//encrypted copy ourselves with another package
$user->username_lookup = $request->get('username'); //set the attribute, then hash manually because we use a modifier
$user->hashAttribute('username_lookup', $request->get('username')); //this time add the plain text email as a salt modifier


//when a user provides their email when logging in, we can replicate the hash and search for the user in the database.
$user = User::where('username_lookup', DatabaseHashing::create($request->get("username"), $request->get("email")))->first();
```

## FAQ's

### Can I manually hash arbitrary data?

Yes! You can manually encrypt or decrypt data using the functions `hashAttribute()` on models and `\DatabaseHashing::create()` globally. For example:
Yes! You can manually hash any string using the `DatabaseHashing::create()` global facade.

```php
// With models using the HasHashedAttributes trait:
$user = new User();
$hashedEmail = $user->hashAttribute(Input::get("email"));
For example:

// or
```php
//hash with only application salt
$hashedEmail = DatabaseHashing::create(Input::get('email'));

// Globally using the DatabaseHasing facade:
$hashedEmail = \DatabaseHashing::create(Input::get("email"));
//hash with application salt AND salt modifier
$hashedEmail = DatabaseHashing::create(Input::get("email"), Input::get('password'));
```

### Can I hash all my model data?

No! The hashing process is irreversible, meaning it should only be used for creating (pseudonymous) identifiers so that it's still possible to look up data in your database. If you want to *encrypt* your data use a package like [this](https://github.com/austinheap/laravel-database-encryption).
No! The hashing process is irreversible, meaning it should only be used for creating (pseudonymous) identifiers so that
it is still possible to look up data in your database. If you want to *encrypt* your data use a package like
[laravel-database-encryption](https://github.com/austinheap/laravel-database-encryption).

### Should I hash numeric auto-incrementing identifiers?

Probably not. If all data stored in your database is encrypted or hashed then the numeric identifier is effectively anonymous (it's really pseudonymous) so there is no way to associate any human readable data with the identifier. There are other reasons for not hashing or encrypting the primary key in your database, and you can read about those [here](https://stackoverflow.com/a/34423898).
Probably not. If all data stored in your database is encrypted or hashed then the numeric identifier is effectively anonymous
(it's really pseudonymous) so there is no way to associate any human readable data with the identifier. There are other
reasons for not hashing or encrypting the primary key in your database, and you can read about those [here](https://stackoverflow.com/a/34423898).

### Compatibility with the laravel-database-encryption package

By default these two packages will conflict but we can get around this by implementing our own `setAttribute()` method that
calls both the packages implementations as well:

```php
class User extends Authenticatable
{
use Notifiable, HasEncryptedAttributes, HasHashedAttributes {
HasEncryptedAttributes::setAttribute as setEncryptedAttribute;
HasHashedAttributes::setAttribute as setHashedAttribute;
}

protected $fillable = [
'name', 'email', 'email_lookup', 'password',
];

protected $hidden = [
'password', 'remember_token',
];

protected $encrypted = [
'name', 'email',
];

protected $hashing = [
'email_lookup',
];

/**
* Overwrite the method so we can attempt to encrypt OR hash an
* attribute without the traits colliding.
*
* @param string $key
* @param mixed $value
*/
public function setAttribute($key, $value)
{
$this->setEncryptedAttribute($key, $value); //attempt to encrypt the attribute

$current = $this->attributes[$key] ?? null; //fetch the current value of the attribute
if($current === $value) { //check to make sure the attribute wasn't modified (we will never hash an encrypted attribute)
$this->setHashedAttribute($key, $value); //attempt to hash the attribute
}
}
}
```

This can be extracted into it's own trait if it is needed across multiple models in your project. This same approach can
also be used to make any package that implements the `setAttribute()` method on models compatible.

0 comments on commit 266d04b

Please sign in to comment.