Skip to content Skip to sidebar Skip to footer

Simple Javascript Encryption Using Libsodium.js In This Sandbox Demo

I've spent an embarrasing number of hours trying to get Libsodium.js to work. See my fiddle demo (and code pasted below too). I keep getting Error: wrong secret key for the given

Solution 1:

Wow, I finally got it working!

The parts that really helped me were:

Here is the working fiddle sandbox.


And in case that ever disappears, here are the important parts:

const nonceBytes = sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES;
let key = sodium.from_hex("724b092810ec86d7e35c9d067702b31ef90bc43a7b598626749914d6a3e033ed");
var nonceTest;

/**
 * @param {string} message
 * @param {string} key
 * @returns {Uint8Array}
 */functionencrypt_and_prepend_nonce(message, key) {
    let nonce = sodium.randombytes_buf(nonceBytes);
    nonceTest = nonce.toString();
    var encrypted = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(message, null, nonce, nonce, key);
    var nonce_and_ciphertext = concatTypedArray(Uint8Array, nonce, encrypted); //https://github.com/jedisct1/libsodium.js/issues/130#issuecomment-361399594     return nonce_and_ciphertext;
}

/**
 * @param {Uint8Array} nonce_and_ciphertext
 * @param {string} key
 * @returns {string}
 */functiondecrypt_after_extracting_nonce(nonce_and_ciphertext, key) {
    let nonce = nonce_and_ciphertext.slice(0, nonceBytes); //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/slice      let ciphertext = nonce_and_ciphertext.slice(nonceBytes);
    var result = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(nonce, ciphertext, null, nonce, key, "text");
    return result;
}

/**
 * @param {string} message
 * @param {string} key
 * @returns {string}
 */functionencrypt(message, key) {
    var uint8ArrayMsg = encrypt_and_prepend_nonce(message, key);
    returnu_btoa(uint8ArrayMsg); //returns ascii string of garbled text
}

/**
 * @param {string} nonce_and_ciphertext_str
 * @param {string} key
 * @returns {string}
 */functiondecrypt(nonce_and_ciphertext_str, key) {
    var nonce_and_ciphertext = u_atob(nonce_and_ciphertext_str); //converts ascii string of garbled text into binaryreturndecrypt_after_extracting_nonce(nonce_and_ciphertext, key);
}

functionu_atob(ascii) {        //https://stackoverflow.com/a/43271130/returnUint8Array.from(atob(ascii), c => c.charCodeAt(0));
}

functionu_btoa(buffer) {       //https://stackoverflow.com/a/43271130/var binary = [];
    var bytes = newUint8Array(buffer);
    for (var i = 0, il = bytes.byteLength; i < il; i++) {
        binary.push(String.fromCharCode(bytes[i]));
    }
    returnbtoa(binary.join(""));
}

Solution 2:

This is what I do in https://emberclear.io :

tests: https://gitlab.com/NullVoxPopuli/emberclear/blob/master/packages/frontend/src/utils/nacl/unit-test.ts#L19

implementation: https://gitlab.com/NullVoxPopuli/emberclear/blob/master/packages/frontend/src/utils/nacl/utils.ts#L48

Snippet of implementation (in typescript):

import libsodiumWrapper, { ISodium } from'libsodium-wrappers';

import { concat } from'emberclear/src/utils/arrays/utils';

exportasyncfunctionlibsodium(): Promise<ISodium> {
  const sodium = libsodiumWrapper.sodium;
  await sodium.ready;

  return sodium;
}


exportasyncfunctionencryptFor(
  message: Uint8Array,
  recipientPublicKey: Uint8Array,
  senderPrivateKey: Uint8Array): Promise<Uint8Array> {

  const sodium = awaitlibsodium();
  const nonce = awaitgenerateNonce();

  const ciphertext = sodium.crypto_box_easy(
    message, nonce,
    recipientPublicKey, senderPrivateKey
  );

  returnconcat(nonce, ciphertext);
}

exportasyncfunctiondecryptFrom(
  ciphertextWithNonce: Uint8Array,
  senderPublicKey: Uint8Array,
  recipientPrivateKey: Uint8Array): Promise<Uint8Array> {

  const sodium = awaitlibsodium();

  const [nonce, ciphertext] = awaitsplitNonceFromMessage(ciphertextWithNonce);
  const decrypted = sodium.crypto_box_open_easy(
    ciphertext, nonce,
    senderPublicKey, recipientPrivateKey
  );

  return decrypted;
}

exportasyncfunctionsplitNonceFromMessage(messageWithNonce: Uint8Array): Promise<[Uint8Array, Uint8Array]> {
  const sodium = awaitlibsodium();
  const bytes = sodium.crypto_box_NONCEBYTES;

  const nonce = messageWithNonce.slice(0, bytes);
  const message = messageWithNonce.slice(bytes, messageWithNonce.length);

  return [nonce, message];
}

