WordPress Web Crypto API Security: Client-Side Encryption and Database Storage

Learn to build a secure WordPress plugin using Laravel’s Web Crypto API for client-side encryption and database storage of sensitive user data.

WordPress Web Crypto API Security

As a developer working with WordPress sites, you’ve likely encountered security concerns when handling sensitive data. You might have seen users’ passwords and other critical information stored in plain text, making your site vulnerable to data breaches. This is especially true for small businesses or organizations that rely heavily on their website’s security.

You’ll build a secure WordPress plugin using Laravel’s Web Crypto API to protect sensitive user data. By the end of this tutorial, you’ll have implemented client-side encryption for a form and stored encrypted data in your WordPress database. You’ll learn how to use JavaScript to encrypt data with the Web Crypto API and then retrieve it securely from your database, ensuring that even if an attacker gains access to your site’s code or database, they won’t be able to read sensitive information.

Prerequisites: Installing Laravel and WordPress for Development

Before diving into the exciting world of secure data encryption using Web Crypto API in a WordPress plugin, we need to set up our development environment. For this tutorial, I’ll be using Laravel as my PHP framework of choice and WordPress as the CMS.

First, let’s install Laravel using Composer:

composer create-project --prefer-dist laravel/laravel web-crypto-api-tutorial

This will download the latest version of Laravel into a directory called web-crypto-api-tutorial. Change into this directory and run the following command to set up a new Laravel project:

./vendor/bin/sail up

Laravel Sail will configure your development environment, including setting up Docker containers for MySQL, Redis, and other dependencies.

Next, install WordPress using Composer by running:

composer require wp-cli/wp-cli
wp core download --url=https://example.com --path=/var/www/html --force
wp config create --dbname=wordpress --dbuser=root --dbpass=password --dbhost=localhost --dbprefix=wp_

These commands will set up a fresh WordPress installation in the public directory of your Laravel project.

Make sure to update the database settings in the docker-compose.yml file to match your WordPress configuration. With this setup, we’re ready to move on to setting up a WordPress plugin structure with Composer and Laravel Mix.

Setting Up a WordPress Plugin Structure with Composer and Laravel Mix

To set up our WordPress plugin structure, we’ll start by creating a new directory for our project. Create a folder named wp-crypto-api in your preferred location:

mkdir ~/Sites/wp-crypto-api

Navigate into the newly created directory:

cd ~/Sites/wp-crypto-api

Next, initialize a new Composer project:

composer init

Follow the prompts to set up our project. When asked about package types, choose “Library” for now.

Install Laravel Mix as a development dependency:

composer require laravel/mix --dev

Create a new file named webpack.mix.js with the following content:

const mix = require('laravel-mix');

mix
    .js('resources/js/app.js', 'public/js')
    .postCss('resources/css/app.css', 'public/css');

This configuration tells Laravel Mix to compile our JavaScript and CSS files for production use.

Create a new directory named resources in the root of our project:

mkdir resources

Inside the resources directory, create two new subdirectories: js and css. We’ll be adding our plugin’s JavaScript code to the app.js file inside the js directory.

Now we have a basic WordPress plugin structure set up with Composer and Laravel Mix. This will allow us to easily manage dependencies and compile our assets for production use.

Implementing Web Crypto API in a WordPress Plugin

Create a new file named src/EncryptedStorage.php within your plugin directory. This class will handle interaction with the Web Crypto API:

// src/EncryptedStorage.php

namespace App\Plugins;

use Illuminate\Support\Facades\Http;
use Exception;

class EncryptedStorage
{
    public function encrypt($data)
    {
        try {
            $encrypted = Http::withToken(config('jwt.token'))->post(
                'https://api.example.com/encrypt',
                ['data' => $data]
            )->json()['encrypted'];
            return $encrypted;
        } catch (Exception $e) {
            throw new Exception("Encryption failed: " . $e->getMessage());
        }
    }

    public function decrypt($encrypted)
    {
        try {
            $decrypted = Http::withToken(config('jwt.token'))->post(
                'https://api.example.com/decrypt',
                ['data' => $encrypted]
            )->json()['decrypted'];
            return $decrypted;
        } catch (Exception $e) {
            throw new Exception("Decryption failed: " . $e->getMessage());
        }
    }
}

You’ll need to replace https://api.example.com/encrypt and https://api.example.com/decrypt with the actual API endpoints you’ve created for encryption and decryption. This class uses Laravel’s Http facade for making requests.

In your WordPress plugin, require this file in a service provider or another relevant location:

