Cryptographic password hashing

Effective encryption is a fundamental part of online security. Node provides the crypto module which we can use to generate our own MD5 or SHA1 hashes for user passwords. Cryptographic hashes, such as MD5 and SHA1 are known as message digests. Once the input data has been digested (encrypted), it cannot be put back into its original form (of course if we know the original password, we can regenerate the hash and compare it to our stored hash).

We can use hashes to encrypt a user's password before we store them. If our stored passwords were ever stolen by an attacker, they couldn't be used to log in because the attacker would not have the actual plain text passwords. However, since a hash algorithm always produces the same result, it could be possible for an attacker to crack a hash by matching it against hashes generated from a password dictionary (see the There's more ... section for ways to mitigate this).

Note

See http://en.wikipedia.org/wiki/Cryptographic_hash_function for more information on hashes.

In this example, we will create a simple registration form, and use the crypto module to generate an MD5 hash of a password gained via user input.

As with Basic Authentication, our registration form should be posted over HTTPS, otherwise the password is sent as plain text.

Getting ready

In a new folder, let's create a new server.js file along with an HTML file for our registration form. We'll call it regform.html.

We'll use the Express framework to provide the peripheral mechanisms (parsing POST requests, serving regform.html, and so on), so Express should be installed. We covered more about Express and how to install it in the previous chapter.

How to do it...

First, let's put together our registration form (regform.html):

<form method=post>
  User  <input name=user>
  Pass <input type=password name=pass>
  <input type=submit>
</form>

For server.js, we'll require express and crypto. Then create our server as follows:

var express = require('express'),
var crypto = require('crypto'),

var userStore = {},
  app = express.createServer().listen(8080);

app.use(express.bodyParser());

bodyParser gives us POST capabilities and our userStore object is for storing registered user details. In production we would use a database.

Now to set up a GET route as shown in the following code:

app.get('/', function (req, res) {
  res.sendfile('regform.html'),
});

This uses Express' sendfile method to stream our regform.html file.

Finally, our POST route will check for the existence of user and pass inputs, turning a user's specified password into an MD5 hash.

app.post('/', function (req, res) {
  if (req.body && req.body.user && req.body.pass) {  
    var hash = crypto
      .createHash("md5")
      .update(req.body.pass)
      .digest('hex'),

    userStore[req.body.user] = hash;
    res.send('Thanks for registering ' + req.body.user);
    console.log(userStore);
  }
});

When we use our form to register, the console will output the userStore object, containing all registered user names and password hashes.

How it works...

The password hashing portion of this recipe is:

    var hash = crypto
      .createHash("md5")
      .update(req.body.pass)
      .digest('hex'),

We've used the dot notation to chain some crypto methods together.

First, we create a vanilla MD5 hash with createHash (see the There's more ... section on how to create unique hashes). We could alternatively create a (stronger) SHA1 hash by passing sha1 as the argument. The same goes for any other encryption method supported by Node's bundled openssl version (0.9.8r as of Node 0.6.17).

For a comparison of different hash functions, see http://ehash.iaik.tugraz.at/wiki/The_Hash_Function_Zoo.

Note

This site labels certain hash functions as broken, which means a weakness point has been found and published. However, the effort required to exploit such a weakness will often far exceed the value of the data we are protecting.

Then we call update to feed our user's password to the initial hash.

Finally, we call the digest method, which returns a completed password hash. Without any arguments, digest returns the hash in binary format. We pass hex (base 16 numerical representation format of binary data, see http://en.wikipedia.org/wiki/Hexadecimal) to make it more readable on the console.

There's more...

The crypto module offers some more advanced hashing methods for creating even stronger passwords.

Uniquifying hashes with HMAC

HMAC stands for Hash-based Message Authentication Code. This is a hash with a secret key (authentication code).

To convert our recipe to using HMAC, we change our crypto portion to:

    var hash = crypto
        .createHmac("md5",'SuperSecretKey')
        .update(req.body.pass)
        .digest('hex'),

Using HMAC protects us from the use of rainbow tables (pre-computed hashes from a large list of probable passwords). The secret key mutates our hash, rendering a rainbow table impotent (unless an attacker discovers our secret key, for instance, by somehow gaining root access to our server's operating system, at which point rainbow tables wouldn't be necessary anyway).

Hardened hashing with PBKDF2

PBKDF2 is the second version of Password-Based Key Derivation Function, which is part of the Password-Based Cryptographic standard.

A powerful quality of PBKDF2 is that it generates hashes of hashes, thousands of times over. Iterating over the hash multiple times strengthens the encryption, exponentially increasing the amount of possible outcomes resulting from an initial value to the extent that the hardware required to generate or store all possible hashes becomes infeasible.

pbkdf2 requires four components: the desired password, a salt value, the desired amount of iterations, and a specified length of the resulting hash.

A salt is similar in concept to the secret key in our HMAC in that it mixes in with our hash to create a different hash. However, the purpose of a salt differs. A salt simply adds a uniqueness to the hash and it doesn't need to be protected as a secret. A strong approach is to make each salt unique to the hash being generated, storing it alongside the hash. If each hash in a database is generated from a different salt, an attacker is forced to generate a rainbow table for each hash based on its salt rather than the entire database. With PBKDF2, thanks to our salt, we have unique hashes of unique hashes which adds even more complexity for a potential attacker.

For a strong salt, we'll use the randomBytes method of crypto to generate 128 bytes of random data, which we will then pass through the pbkdf2 method with the user-supplied password 7,000 times, finally creating a hash 256 bytes in length.

To achieve this, let's modify our POST route from the recipe.

app.post('/', function (req, res) {
  if (req.body && req.body.user && req.body.pass) {
    crypto.randomBytes(128, function (err, salt) {
      if (err) { throw err;}
      salt = new Buffer(salt).toString('hex'),
      crypto.pbkdf2(req.body.pass, salt, 7000, 256, function (err, hash) {
        if (err) { throw err; }
        userStore[req.body.user] = {salt : salt,
          hash : (new Buffer(hash).toString('hex')) };
        res.send('Thanks for registering ' + req.body.user);
        console.log(userStore);
      });
    });
  }
});

randomBytes and pbkdf2 are asynchronous, which is helpful because it allows us to perform other tasks or improve the user experience by immediately taking them to a new page while their credentials are being encrypted. This is done by simply placing res.send outside of the callbacks (we haven't done this here but it could be a good idea since encryption of this magnitude could take around a second to calculate).

Once we have both our hash and salt values we place them into our userStore object. To implement a corresponding login we would simply compute the hash in the same way using that user's stored salt.

We chose to iterate 7,000 times. When PBKDF2 was standardized the recommended iteration count was 1,000. However, we need more iterations to account for technology advancements and reductions in the cost of equipment.

See also

  • Implementing Digest Authentication discussed in this chapter
  • Setting up an HTTPS web server discussed in this chapter
  • Generating Express scaffolding discussed In Chapter 6,Accelerating Development with Express
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.34.87