peq42
peq42

Reputation: 402

Fast yet secure way to encrypt strings in nodejs?

I've been testing the encryption/decryption performance of nodejs (well, crypto more specifically) to implement it in a project of mine. After a small amount of edits, I thought I had achieved a somewhat decent speed but then I talked to a friend and did some research, and now wish to know if there are any ways to do this more efficiently

I moved the require("crypto") to outside the function so it only runs once, tried saving the cipher and decipher in a variable (which didn't work), googling more efficient ways to encrypt/decript,etc but couldn't achieve much more performance

var crypt = require('crypto')
function encrypt(text,password){
   var text1=String(text)
   var cipher = crypt.createCipher('aes-128-cbc',password) 
   var crypted = cipher.update(text1,'utf8','hex')
   crypted += cipher.final('hex');
   return crypted;
}


function decrypt(text,password){
   var text1=String(text)
   var decipher = crypt.createDecipher('aes-128-cbc',password)  
   var dec = decipher.update(text1,'hex','utf8')
   dec += decipher.final('utf8');
   return dec;
}

function generatepass(length) {
  var text = "";
  var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  for (var i = 0; i < length; i++){
     text += possible.charAt(Math.floor(Math.random() * possible.length))
    }
     text=text.toString()
     return text;
}

var text=generatepass(50)
var pass=generatepass(50)
aa=performance.now()
for(var i=0;i<1000;i++){
   decrypt(encrypt(text,pass),pass)
 } 
console.log((performance.now()-aa)/1000) //around 0.05ms on my end

Upvotes: 4

Views: 5473

Answers (1)

Scott Arciszewski
Scott Arciszewski

Reputation: 34113

Short recommendation: Use sodium-plus with sodium-native.

There are several reasons why sodium-plus will be a good move here:

  1. Sodium-Plus is asynchronous, so you can encrypt/decrypt more messages at once without bottle-necking your server.
  2. If you install sodium-native alongside it, your code will be faster. (It automatically selects the best backend.)
  3. AES-CBC is unauthenticated and therefore vulnerable to chosen-ciphertext attacks. sodium.crypto_secretbox() and sodium.crypto_secretbox_open() is superior here.
  4. Your generatepass() function doesn't use a secure random number generator, but sodium-plus provides one.

Your functions can be written as follows (using keys rather than passwords):

const { SodiumPlus } = require('sodium-plus');
let sodium;

async function encrypt(text, key) {
    if (!sodium) sodium = await SodiumPlus.auto();

    let nonce = await sodium.randombytes_buf(24);
    let encrypted = await sodium.crypto_secretbox(text, nonce, key);
    return Buffer.concat([nonce, encrypted]).toString('hex');
}

async function decrypt(ciphertext, key) {
    if (!sodium) sodium = await SodiumPlus.auto();

    const decoded = Buffer.from(ciphertext, 'hex');
    const nonce = decoded.slice(0, 24);
    const cipher = decoded.slice(24);
    return sodium.crypto_secretbox_open(cipher, nonce, key);
}

async function randomString(length) {
    if (!sodium) sodium = await SodiumPlus.auto();
    const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    let text = "";
    let r;
    for (var i = 0; i < length; i++){
        r = await sodium.randombytes_uniform(possible.length);
        text += possible[r];
    }
    return text;
}

// Wrap this in an async function for calling:
(async function () {
    if (!sodium) sodium = await SodiumPlus.auto()
    var text = randomString(50)
    var key = await sodium.crypto_secretbox_keygen()
    let aa=performance.now()
    for (var i=0; i<1000; i++) {
        await decrypt(await encrypt(text, key), key)
    }
    console.log((performance.now()-aa)/1000)
})();

Note that the incumbent benchmark is also a bit misleading. Since this uses async functions, you can queue up a bunch of them at once and then resolve them all.

(async function () {
    if (!sodium) sodium = await SodiumPlus.auto()
    var text = randomString(50)
    var key = await sodium.crypto_secretbox_keygen()
    let aa=performance.now()
    let queued = [];
    for (var i=0; i<1000; i++) {
        queued[i] = decrypt(await encrypt(text, key), key)
    }
    await Promise.all(queued);
    console.log((performance.now()-aa)/1000)
})();

The main advantage of async functions for a Node.js web application is to prevent a single process from blocking the whole server for all users.

Upvotes: 2

Related Questions