// app/Providers/PluginServiceProvider.php

namespace App\Providers;

use Illuminate\Support\Facades\Route;
use App\Plugins\EncryptedStorage;

class PluginServiceProvider extends ServiceProvider
{
    public function boot()
    {
        // Register the EncryptedStorage class
        $this->app->bind(EncryptedStorage::class, function ($app) {
            return new EncryptedStorage();
        });
    }
}

Now you can use the EncryptedStorage class to handle encryption and decryption in your plugin. In the next section, we’ll explore how to use this class within a WordPress form using JavaScript.

Encrypting Sensitive Data in a WordPress Form using JavaScript

To encrypt sensitive data submitted through your WordPress form, you’ll need to use the Web Crypto API in conjunction with JavaScript.

First, ensure that the window.crypto object is available by checking for its presence:

if (!window.crypto || !window.crypto.subtle) {
    console.error('Web Crypto API not supported');
}

If it’s supported, you can proceed to generate a key pair using the generateKey method:

const algorithm = 'AES-GCM';
const keySize = 256;
try {
    const keyPair = await window.crypto.subtle.generateKey({
        name: 'AES-GCM',
        length: 256,
    }, true, ['encrypt', 'decrypt']);
} catch (error) {
    console.error('Error generating key pair:', error);
}

Next, you’ll need to create a function to encrypt the form data. This can be done using the encrypt method:

async function encryptData(data) {
    try {
        const encrypted = await window.crypto.subtle.encrypt({
            name: 'AES-GCM',
            iv: new Uint8Array(12),
        }, keyPair, data);
        return encrypted;
    } catch (error) {
        console.error('Error encrypting data:', error);
    }
}

In your form submission handler, you can call the encryptData function to encrypt the sensitive data before sending it to the server:

const form = document.getElementById('myForm');
form.addEventListener('submit', async (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    const encryptedData = await encryptData(formData.get('password'));
    // Send the encrypted data to the server using AJAX or a similar method
});

By following these steps, you can securely encrypt sensitive form data using the Web Crypto API and JavaScript. This approach ensures that even if an attacker gains access to your database or server logs, they won’t be able to extract sensitive information from the stored data.

Storing Encrypted Data in the WordPress Database

Now that we have a working encryption mechanism using the Web Crypto API, let’s create a function in our plugin to store the encrypted data in the WordPress database.

First, update your functions.php file with this code:

function wpse_store_encrypted_data( $data ) {
    global $wpdb;
    
    // Generate a random salt for each encryption
    $salt = substr( bin2hex( random_bytes( 16 ) ), 0, 32 );
    
    // Encrypt the data using the generated key and salt
    $encrypted_data = encryptData( $data, $salt );
    
    // Insert the encrypted data into a custom database table
    $wpdb->insert(
        'wpse_encrypted_data',
        array(
            'post_id' => get_the_ID(),
            'data' => $encrypted_data,
            'salt' => $salt,
        )
    );
}

function encryptData( $data, $salt ) {
    // This function should be the same as before
}

Next, create a new database table in your WordPress database to store the encrypted data:

CREATE TABLE wpse_encrypted_data (
    post_id BIGINT,
    data TEXT,
    salt CHAR(32),
    PRIMARY KEY (post_id)
);

Now you can use wpse_store_encrypted_data() whenever you need to save sensitive data, like form submissions or user input. Remember that the encrypted data will be stored in a custom table, allowing for more secure storage of sensitive information.

This concludes our tutorial on securing WordPress sites with the Web Crypto API.

Decrypting Stored Data for Display Purposes

Now that we have stored encrypted data in our WordPress database using JavaScript encryption libraries on the client-side, it’s time to decrypt and display that information on the frontend of our website.

In this example, let’s assume we’re building a simple password manager plugin. When users fill out their login credentials in a form and submit them for storage, those credentials are encrypted with a random key generated by Web Crypto API. However, when it comes time to display these credentials (e.g., on the user dashboard), we need to decrypt the stored data using the same algorithm.

To achieve this, we’ll create a new JavaScript file in our plugin directory, e.g., decrypt.js. This script will use the Web Crypto API’s decrypt() function to decode the encrypted data stored in the WordPress database. We’ll then display the decrypted credentials on the user dashboard.

// decrypt.js

const decryptData = async (encryptedData) => {
    const key = new Uint8Array(32); // 256-bit random key for decryption
    await window.crypto.getRandomValues(key);

    try {
        const decryptedData = await window.crypto.subtle.decrypt(
            await importKey(key, null, 'raw', false, ['decrypt']),
            await getDecryptParams('aes-gcm')
        );
        return new TextDecoder('utf-8').decode(decryptedData);
    } catch (e) {
        console.error(e);
    }
};

