Better Encryption with PHP

5 min read

Table of Contents:

Title Image for the Article

Learn to secure files and data to send between users. Keep information secure and only readable by the associated parties. Build off what we have created by adding additional security and compartmentalizing code.

cobb208/phpencryptionapp (github.com)

Introduction

This is a project that builds off another. Which can be viewed at Lock down Encryption with PHP

We will continue to improve this application for security.

What's New?

  • A validation class that will house current and future validation parameters.
  • A CSRF Token system to ensure proper submissions.
  • Enclosed the Database Information into a Class
  • Started a Global Variable Class
  • Utility items (Database, validation, etc.) are enclosed into Namespaces
  • Unique code to improve security

Breaking up things into namespaces and classes will ensure that, as we create more content, things do not conflict with each other. The problem with not using a framework is you ironically will create your own framework in the process 😂.

Read more about PHP's Namespaces at: PHP: Namespaces - Manual

Unless you use Composer Autoloading we must ensure we pull in the Namespaces with the require functions. Namespaces are an addition to PHP and do not change how the language learns so they must confirm to PHP rather than the other way.

Validation Class

<?php

namespace EncryptionValidation;

class ValidationRules {


    /**
     * @param string $email
     * @return string|bool
     */
    public static function validate_email(string $email) : string | bool {
        return filter_var($email, FILTER_VALIDATE_EMAIL);
    }

    /**
     * @return void
     * Method that validates the CSRF token sent with a request.
     */
    public static function validate_csrf_token() : void {
        if($_SERVER['REQUEST_METHOD'] === 'POST' or $_SERVER['REQUEST_METHOD'] === 'post')
        {
            $token = $_POST['csrf_token'];
            if(!$token or $token !== $_SESSION['csrf_token'])
            {
                http_response_code(403);
                exit();
            }
        }
        $_SESSION['csrf_token'] = md5(uniqid(mt_rand(), true));
    }

    public static function generate_csrf_form_token() : void {
        $csrf_token = $_SESSION['csrf_token'];
        echo "<input type='hidden' value='$csrf_token' name='csrf_token'/>";
    }
}

Validate email

While this method is only one line, we will put it into a class like that for two reasons:

  • This will ensure we have the same way to validate emails throughout the program.
  • We will not have to chase down every way to validate if we decide to add or remove changes.

Validate and Generate CSRF

This is a protection emplacement for users. This will ensure that the form request has a unique session variable associated with their device's session. We will use PHP's Session infrastructure, cookies, and the ability to generate hidden form fields.

Special note on hidden form fields, the user can still look at the form fields if they inspect the web page. They are not truly hidden; they are just hidden visually.

The validation method will check if the request is a POST method (form submission) if it is, it will check the submitted CSRF token against the session's CSRF. If they match, continue the program. If they do not match, send a 403 (Forbidden Request) to the user.

The generate CSRF is much like our validation of email. It ensures we have a uniform way of creating our form validation. If we need to change its structure, we just have to come here.

Learn more about CSRF at: Cross Site Request Forgery (CSRF) | OWASP Foundation

The Database

Enclosing the database into a class lets us keep naming conflicts at a minimum. While the names in the program are the same, it will help keep the mystery out of the way. Instead of us having a $mysqli variable from somewhere in the program... we will have an established class to call from.

<?php

namespace EncryptionConnection;


use Exception;
use mysqli;

class DatabaseConnection
{
    public mysqli $mysqli;

    public function __construct()
    {
        $sql_hostname = 'localhost';
        $sql_username = 'yourusername';
        $sql_password = 'yourpassword';
        $sql_database = 'encryptiontest';

        try {
            $this->mysqli = new mysqli($sql_hostname, $sql_username, $sql_password, $sql_database);
        } catch (Exception $e) {
            header('Location: /encryption/custom-500.php');
            exit;
        }
    }
}

With this encapsulation, we can keep all of the database information inside of one class and ensure that someone does not try to grab the hostname from another location and cause a headache when we reformat our code.

The code still works the same, as before, I just added the try-catch to handle if the database fails.

Globals

While we will only have one global variable right now, it will ensure that we have a one stop shop for future global variables. The one variable we declare here is the "base_url". In our pages we will add this to our project, so we do not statically type our URL and cause headaches later on.

<?php
namespace EncryptionGlobals;


class GlobalVars
{
    public static string $base_url = 'http://localhost/encryption/';
}

Add the Unique Code

As our application stands, if someone got ahold of the database information, they would be able to access the files. That is not very secure! Instead, the creator will be given a passcode that they need to pass onto the receiver. On top of that, they will also need to pass a unique code (in case of multiple transactions) this ensure they get the one for that specific file.

