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.
Top comments (5)
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.
Nice post.
Is there any way to add random key for each request? Because keeping the same key is not secured right?