4

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
5
  • What will you save going down from 0.5ms ? Why is it important? Commented Sep 26, 2019 at 3:11
  • I'm using those encrypt and decrypt functions in a game server script. Considering the amount of players it should be able to have(100 at best, maybe more in the future) I want to be able to extract as much performance of them as I can(since every message sent/received goes through them) Commented Sep 26, 2019 at 3:25
  • 100 players * O(algo) ~ 50ms, still pretty decent IMO. To get better performance you'll probably have to use a strong GPU. Commented Sep 26, 2019 at 3:29
  • actually its 0.05 ms per message(excluding the time it takes to process the contents of it before encrypting an answer). if each user sends above 200 messages/s(or even less, but with more than 50 characters), the server will start lagging a lot, as it will be receiving more messages/second/user than it can handle(that if I consider the above test. In a real life experiment it would take much less messages/s from each user for lags to occur) Commented Sep 26, 2019 at 3:40
  • also by "messages" I don't mean like chat messages, but all types of messages(positions, actions, reactions, informations, chat,etc) Commented Sep 26, 2019 at 3:45

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.