A Guide to encryption and hashing in Laravel
Tags: php, laravel
Published: 2024-03-11 07:00:00

*This article was originally written by [Ashley Allen](https://www.honeybadger.io/blog/authors/ashleyallen/) on the [Honeybadger Developer Blog](https://www.honeybadger.io/blog/an-introduction-to-hashing-and-encryption-in-laravel/).*

Every web developer should be aware of hashing and encryption because they are vital security concepts. When used correctly, they can improve web application security and help ensure the privacy of users' personally identifiable information (PII).

In this article, we'll explore hashing and encryption, including their differences. We'll also learn how to use them within Laravel projects to improve application security. We'll cover how to hash data and compare it to plain-text values. We'll also explain the different ways that you can encrypt and decrypt data in Laravel.

## What are hashing and encryption?

As a web developer, you've likely encountered the terms "encryption" and "hashing". You may have even used them in your own projects, but what do they mean, and how are they different?

These terms are sometimes used interchangeably, but they are different techniques with their own use cases.

Encryption and hashing are both forms of "cryptographic security" and consist of using mathematical algorithms to transform data into a form that is unreadable to humans and (mostly) secure.

The world of cryptography is complex and relies heavily on mathematics. Therefore, for the sake of this article, we'll keep things simple and focus on the high-level concepts of both techniques and how they can be used in your Laravel applications.

### Encryption

Let's start by taking a look at encryption.

Encryption is the process of encoding information (usually referred to as "plaintext") into an alternative form (usually referred to as "ciphertext"). Typically, ciphertext is generated using a "key" and an encryption algorithm. It can then only be decrypted and read by someone who has the correct key, depending on the type of encryption used.

Encryption can come in two forms: "symmetric" and "asymmetric".

Symmetric encryption uses the same key for both encryption and decryption. This means that the same key is used to encrypt and decrypt the data. Examples of this type of encryption include Advanced Encryption Standard  (AES) and Data Encryption Standard (DES). Laravel uses AES-256-CBC encryption by default.

Asymmetric encryption is a bit more complicated and uses a pair of keys: a public key and a private key. The public key is used to encrypt the data, and the private key is used to decrypt the data. Thus, the public key can be shared with anyone, but the private key must be kept secret, as anyone with it can successfully decrypt the data. Examples of this type of encryption include Rivest–Shamir–Adleman (RSA) and Diffie-Hellman.

As an example of what an encrypted string might look like, let's say that we have a string of text that we want to encrypt. We'll use the following string as an example:

```text
This is a secret message.
```

If we encrypted this text using AES-256-CBC encryption and used `U2x2QdvosFTtk5nL0ejrKqLFP1tUDtSt` as the key, we would get the following ciphertext:

```text
eyJpdiI6IjF1cmF0YU5TMkRnR3NUMVRMMm1udFE9PSIsInZhbHVlIjoieGloYW5VVWtXV2hjcVRVY3hGTkZ1bDdoOVBSZEo1VkVWNE1LSlB5S0lhNkF3SloxeWhRejNwbjN5SEgxeUJXayIsIm1hYyI6IjljNzY0MTBmMGJlZmRjNzcwMjFiMmFjYmJhNTNkNWVhODkxMTgzYmYwMjA3N2YzMjM1YmVhZWU4NDRiOTYzZWQiLCJ0YWciOiIifQ==
```

If we were then to decrypt the above ciphertext using the same key, we would get the original plaintext:

```text
This is a secret message.
```

### Hashing

Hashing is a bit different from encryption. It is a one-way process, meaning that when data is "hashed", it's transformed into what is referred to as a "hash value". However, unlike encryption, you cannot reverse the process and get the original data. Hence, the hash value is not reversible.

Typically, you’d want to use a hash in a situation where you don't want any data to be retrieved in the event of a leak. For example, you may want to store a user's password in your database but wouldn't want to store it in plain text in case the database was ever compromised. If you did store it in plain text, you would potentially be giving malicious hackers a list of passwords in use. In an ideal world, all users would use unique passwords, but we all know that this isn't the case. Therefore, a leaked database of plain-text passwords would be a goldmine for hackers because they could then try to use the passwords on other websites or applications. To combat this problem, we store passwords in a hashed format.

For example, let's say that a user's password is "hello". If we were to use the `bcrypt` algorithm to hash this password, it would look something like this:

```text
$2y$10$XY2DMYKvLrMj7yqYwyvK5OSvKTA6HOoPTpe3gVpVP5.Y4kN1nbOLq
```

An important part of hashing is the use of a "salt". A salt is a random string of characters used to make the hashed values of the same data different. This means that if two users have the same password, their hashed passwords would be stored differently in the database. This is important because it would stop hackers from being able to deduce which users had the same password. For instance, the following strings are all hashed versions of `hello`:

```text
$2y$10$NUgYbLrzxn471GzcIN10wedXEcltcbAasHqU7hCeMFv4aCTl/6bVW
```

```text
$2y$10$AvBxO6HCRwYPNPZmeERIEOzLAJP7ZkcjrekdzaRLwY8YX4m9VJiFy
```

```text
$2y$10$YQ3lzNx8h0tDgw4K3dzJAOxycZhDhTAnueSugbmoo3NDTuq1OT8KW
```

You're probably thinking, "If it's hashed and can't be reversed, how do we know if the password is correct?". We can do this by comparing the hash value of the password that the user has entered with the hash value of the password that is stored in the database by using the `password_verify()` function PHP provides. If they match, then we know that the password is correct. If they don't match, then we know that the password is incorrect. Later in this article, we'll cover how you can do this comparison in Laravel.

Many hashing algorithms are available, such as `bcrypt`, `argon2i`, `argon2id`, `md5`, `sha1`, `sha256`, `sha512`, and many more. However, when dealing with passwords, you should always use a hashing algorithm designed to be slow, such as `bcrypt`, because it makes it more difficult for hackers to brute-force the passwords.

## Hashing in Laravel

Now that we have a basic idea of what hashing is, let's take a look at how hashing works within the Laravel framework.

### Hashing passwords

As mentioned previously, you don't want to store users' passwords as plain text in a database. Instead, you'll want to store their passwords in a hashed format. This is where the `Hash` facade comes into play.

By default, Laravel supports three different hashing algorithms: "Bcrypt", "Argon2i", and "Argon2id". By default, Laravel uses the "Bcrypt" algorithm, but if you want to use a different one, Laravel allows you to change it. We'll cover this topic in more detail later in the article.

To get started with hashing a value, you can use the `Hash` facade. For example, let's say that we want to hash the password "hello":

```php
use Illuminate\Support\Facades\Hash;

$hashedValue = Hash::make('hello');

// $hashedValue = $2y$10$XY2DMYKvLrMj7yqYwyvK5OSvKTA6HOoPTpe3gVpVP5.Y4kN1nbOLq
```

As you can see, it's really easy to hash a value.

To give this a bit of context, let's look at how it might work in a controller in your application. Imagine that you have a `PasswordController` that allows authenticated users to update their password. The controller may look something like so:

```php
namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;

final class PasswordController extends Controller
{
    public function update(Request $request)
    {
        // Validate the new password here...

        $request->user()->fill([
            'password' => Hash::make($request->newPassword)
        ])->save();
    }
}
```

### Comparing hashed values to plain text

As previously mentioned, it's not possible to reverse a hashed value. Therefore, if we want to determine the hashed value, we can only do so by verifying the hashed value against a plain-text value. This is where the `Hash::check()` method comes into play.

Let's imagine that we want to determine whether the "hello" plain-text password is the same as the hashed password that we created earlier. We can do this by using the `Hash::check()` method:

```php
$plainTextPassword = 'hello';
$hashedPassword = "$2y$10$XY2DMYKvLrMj7yqYwyvK5OSvKTA6HOoPTpe3gVpVP5.Y4kN1nbOLq";

if (Hash::check($plainTextPassword, $hashedPassword)) {
    // The passwords match...
} else {
    // The passwords do not match...
}
```

The `Hash::check()` method will return `true` if the plain-text password matches the hashed password. Otherwise, it will return `false`.

This type of approach is what would typically be used when a user is logging into your application. If we were to ignore any additional security measures (such as rate limiting) for our login form, your login flow might be following the steps below:

- The user provides an `email` and `password`.
- Attempt to retrieve a user from the database with the given `email`.
- If a user is found, then compare the given `password` with the hashed password stored in the database by using the `Hash::check` method.
- If the `Hash::check` method returns `true`, then the user has successfully logged in. Otherwise, they've entered the incorrect password.

### Hash drivers in Laravel

Laravel provides the functionality for you to choose between different hashing algorithms. By default, Laravel uses the "Bcrypt" algorithm, but you can change it to either the "Argon2i" or the "Argon2id" algorithm, which are also supported. Alternatively, you can implement your own hashing algorithm, but this is strongly discouraged because you may introduce security vulnerabilities into your application. Instead, you should use one of the algorithms provided through PHP so that you can be sure the algorithms are tried and tested.

To change the hashing algorithm being used across your entire application, you can change the `driver` value in the `config/hashing.php` config file. The default value is `bcrypt`, but you can change this to either `argon` or `argon2id`, like so:

```php
return [

    // ...

    'driver' => 'bcrypt',

    // ...

];
```

Alternatively, if you'd prefer to explicitly define the algorithm that should be used, you can use the `driver` method on `Hash` facade to determine which hashing driver is used. For example, if you wanted to use the "Argon2i" algorithm, you could do the following:

```php
$hashedValue = Hash::driver('argon')->make('hello');
```

## Encryption in Laravel

Let's now take a look at how to use encryption in Laravel.

### Encrypting and decrypting values

To get started with encrypting values in Laravel, you can make use of the `encrypt()` and `decrypt()` helper functions or the `Crypt` facade, which provides the same functionality. For example, let's say that we want to encrypt the string "hello":

```php
$encryptedValue = encrypt('hello'); 
```

By running the above, the `encryptedValue` variable would now be equal to something like this:

```text
eyJpdiI6IitBcjVRanJTN3hTdnV6REdScVZFMFE9PSIsInZhbHVlIjoiZGcycC9pTmNKRjU3RWpmeW1GdFErdz09IiwibWFjIjoiODg2N2U0ZTQ1NDM3YjhhNTFjMjFmNmE4OTA2NDI0NzRhZmI2YTg5NzEwYjdmY2VlMjFhMGZhYzE5MGI2NDA3NCIsInRhZyI6IiJ9
```

Now that we have the encrypted value, we can decrypt it by using the `decrypt()` helper function:

```php
$encryptedValue = 'eyJpdiI6IitBcjVRanJTN3hTdnV6REdScVZFMFE9PSIsInZhbHVlIjoiZGcycC9pTmNKRjU3RWpmeW1GdFErdz09IiwibWFjIjoiODg2N2U0ZTQ1NDM3YjhhNTFjMjFmNmE4OTA2NDI0NzRhZmI2YTg5NzEwYjdmY2VlMjFhMGZhYzE5MGI2NDA3NCIsInRhZyI6IiJ9';

$decryptedValue = decrypt($encryptedValue);
```

By running the above, the `decryptedValue` variable would now be equal to `"hello"`.

If any incorrect data were passed to the `decrypt()` helper function, an `Illuminate\Contracts\Encryption\DecryptException` exception would be thrown.

As you can see, encrypting and decrypting data in Laravel is relatively easy. It can simplify the process of storing sensitive data in your database and then decrypting it when you need to use it.

### Changing the encryption key and algorithm

As mentioned earlier in the article, Laravel uses the "AES-256" symmetric encryption algorithm by default, meaning that a single key is used for encrypting and decrypting data. By default, this key is the `APP_KEY` value defined in your `.env` file. This is very important to remember because if you change your application's `APP_KEY`, then any encrypted data that you have stored can no longer be decrypted (without making changes to your code to explicitly use the older key).

If you wish to change the encryption key without changing your `APP_KEY`, you can do so by changing the `key` value in the `config/app.php` config file. Likewise, you can also change the `cipher` value to specify the encryption algorithm used. The default value is `AES-256-CBC`, but can be changed to `AES-128-CBC`, `AES-128-GCM`, or `AES-256-GCM`.

### Automatically encrypting model attributes

If your application is storing sensitive information in the database, such as API keys or PII, you can use make use of Laravel's `encrypted` model cast. This model cast automatically encrypts data before storing it in the database and then decrypts it when it's retrieved.

This is a useful feature because it allows you to continue working with your data as if it weren't encrypted, but it's stored in an encrypted format.

For example, to encrypt the `my_secret_field` on a `User` model, you could update your model like so:

```php
class User extends Model
{
    protected $casts = [
        'my_secret_field' => 'encrypted',
    ];
}
```

Now that it's defined as an accessor, you can continue using the field as usual. Thus, if we wanted to update the value stored in the `my_secret_field`, we could still use the `update` method like so:

```php
$user->update(['my_secret_field' => 'hello123']);
```

Notice how we didn't need to encrypt the data before passing it to the `update` method.

If you were to now inspect the row in the database, you wouldn't see `"hello123"` in the user's `my_secret_field` field. Instead, you'd see an encrypted version of it, such as the following:

```text
eyJpdiI6IjM3MUxuV0lKc2RjSGNYT2dXanhKeXc9PSIsInZhbHVlIjoiNmxPZjUray9ZV21Ba1RnRkFNdHRTZz09IiwibWFjIjoiNTNlNmU0YTY5OGFjZWU2OGJiYzY4OWYzYzExYjMzNTI0MDQ2YTJiM2M4YWZkMjkyMGQxNmQ2MmYwNzQyNGFjYSIsInRhZyI6IiJ9
```

Thanks to the `encrypted` model cast, we would still be able to use the intended value of the field. For example,

```php
$result = $user->my_secret_field;

// $result is equal to: "hello123"
```

As you can imagine, using the `encrypted` model cast is a great way to quickly add encryption to an application without having to manually encrypt and decrypt the data. Hence, it is a quick way to improve data security.

However, it does have some limitations, of which you should be aware. First, because the encryption and decryption are run when storing and fetching the `Model`, calls using the `DB` facade won't automatically do the same. Therefore, if you intend to make any queries to the database using the `DB` facade (rather than using something like `Model::find()` or `Model::get()`), you'll need to manually handle the encryption.

Furthermore, it's worth noting that although encrypting fields in the database improves security, it doesn't mean the data are completely secure. If a malicious attacker finds the encryption key, they could decrypt the data and access the sensitive information. Therefore, encrypting the fields is beneficial only if the database is compromised. If you're using a key (such as your application's `APP_KEY`) that's stored in your `.env` file, then a breach of your application server would also allow the attacker to decrypt the data.

