Skip to content

Instantly share code, notes, and snippets.

@paragonie-scott
Last active September 14, 2022 22:04
Show Gist options
  • Save paragonie-scott/fae9903ae19c2ae75bbada9dbad4b1d1 to your computer and use it in GitHub Desktop.
Save paragonie-scott/fae9903ae19c2ae75bbada9dbad4b1d1 to your computer and use it in GitHub Desktop.
String 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_no="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

I originally submitted this on dev.to but their domain name is shadowbanned everywhere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment