Encrypting Large Files in Laravel easy

How To Encrypt Large Files in Laravel, encrypting files is easy and very important

Recently, I came across what I thought would be a pretty common problem. In a Laravel project where users can upload files of any size, the files needed to be encrypted at rest for security purposes.

Laravel provides encryption facilities, but they are designed mostly for encrypting values. Encrypting a small file like an image with the encrypt helper method works fine, but in the process, the file contents need to be loaded in memory, which for large files becomes an issue.

In this tutorial, I will describe all the steps needed to encrypt large files into a brand new Laravel project.

First, create a new Laravel project using the Laravel installer. We’ll call it security-app:

laravel new security-app

At the time of writing this tutorial, we are using Laravel v6.5.2.

Because we have used the Laravel installer, we already have an application key generated and added to our .env file. If you are using other installation methods, don’t forget to generate a new app key using:

 php artisan key:generate

Because we are using Laravel Valet, we should already have the security-app.testdomain created for us. If you are using another development environment, you should add a local domain to point to the new project.

Since the front-end scaffolding has been moved to Laravel UI since v6, we will install the laravel/ui package.

composer require laravel/ui — dev

Next, we will install the bootstrap and auth scaffolding:

php artisan ui bootstrap --auth

And compile everything.

npm install && npm run dev

We also need to configure our database access credentials in the .env file and run the initial migrations:

php artisan migrate

We can now create a new user and log in to see the user dashboard.

Note: For the purpose of this demonstration, we will create a basic upload form, but in your application, you should consider using a more sophisticated upload functionality, using chunked uploads for large files.

Laravel auth scaffolding has created for us a /home route, a HomeController,and a home.blade.php view file.

Let’s edit the home.blade.php file and add a form and an upload field:

<form action="{{ route('uploadFile') }}" method="post" enctype="multipart/form-data" class="my-4">
    @csrf

    <div class="form-group">
        <div class="custom-file">
            <input type="file" class="custom-file-input" id="userFile" name="userFile">
            <label class="custom-file-label" for="userFile">Choose a file</label>
        </div>
    </div>

    <button type="submit" class="btn btn-primary">Upload</button>

    @if (session()->has('message'))
        <div class="alert alert-success mt-3">
            {{ session('message') }}
        </div>
    @endif
</form>

Next, we will add a new route:

Route::post(‘/home’, ‘HomeController@store’)->name(‘uploadFile’);

And a new store method to the HomeController. This method will store the uploaded file in a subdirectory with the current user ID, within a files directory (storage/app/files/{user-id}).

Note: This is poor practice and should not be used in a production app. We are relying on the filesystem to get a user’s files for demonstration purposes to keep this tutorial small, but a more robust system using database tables for keeping track of each user’s files is required in a production app.

<?php
   
   /**
     * Store a user uploaded file
     *
     * @param  \Illuminate\Http\Request $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        if ($request->hasFile('userFile') && $request->file('userFile')->isValid()) {
            Storage::putFile('files/' . auth()->user()->id, $request->file('userFile'));
        }
        return redirect()->route('home')->with('message', 'Upload complete');
    }

This is the stage where we need to encrypt the user uploaded files. We will pull in the file-vault package:

composer require soarecostin/file-vault

The package allows access to the FileVault facade, which exposes a few methods for encrypting and decrypting a file, as well as a few methods to set options like a different encryption key for each file, or specifying the Laravel filesystem disk that the file belongs to.

We will use the FileVault::encrypt($file) method to encrypt our user-uploaded file. This function will delete the original unencrypted file, and replace it with a file with the same name and an additional .encextension.

If you would like to name your file differently, you can pass in the desired name as the second parameter to the encrypt method. If you’d like to preserve your original file, you can use the encryptCopy method.

This is what our storemethod looks like now:

<?php
    
    /**
     * Store a user uploaded file
     *
     * @param  \Illuminate\Http\Request $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        if ($request->hasFile('userFile') && $request->file('userFile')->isValid()) {
            $filename = Storage::putFile('files/' . auth()->user()->id, $request->file('userFile'));
            // Check to see if we have a valid file uploaded
            if ($filename) {
                FileVault::encrypt($filename);
            }
        }
        return redirect()->route('home')->with('message', 'Upload complete');
    }

Next, we need to see all of the user-uploaded files and we also need a way of downloading them.

We will create a new downloadFile route and a new downloadFile method in HomeController:

Route::get(‘/files/{filename}’, ‘HomeController@downloadFile’)->name(‘downloadFile’);
<?php
    /**
     * Download a file
     *
     * @param  string  $filename
     * @return \Illuminate\Http\Response
     */
    public function downloadFile($filename)
    {
        // Basic validation to check if the file exists and is in the user directory
        if (!Storage::has('files/' . auth()->user()->id . '/' . $filename)) {
            abort(404);
        }
        return response()->streamDownload(function () use ($filename) {
            FileVault::streamDecrypt('files/' . auth()->user()->id . '/' . $filename);
        }, Str::replaceLast('.enc', '', $filename));
    }

The downloadFilemethod uses the Laravel native streamDownload response, which accepts a callback.

Inside the callback, we are calling the streamDecrypt method of the FileVault facade provided by the package_,_ which will decrypt the file and serve it segment-by-segment to the streamDownload method, allowing your users to download the decrypted file directly.

We now need to display all the user’s files below the upload form. For this, we will send a $files variable from the index method of HomeController to the home.blade.php view file and display the user’s files in the home.blade.php file, below the upload form.

<?php
    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Contracts\Support\Renderable
     */
    public function index()
    {
        $files = Storage::files('files/' . auth()->user()->id);
        return view('home', compact('files'));
    }
<ul class="list-group">
    @forelse ($files as $file)
        <li class="list-group-item">
            <a href="{{ route('downloadFile', basename($file)) }}">
                {{ basename($file) }}
            </a>
        </li>
    @empty
        <li class="list-group-item">You have no files</li>
    @endforelse
</ul>

You can find the entire Laravel app created in this tutorial in this GitHub repo.

Thank you for reading !

#Laravel #PHP #Encryption #Security #Programminig

Encrypting Large Files in Laravel easy
2 Likes49.40 GEEK