### Using a custom encryption key when manually encrypting data

When encrypting and decrypting data, there may be times when you want to use a custom encryption key. You might want to do this to avoid coupling your encrypted data to your application's `APP_KEY` value. Alternatively, you may want to give users the ability to define their own encryption keys so that (theoretically) only they can decrypt their own data.

If you're manually encrypting and decrypting data, to define your own encryption key, you can instantiate the `\Illuminate\Encryption\Encrypter` class and pass the key to the constructor. For example, let's imagine that we want to encrypt some data using a different encryption key. Our code may look something like so:

```php
use Illuminate\Encryption\Encrypter;

// Our custom encryption key:
$key = 'U2x2QdvosFTtk5nL0ejrKqLFP1tUDtSt';

$encrypter = new Encrypter(
    key: $key,
    cipher: config('app.cipher'),
);

$encryptedValue = $encrypter->encrypt('hello');
```

As you can see, this is easy to do and adds the flexibility to use a custom encryption key. Now, if we were to try and decrypt the `$encryptedValue` variable, we would need to use the same key that we used to encrypt it and wouldn't be able to run the `decrypt()` helper function because it wouldn't be using the correct key.

### Using a custom encryption key when using model casts

If you're using the `encrypted` model cast, as we've covered earlier in this article, and you want to use a custom key for the encrypted fields, you can define your own encrypter for Laravel to use by using the `Model::encryptUsing` method.

