Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Stronger Encryption and Decryption in Node.js
'use strict';
const crypto = require('crypto');
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY; // Must be 256 bytes (32 characters)
const IV_LENGTH = 16; // For AES, this is always 16
function encrypt(text) {
let iv = crypto.randomBytes(IV_LENGTH);
let cipher = crypto.createCipheriv('aes-256-cbc', new Buffer(ENCRYPTION_KEY), iv);
let encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return iv.toString('hex') + ':' + encrypted.toString('hex');
}
function decrypt(text) {
let textParts = text.split(':');
let iv = new Buffer(textParts.shift(), 'hex');
let encryptedText = new Buffer(textParts.join(':'), 'hex');
let decipher = crypto.createDecipheriv('aes-256-cbc', new Buffer(ENCRYPTION_KEY), iv);
let decrypted = decipher.update(encryptedText);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}
module.exports = { decrypt, encrypt };

Must be 256 bytes (32 characters) how to generate this string ?

you can try to use randomstring to generate 32 characters string

I have a more general question.
because of random iv i guess i should store it somewhere safe so i can decrypt my text, right?
Let's say, i store encrypted tokens in a DaaS (i.e. mongodb Atlas or mongoLab). Where do you think i should store every token's iv value?
I guess that is kinda pointless to store them in the same database

i have a question: how can i encrypt string inside android and can be decrypt that with your samples?

ooip commented Jul 1, 2017

@vlucas could this be used for streams? Would it be possible to provide an example here? Thanks

motss commented Jul 17, 2017

@vlucas Not planning to create a NPM package with some test with this amazing Gist?

Owner

vlucas commented Aug 7, 2017

@andreash92 You could certainly generate your own iv, and then pass it to this function (you would have to modify it to accept the iv as a second argument). This set of functions was intended to be as simple as possible though, so it stores the iv along with the encrypted text in a single database field. It achieves the desired randomness at rest, and does not add any complexities that people have to worry about other than a single encryption key.

Owner

vlucas commented Aug 7, 2017

@MahdiPishguy I really don't know the first thing about Android development, so I have no idea.

Owner

vlucas commented Aug 7, 2017 edited

@ooip I am sure that streams could be used instead of buffers - just drop the toString calls and you're already halfway there.

Owner

vlucas commented Aug 7, 2017

@motss Not at the moment, no - but I did think about it before making this gist the and the accompanying blog post.

Mankee commented Aug 10, 2017

@vlucas Is it necessary to keep the IV secret / separate from the encrypted data? Or is storing the IV with the encrypted data okay, assuming the encrypted data is accessible to the public.

Tiriel commented Aug 16, 2017 edited

@Mankee Since the function returns a concat comprising "iv:encrypted". If you prefer, it seems the final ciphered string is built a bit like a bcrypt password: salt+hash.

Or at least it's what I understand. I'm mostly a noob in crypto
@vlucas This is great job. Do you see any way to cipher some data on one server running on PHP then deciphering it in Node using your method?
That is, if I use mcrypt functions with the Rijndael-128 algo, proper 32chars key, CBC mode and randomly generated iv, is there anything opposing decryption by this method?

Tiriel commented Aug 16, 2017

For those interested, it is indeed possible.
This code is adapted from the Paragon Initiative:

define('AES_METHOD', 'aes-256-cbc');

function encrypt($message, $password)
{
    // Check versions with Heartbleed vulnerabilities
    if (OPENSSL_VERSION_NUMBER <= 268443727) {
        throw new RuntimeException('OpenSSL Version too old');
    }
    
    $iv_size        = openssl_cipher_iv_length(AES_METHOD);
    $iv             = openssl_random_pseudo_bytes($iv_size);
    $ciphertext     = openssl_encrypt($message, AES_METHOD, $password, OPENSSL_RAW_DATA, $iv);
    $ciphertext_hex = bin2hex($ciphertext);
    $iv_hex         = bin2hex($iv);

    return "$iv_hex:$ciphertext_hex";
}

function decrypt($ciphered, $password) {
    $iv_size    = openssl_cipher_iv_length(AES_METHOD);
    $iv         = mb_substr($ciphered, 0, $iv_size, '8bit');
    $ciphertext = mb_substr($ciphered, $iv_size+1, strlen($ciphered), '8bit');

    return openssl_decrypt($ciphertext, AES_METHOD, $password, OPENSSL_RAW_DATA, $iv);
}

echo encrypt($message, $key);

The result of encrypt() can be decrypted by @vlucas 's code, so I guess the reverse is true. Still have to test it though.

pablomagro commented Sep 5, 2017 edited

@vlucas Thanks for sharing!

If someone is interested in use the function decrypt posted by @Tiriel, just change below code:

$iv = mb_substr($ciphered, 0, $iv_size, '8bit');
$ciphertext = mb_substr($ciphered, $iv_size+1, strlen($ciphered), '8bit');

by this:

$parts = explode(':', $ciphered);
$iv = hex2bin($parts[0]);
$ciphertext = hex2bin($parts[1]);

hex2bin is available with PHP version >= 5.4.0, in php 5.3 you can use pack instead: pack("H*" , $parts[0]);

i got this error

crypto.js:265
this._handle.initiv(cipher, toBuf(key), toBuf(iv));
^

Error: Invalid IV length
at new Decipheriv (crypto.js:265:16)
at Object.Decipheriv (crypto.js:262:12)
at Object.decrypt (/var/www/html/server/lib/hash.js:20:25)
at /var/www/html/server/routes/auth.js:26:24
at newArguments.(anonymous function) (/var/www/html/server/node_modules/nedb/lib/executor.js:29:17)
at Cursor.execFn (/var/www/html/server/node_modules/nedb/lib/datastore.js:518:14)
at callback (/var/www/html/server/node_modules/nedb/lib/cursor.js:126:19)
at /var/www/html/server/node_modules/nedb/lib/cursor.js:193:12
at /var/www/html/server/node_modules/nedb/lib/datastore.js:329:14
at Object.async.eachSeries (/var/www/html/server/node_modules/nedb/node_modules/async/lib/async.js:130:20)

aksel commented Sep 27, 2017

Something to note, AES-256 keys are 256 bits, not 256 bytes as the comment states.

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