$passcode = $_POST['password'];
$cipher = 'aes-128-gcm';
$ivlen = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivlen);
$cipher_text = openssl_encrypt($file_contents, $cipher, $passcode, 0, $iv, $tag);

The passcode will be the random value for the encryption. It will not be stored. If the password is lost, there will not be a way to recover the contents. It provides security and insurance for the instance things get hacked.

When the decryption file has to decrypt the file, it will simply take the user's inputted file as the unique value.

$cipher = 'aes-128-gcm';
$file_contents = file_get_contents($result_filepath);
$plain_text = openssl_decrypt($file_contents, $cipher, $password, 0, $result_iv, $result_tag);
$file_prefix = explode("@", $receiver_email);
$tmp_file = fopen('/tmp/' . $file_prefix[0] . $date_stamp . '.' . $result_file_extension, 'w');
$true_file_name = $file_prefix[0] . $date_stamp . '.' . $result_file_extension;

Update the App

Now that we have all of these updates, we need to update our actual application!

Update the Database Request

The code will be similar; however, we are going to add a line to instantiate the class.

The code will be the same in the Index and Decryption Page:

$db = new DatabaseConnection();
$mysqli = $db->mysqli;

$stmt = $mysqli->prepare(
    "INSERT INTO files(iv, filepath, sender_email, receiver_email, created_at, tag, file_ext, passphrase)
    VALUES(?, ?, ?, ?, ?, ?, ?, ?)"
    );

Form Validation

Inside our HTML forms we will add the following line:

<?php ValidationRules::generate_csrf_form_token(); ?>

That will ensure it creates our hidden input for the submission(s). We will also update the base URL for form requests.

<form action="<?php echo GlobalVars::$base_url ?>index.php" method="post" enctype="multipart/form-data">
    <?php ValidationRules::generate_csrf_form_token(); ?>
    <fieldset>
        <legend>Emails</legend>
        <label for="ownerEmail">Your Email:</label>
        <input type="email" id="ownerEmail" name="ownerEmail" required />
        <?php
            if(in_array('ownerEmail', $error_list)) {
                echo '<p>Error in the provided email address</p>';
            }
        ?>
        <label for="receiverEmail">Their Email:</label>
        <input type="email" id="receiverEmail" name="receiverEmail" required />
        <?php
            if(in_array('receiverEmail', $error_list)) {
                echo '<p>Error in the provided email address</p>';
            }
        ?>
    </fieldset>
    <fieldset>
        <legend>Password</legend>
        <label for="password">Password:</label>
        <input type="password" id="password" name="password" required />
        <?php
        if(in_array('password', $error_list)) {
            echo '<p>Error in the provided password</p>';
        }
        ?>
    </fieldset>
    <fieldset>
        <legend>File</legend>
        <label for="uploadFile">File:</label>
        <input type="file" id="uploadFile" name="uploadFile" required>
        <?php
        if(in_array('uploadFile', $error_list)) {
            echo '<p>Error in the provided email address</p>';
        }
        ?>
        <input type="submit" value="Submit" />
    </fieldset>
</form>

There are PHP snippets in the form that check if the form has been validated, if not it will show the user incorrect values.

function validate_file_upload_input(array &$error_array, string &$sender_email, string &$receiver_email) : bool
{
    $is_valid = true;
    $s_email = ValidationRules::validate_email($_POST['ownerEmail']);
    $r_email = ValidationRules::validate_email($_POST['receiverEmail']);

    if(empty($s_email)) {
        $error_array[] = 'ownerEmail';
        $is_valid = false;
    }
    $sender_email = $s_email;

    if(!$r_email) {
        $error_array[] = 'receiverEmail';
        $is_valid = false;
    }
    $receiver_email = $r_email;

    if(empty($_FILES['uploadFile']))
    {
        $error_array[] = 'uploadFile';
        $is_valid = false;
    }

    if(empty($_POST['password']))
    {
        $error_array[] = 'password';
        $is_valid = false;
    }

    return $is_valid;
}

Conclusion

Think of what you would want to add to it. It still needs to be styled, maybe a login system, hooking up an email client to send out the information. There are so many things that can be done! Stay tuned for more updates to the project.

Recent Articles

Push Weight with Python

Create a simple terminal application that can let you know how much weight you need to put on the ba...

Validate Forms in TypeScript

Relying on HTML to validate your forms is just one step in ensuring visitors enter their information...

Automation with NodeJS

NodeJS can help you automate simple tasks. Being a web developer you can achieve the same goals as o...

Automation with Python

Python can take boring tasks away from you so you can continue to do other things. It is a scripting...

Speed With Cache in WordPress

Most WordPress sites use Apache as their server. Apache has a lot of tools built into it to speed up...

Social Media