We'd typically want to do this within a service provider (such as the `App\Providers\AppServiceProvider`) so that the custom encrypter is defined and ready to use when the application starts.

Let's take a look at an example of how we could use the `Model::encryptUsing` method within our `AppServiceProvider`:

```php
declare(strict_types=1);

namespace App\Providers;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Encryption\Encrypter;

final class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->defineCustomModelEncrypter();
    }
    
    private function defineCustomModelEncrypter(): void
    {
        // Our custom encryption key:
        $key = 'U2x2QdvosFTtk5nL0ejrKqLFP1tUDtSt';
    
        $encrypter = new Encrypter(
            key: $key,
            cipher: config('app.cipher'),
        );

        Model::encryptUsing($encrypter);
    }
    
    // ...
}
```

As you can see in the example above, defining the custom encrypter is very similar to how we defined it manually earlier in this article. The only difference is that we're passing the `Encrypter` object to the `Model::encryptUsing` method so that Laravel can use it behind-the-scenes for us.

### Encrypting your `.env` file

As of Laravel 9.32.0, your application's `.env` file can also be encrypted. This is useful if you want to store sensitive information in your `.env` file to your source control (such as `git`) but don't want to store it in plain text. This is also beneficial because it allows a local versions of your `.env` config variables to be shared with other developers on your team for local development purposes.

