DEV Community

Message Encryption in JavaScript and PHP

Just for fun, let's encrypt some stuff in client-side JavaScript and have a PHP server decrypt it. Note that this will never replace TLS (HTTPS).

JavaScript Encryption with Sodium-Plus

You'll want the latest release of sodium-plus for this. (As of this writing, it's version 0.4.0.)

<script
  src="/static/js/sodium-plus.min.js"
  integrity="sha384-lv7SVE0eb0bXA3fgK6PwlhViiUwG6tBuMAhS8XX7RvBvyRcdEdJ8HKtFgs4vHTUh"
></script>

Next, you'll want to write some JavaScript code to encrypt a message and send it to a server. I'll be using jQuery for this example, but you can easily adapt it to use a XMLHttpRequest object instead.

Let's define two functions. One loads a CryptographyKey object from a hard-coded string (n.b. you never want to actually do this, but for the sake of an easy, working example, we're using a hard-coded secret). The other actually encrypts a message.

/**
 * Get the example key. In the real world, you want to generate these randomly.
 */
async function getExampleKey() {
    if (!window.sodium) window.sodium = await SodiumPlus.auto();
    return CryptographyKey.from(
        'e9897cea109576c2f8088c277125d553e4f83afbc0abbb92cfb1f7b776b4fee0',
        'hex'
    );
    // return await sodium.crypto_secretbox_keygen();
}

/**
 * Encrypt a message under a given key.
 */
async function encryptMessage(message, key) {
    if (!window.sodium) window.sodium = await SodiumPlus.auto();

    let nonce = await sodium.randombytes_buf(24);
    let encrypted = await sodium.crypto_secretbox(message, nonce, key);
    return nonce.toString('hex') + encrypted.toString('hex');
}

Next, you'll want to write a function that gathers user input, encrypts it, and sends it to a server.

async function sendEncryptedMessage() {
    let key = await getExampleKey();
    let message = $("#user-input").val();
    let encrypted = await encryptMessage(message, key);
    $.post("/send-message", {"message": encrypted}, function (response) {
        console.log(response);
        $("#output").append("<li><pre>" + response.message + "</pre></li>");
    });
}

...and some supporting HTML:

<label for="user-input">Type a message to encrypt and send:</label>
<textarea id="user-input"></textarea>
<button id="send-it" type="button">Send Encrypted Message</button>
<hr />
<ol id="output"></ol>

<script type="text/javascript">
$("#send-it").on('click', sendEncryptedMessage);
</script>

PHP Decryption with Sodium

You're going to want paragonie/sodium_compat.

If you're using PHP 7.2, with overwhelming probability you can just use the built in sodium_* functions. However, some distros may incorrectly disable the sodium extension by default. So to play it safe, install sodium_compat anyway.

If you're using a framework (Symfony, Laravel), your code will look a lot cleaner, but for the sake of illustration, the decryption code will look like this:

<?php
declare(strict_types=1);

require 'vendor/autoload.php'; // Composer

header('Content-Type: application/json');

$key = sodium_hex2bin('e9897cea109576c2f8088c277125d553e4f83afbc0abbb92cfb1f7b776b4fee0');

$encrypted = $_POST['message'] ?? null;
if (!$encrypted) {
    echo json_encode(
        ['message' => null, 'error' => 'no message provided'],
        JSON_PRETTY_PRINT
    );
    exit(1);
}

$nonce = sodium_hex2bin(substr($encrypted, 0, 48));
$ciphertext = sodium_hex2bin(substr($encrypted, 48));
$plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);

echo json_encode(
    ['message' => $plaintext, 'original' => $encrypted],
    JSON_PRETTY_PRINT
);

Putting it Together

When you type in a message and press the button, it will encrypt it and send a hex-encoded string to the server.

The PHP code will then decrypt the message and return the plaintext in a JSON response.

The JavaScript code will then grab the plaintext from the JSON response and append it to the output field below the form.

Security Considerations

This is just a toy example to illustrate how to use sodium-plus (JavaScript) and libsodium (PHP) to encrypt/decrypt messages.

We took a lot of shortcuts that you won't want to take in a real system (for example: hard-coding the encryption keys, and eschewing error-checking in favor of brevity).

If you'd like to do something more advanced (public-key encryption in JavaScript and the congruent PHP functions), the documentation is available for free online.

Shameless plug: If you're looking for security experts to review your JavaScript or PHP code, check out why you may want to hire Paragon Initiative Enterprises for code audits.

Further Reading

Top comments (5)

Collapse
 
brzuchal profile image
Michał Brzuchalski

Shouldn't sendEncryptedMessage() function send content of encrypted variable in JSON? Currently in example it sends unencrypted message.

Collapse
 
paragoniescott profile image
Scott Arciszewski

Yep! That was a pasto. I originally stored the ciphertext in message then refactored it to make it easier to read.

Collapse
 
nathilia_pierce profile image
Nathilia Pierce

This is great! I'm glad to see libsodium making its way into the JS eco-system.

Collapse
 
eduluz1976 profile image
Eduardo Luz

Nice post.

Collapse
 
rajkumar12992 profile image
Rajkumar

Is there any way to add random key for each request? Because keeping the same key is not secured right?