Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

Encrypt and hash passwords

Save for later
  • 420 min read
  • 2016-11-16 00:00:00

article-image

A couple of days ago, Dropbox posted an explanation on how they store your passwords. In this post, we are going to implement a library that encrypts and hashes a password in a similar way.

Disclaimer: Security is a complex AND important topic. It is YOUR responsability to educate yourself on it.

As you may have read on the Dropbox blog post, they:

  1. Get the hash product of the SHA512 function over the password.
  2. Apply the bcrypt function over the previous result.
  3. Encrypt the bcrypt product with a symmetric encryption algorithm (AES256).

The above transformation can be written in pseudocode as:

hashed_password_to_store = encrypt_aes256(bcrypt(sha512('my password')))

Remember: You should not keep the AES256 key in your database nor hardcoded in the application code.

Okay, let's start with it! First, we are going to create a project:

$ mkdir password-encryption-tool
$ cd password-encryption-tool
$ npm init // init our node.js project

Edit index.js and write the skeleton of the function.

'use strict';

function hashAndEncryptPassword(input){
    let sha512hash = sha512(input);
    let bcryptHash = bcryptHash(sha512hash);
    let encryptedPassword = encrypt(bcryptHash);
    return encryptedPassword;
}

Good! Of course, that function does not work yet. The functions sha512, bcryptHash and encrypt are not yet implemented, but it gives us a starting point.

The #sha512(input) function recives a password as input and returns its SHA512 product. To implement that function, we are going to use the Hash class from the crypto module. Docs here.

function sha512(input){
    let hasher = crypto.createHash('SHA512');
    hasher.update(input);
    return hasher.digest('base64'); //Returns the hash in base64 format
}

Done! The bcrypt function requires a third party library. There are many options but here are my personal recommendations – bcrypt.js and bcrypt. The former is a pure JavaScript implementation with 0 dependencies. The latter is a native one and therefore faster (2.7 times), which is what I use personally. But, for this example, we are going to use bcrypt.js. Why? Because if you do not have the build environment needed for the native version, I don't want you to stop following this tutorial and start pulling out your hair because there is some dependency not compiling on your machine. Anyway, your code will work with both of them because bcrypt.js API is compatible with bcrypt.

Okay, that was a long explanation. Let's install bcryptjs:

$ npm install bcryptjs --save

This installs bcryptjs and updates our package.json file with the new dependency.

Bcrypt implementations expose both synchronous and asynchronous APIs. Given that the bcrypt algorithm was made to be slow, I am going to use the asynchronous API. After all, you don't want to block the event loop, do you?

This decision forces us to refactor our code a little bit. The asynchronous version of #hashAndEncryptPassword(input) looks like:

//...
const crypto = require('crypto'),
    bcrypt = require('bcryptjs');

const BCRYPT_SALT_ROUNDS=10; 

function hashAndEncryptPassword(key, input, callback){
    let sha512hash ;
    try{
        sha512hash = sha512(input);
    }catch(err){
        return callback(err);
    }
    bcrypt.hash(sha512hash, BCRYPT_SALT_ROUNDS, function(err, result){
        var encryptedHash;
        if(err){
            return callback(err);
        }
        try{
            encryptedHash = encrypt(key, result);
        }catch(err){
            return callback(err);
        }

        callback( null, encryptedHash);
    });
}
//...

Note the function signature change with the inclusion of the callback parameter, plus the bcrypt integration. Also, the SHA512 is wrapped in a try/catch block. Any error happening here will be properly handled and returned to the callback function.

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at $19.99/month. Cancel anytime

Time to implement the encrypt function. Node.js Crypto API exposes the Cipher class. Its usage is similar to the Hash class. We just need to provide the algorithm, key and IV.

The IV is used for randomization at the encryption stage; for that reason, it is very important that you use a new random IV for each different value you encrypt. Later, when we want to decrypt a value, you need to provide the same key and IV. Because they do not need to be secret, we are going to append them along the encrypted hash. IVs are just a bunch of random bytes and their generation is quite simple.

const IV = new Buffer(crypto.randomBytes(16)); // A buffer with 16 random bytes

And here is our encrypt function:

...
const IV_LEN = 16,
    ALGORITHM = 'AES-256-CTR';

function encrypt (key, input) {
    const IV = new Buffer(crypto.randomBytes(IV_LEN));

    let cipher = crypto.createCipheriv(ALGORITHM, key, IV);
    cipher.setEncoding('base64');
    cipher.write(input);
    cipher.end();

    const cipherText = cipher.read();

    // IV is not a secret. We can store it along the password
    return cipherText + '$' + IV.toString('base64');
}
...

The encrypt function accepts key and input as arguments, and:

  1. Generates an IV..
  2. Creates a cipher using the AES-256-CTR algorithm, our key and the IV.
  3. Sets cipherText constant with the encrypted version of input.
  4. Returns the cipherText plus $ plus the IV.

We are almost done. We now have a function that allows us to safely store a user password in our database. Now, as you've probably guessed, when we want to check if a given password is valid, we need to compare it with our stored password. But, you can just simple compare them. You need to apply some transformations in order to get comparable versions. A simple approach could be to just apply the same transformation to a plain password and then they will be comparable.

AES256(bcrypt(SHA512(plain text))) === hashed-and-encrypted

But we are going to make it slightly different. Here is our recipe:

  1. Get the SHA512 of the password we want to validate.
  2. Decrypt our stored password.
  3. Use bcrypt.compare to compare them.

The SHA512 part was already implemented as the sha512 function.
To decrypt the stored password we need to create a decipher with crypto.createDecipheriv with the previously used algorithm, key, and IV.

function decrypt(key, input){
    var result;
    let [cipherText, IV] = input.split('$');
    let buffIV = new Buffer(IV, 'base64');
    let decipher = crypto.createDecipheriv(ALGORITHM, key, buffIV);
    result = decipher.update(cipherText, 'base64', 'utf8');
    result += decipher.final('utf8');
    return result;
}

Now we can implement our compare function.

function compare(key, clearPassword, encryptedPassword, callback){
    var hash;
    try{
        hash = decrypt(key, encryptedPassword);
    }catch(err){
        return callback(err);
    }
    bcrypt.compare(sha512(clearPassword), hash, callback);
}

And do not forget to export compare and hashAndEncryptPassword functions.

exports.hashAndEncryptPassword = hashAndEncryptPassword;
exports.compare = compare;

We can now use our module to hash and encrypt passwords:

var passwordTool = require('.');
// Remember to not include your key in your code!
// better load it from an environment variable
var key = 'BKYHAT11zlXUiXE3iZfzSEWfvwjdbfPK';

var password = 'super secret';
passwordTool.hashAndEncryptPassword(key, password, function(err, hash){
    if(err){
        throw err;
    }
    console.log('Password ready to store', hash);
});

// prints: Password ready to store ZGmzcPy29oYjZj5+P/wg4nS0Bs64...

And to match a user provided password with our stored copy:

passwordTool.compare(key, password, storedPassword, function (err, result){
    if(err){
        throw err;
    }
    console.log('match?', result);
});

You can find the complete source code of this article at https://github.com/revington/password-tool.

About the author

Pedro Narciso García Revington is a senior full-stack developer with 10+ years of experience in high scalability and availability, microservices, automated deployments, data processing, CI, (T,B,D)DD, and polyglot persistence. He is a self-taught, highly motivated problem solver with a focus on delivering quick and elegant solutions.

Modal Close icon
Modal Close icon