exportasyncfunctiongenerateNonce(): Promise<Uint8Array> {
  const sodium = awaitlibsodium();

  returnawaitrandomBytes(sodium.crypto_box_NONCEBYTES);
}

exportasyncfunctionrandomBytes(length: number): Promise<Uint8Array> {
  const sodium = awaitlibsodium();

  return sodium.randombytes_buf(length);
}

Snippet of tests:

import * as nacl from'./utils';
import { module, test } from'qunit';

module('Unit | Utility | nacl', function() {
  test('libsodium uses wasm', asyncfunction(assert) {
    const sodium = await nacl.libsodium();
    const isUsingWasm = sodium.libsodium.usingWasm;

    assert.ok(isUsingWasm);
  });

  test('generateAsymmetricKeys | works', asyncfunction(assert) {
    const boxKeys = await nacl.generateAsymmetricKeys();

    assert.ok(boxKeys.publicKey);
    assert.ok(boxKeys.privateKey);
  });

  test('encryptFor/decryptFrom | works with Uint8Array', asyncfunction(assert) {
    const receiver = await nacl.generateAsymmetricKeys();
    const sender = await nacl.generateAsymmetricKeys();

    const msgAsUint8 = Uint8Array.from([104, 101, 108, 108, 111]); // helloconst ciphertext = await nacl.encryptFor(msgAsUint8, receiver.publicKey, sender.privateKey);
    const decrypted = await nacl.decryptFrom(ciphertext, sender.publicKey, receiver.privateKey);

    assert.deepEqual(msgAsUint8, decrypted);
  });

Solution 3:

I've been experimenting with @Ryan's answer, and found that while it is working, a far simpler solution is to use sodium-plus. An example of a sodium-plus script can be found here. In short, the encryption side looks like this:

<scripttype='text/javascript'src='sodium-plus.min.js'></script><script>asyncfunctionencryptString(clearText) {
    if (!window.sodium) window.sodium = awaitSodiumPlus.auto();
    let publicKey = await X25519PublicKey.from('[Place your 64-char public key hex or variable name here]','hex');
    let cipherText = await sodium.crypto_box_seal(clearText, publicKey);
    return cipherText.toString('hex');
}

(asyncfunction () {
    let clearText = "String that contains secret.";
    console.log(awaitencryptString(clearText));
})();
</script>

A lot simpler. On the PHP side, all you'll need to do is use the sodium methods to handle encryption/decryption of strings.

The only downside with sodium-plus is that I haven't found a CDN for the browser version yet.

Solution 4:

I think you're making this harder than it needs to be. For your typescript encryption for example, all you need to do is this:

private asyncencrypt(obj: any): Promise<string> {
    awaitSodium.ready;

    const json = JSON.stringify(obj);
    const key = Sodium.from_hex(this.hexKey);

    const nonce = Sodium.randombytes_buf(Sodium.crypto_aead_chacha20poly1305_ietf_NPUBBYTES);
    const encrypted = Sodium.crypto_aead_chacha20poly1305_ietf_encrypt(json, '', null, nonce, key);

    // Merge the two togetherconst nonceAndCipherText = newUint8Array(Sodium.crypto_aead_chacha20poly1305_ietf_NPUBBYTES + encrypted.byteLength);
    nonceAndCipherText.set(nonce);
    nonceAndCipherText.set(encrypted, Sodium.crypto_aead_chacha20poly1305_ietf_NPUBBYTES);

    returnbtoa(String.fromCharCode(...nonceAndCipherText));
}

You don't need all the extra libraries you're using. And on your PHP side, to decrypt you'd just do this:

functiondecode($encrypted, $key)
{
    $decoded = base64_decode($encrypted); // Should be using sodium_base642bin?if ($decoded === false) {
        thrownewException('Scream bloody murder, the decoding failed');
    }

    $nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES, '8bit');
    $ciphertext = mb_substr($decoded, SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES, null, '8bit');

    $plain = sodium_crypto_aead_chacha20poly1305_ietf_decrypt($ciphertext, '', $nonce, sodium_hex2bin($key));

    sodium_memzero($ciphertext);
    sodium_memzero($key);

    if ($plain === false) {
        thrownewException('the message was tampered with in transit');
    }

    return$plain;
}

You don't need to set the nonce multiple times. That second parameter to the encryption is the "additional data" parameter, and it can just be an empty string if it's an empty string on the decryption side as well.

Post a Comment for "Simple Javascript Encryption Using Libsodium.js In This Sandbox Demo"