const userCredentials = document.getElementById('user-credentials');
if (userCredentials !== null) {
    const encryptedCredentials = userCredentials.dataset.credentials;
    if (encryptedCredentials) {
        decryptData(encryptedCredentials).then((decryptedCredentials) => {
            userCredentials.textContent = `Username: ${decryptedCredentials.username}\nPassword: ${decryptedCredentials.password}`;
        });
    }
}

This is a basic example of how to decrypt stored data for display purposes in our WordPress plugin. By using the Web Crypto API’s decryption functions, we ensure that sensitive information remains secure throughout the process.

With this final step complete, we have successfully implemented encryption and decryption using the Web Crypto API within our WordPress plugin.

Testing Client-Side Encryption with a Sample Use Case

Now that you have implemented client-side encryption using Web Crypto API in your WordPress plugin, it’s time to test its functionality with a sample use case.

Let’s consider an example where we need to encrypt sensitive user data such as passwords or credit card numbers. We’ll create a simple form with input fields for username and password, and then use JavaScript to encrypt the submitted data before sending it to our WordPress server.

Create a new PHP file example-form.php in your plugin directory, and add the following code:

<?php
// example-form.php

use Illuminate\Support\Facades\View;

function register_example_form_shortcode() {
    add_shortcode('example_form', function () {
        return View::make('example.form')->render();
    });
}

add_action('init', 'register_example_form_shortcode');

Create a new Blade template example/form.blade.php in your plugin directory, and add the following code:

<!-- example/form.blade.php -->

<form id="example-form">
    <input type="text" name="username" placeholder="Username">
    <input type="password" name="password" placeholder="Password">
    <button type="submit">Submit</button>
</form>

<script>
// client-side JavaScript code to encrypt submitted data
const form = document.getElementById('example-form');
form.addEventListener('submit', async (e) => {
    e.preventDefault();
    const username = form.username.value;
    const password = form.password.value;
    
    // Create a new key pair for encryption
    const keyPair = await window.crypto.subtle.generateKey({
        name: 'RSA-OAEP',
        modulusLength: 2048,
        publicExponent: new Uint8Array([1, 0, 1]),
        hash: 'SHA-256'
    }, true, ['encrypt', 'decrypt']);
    
    // Encrypt data
    const encryptedUsername = await window.crypto.subtle.encrypt(
        keyPair.publicKey,
        new TextEncoder().encode(username)
    );
    
    const encryptedPassword = await window.crypto.subtle.encrypt(
        keyPair.publicKey,
        new TextEncoder().encode(password)
    );
    
    // Send encrypted data to server
    fetch('/encrypt-data', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username: encryptedUsername, password: encryptedPassword })
    });
});
</script>

This code will create a simple form with input fields for username and password. When the user submits the form, client-side JavaScript code will encrypt the submitted data using Web Crypto API before sending it to our WordPress server.

Make sure to modify your WordPress server-side code to handle incoming encrypted data, store it securely in the database, and decrypt it when needed for display purposes.

Frequently Asked Questions

How do I install Laravel and WordPress for development?

To set up your development environment, run composer create-project --prefer-dist laravel/laravel web-crypto-api-tutorial to install Laravel, then change into the directory and run ./vendor/bin/sail up to configure Docker containers. Next, install WordPress using Composer with composer require wp-cli/wp-cli, followed by wp core download and other commands as described in the article.

What if I encounter an error installing Laravel or WordPress?

If you encounter issues during installation, ensure that your Docker containers are running correctly. Check the logs for any errors related to MySQL, Redis, or other dependencies. You can also refer to the official documentation for troubleshooting guides specific to Laravel and WordPress.

Is using Web Crypto API more secure than traditional encryption methods?

Yes, the Web Crypto API provides a modern and standardized way of performing cryptographic operations in web applications, making it more secure than traditional methods. Its implementation is also more efficient and easier to maintain.

What are some common pitfalls when working with client-side encryption?

Be cautious not to expose sensitive data or encryption keys to the client-side, as this can compromise the security of your application. Additionally, ensure that you properly handle key management and rotation to prevent potential vulnerabilities.

Can I use Web Crypto API with other PHP frameworks besides Laravel?

While this tutorial focuses on using Laravel with Web Crypto API, the API is standardized and can be used with other PHP frameworks. However, you may need to adapt the implementation details depending on your specific framework’s requirements.

Comments

comments