To encrypt your environment file, you'll need to run the following command in your project root:

```text
php artisan env:encrypt
```

This will generate an output similar to this:

```text
INFO  Environment successfully encrypted.  

Key ........................ base64:amNvB/EvaX1xU+5R9Z37MeKR8gyeIRxh1Ku0pqNlK1Y
Cipher ............................................................ AES-256-CBC
Encrypted file ................................................. .env.encrypted
```

As we can see from the output, the command has used the key `base64:amNvB/EvaX1xU+5R9Z37MeKR8gyeIRxh1Ku0pqNlK1Y` to encrypt the `.env` file using the `AES-256-CBC` cipher and then stored the encrypted value in the `.env.encrypted` file.

This means that we can now store that file in our source control, and other developers on our team can use the same key to decrypt the file and use the values within it.

To decrypt the file, you need to run the `php artisan env:decrypt` command and pass the key used to encrypt the file. For example,

```text
php artisan env:decrypt base64:amNvB/EvaX1xU+5R9Z37MeKR8gyeIRxh1Ku0pqNlK1Y
```

This will then decrypt the `.env.encrypted` file and store the decrypted values in the `.env` file.

## Conclusion

Hopefully, reading this article has given you a basic understanding hashing and encryption, as well as the differences between them. It should also have given you the confidence to use both concepts within Laravel projects to improve the security of your applications.