loading...
Paragon Initiative Enterprises

Message Encryption in JavaScript and PHP

paragoniescott profile image Scott Arciszewski ・3 min read

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

Posted on by:

paragoniescott profile

Scott Arciszewski

@paragoniescott

I do crypto/appsec/development for Paragon Initiative Enterprises.

Paragon Initiative Enterprises

Software consulting and web development for businesses with attention to security above and beyond compliance.

Discussion

markdown guide
 

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

 

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

 

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