Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
Eric Elliott committed Jun 9, 2013
0 parents commit 0ae79e3
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Credential

Fortify your user's passwords against rainbow table and brute force attacks using Node's built in crypto functions.

Employs cryptographically secure, password unique salts to prevent rainbow table attacks.

Key stretching is used to make brute force attacks impractical.

Several other libraries claim to do the same thing, but fall short. Several fail to use cryptographically secure salts, which make salt guessing possible. Others fail to use either a long enough salt, or a long enough hash. The salt should be the same size of the hash. No shorter, and no longer.

Others fail to use key stretching, or fail to use enough iterations (taking into account processor speeds, and clustered attacks, while balancing that against user experience).

The hash should be sufficiently long not just to prevent an attack from a single machine, but to prevent an attack from a large cluster of machines.


## Motivation

Passwords should be stored with a one way encryption hash, so that even if a malicious intruder obtains access to the user database, they still won't have access to user passwords.

Passwords are vulnerable to the following common attacks:

* Rainbow tables
* Brute force
* Passwords stolen from third parties

### Rainbow tables

Rainbow tables are precomputed tables used to look up passwords using stolen hashes. Once bad guys get their hands on user passwords, they'll attempt to attack popular services such as email and bank accounts -- which spells very bad PR for your service.

There are rainbow tables that exist today which can discover every possible password up to 12 characters. To prevent password theft by rainbow table, users should choose passwords of at least 14 characters. Sadly, such passwords are definitely not convenient, particularly on mobile devices. In other words, you should not rely on users to select appropriate passwords.


#### Password Salts

One defence you can employ against rainbow tables is password salting. A salt is a sequence of random characters that gets paired with a password during the hashing process. Salts should be cryptographically secure random values of a length equal to the hash size. Salts are not secrets, and can be safely stored in plaintext alongside the user's other credentials.

Salting can protect passwords in a couple of ways.

First: A uniquely generated salt can protect your password databases against existing rainbow tables. Using a random salt makes your site immune from these attacks. However, if you use the same salt for every password, a new rainbow table can be generated to attack the password database.

Second: If two different users use the same password, the compromised password will grant access to both user accounts. To prevent that, you must use a unique salt for each password. Doing so makes a rainbow table attack impractical.


### Brute force

A brute force attack will attempt to crack a password by attempting a match using every possible character combination.

One way to thwart brute force attacks is to programatically lock a user's account after a handful of failed login attempts. However, that strategy won't protect passwords if an attacker gains access to the password database.

Key stretching can make brute force attacks impractical by increasing the time it takes to hash the password. This can be done by applying the hash function in a loop. The delay will be relatively unnoticed by a user trying to sign in, but will significantly hamper an attacker attempting to discover a password through brute force.

*Created by Eric Elliott for the book, "Programming JavaScript Applications" (O'Reilly)*
147 changes: 147 additions & 0 deletions credential.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/**
* credential
*
* Fortify your user's passwords against rainbow table and
* brute force attacks using Node's built in crypto functions.
*
* Employs cryptographically secure, password unique salts to
* prevent rainbow table attacks.
*
* Key stretching is used to make brute force attacks
* impractical.
*
* Created by Eric Elliott for the book,
* "Programming JavaScript Applications" (O'Reilly)
*
* MIT license http://opensource.org/licenses/MIT
*/

var crypto = require('crypto'),
mixIn = require('mout/object/mixIn'),
pick = require('mout/object/pick'),

/**
* pbkdf2(password, salt, callback)
*
* A standard to employ hashing and key stretching to
* prevent rainbow table and brute-force attacks, even
* if an attacker steals your password database.
*
* This function is a thin wrapper around Node's built-in
* crypto.pbkdf2().
*
* Internet Engineering Task Force's RFC 2898
* @param {[type]} password
* @param {[type]} salt
* @param {Function} callback err, buffer
*/
pbkdf2 = function pbkdf2(password, salt, callback) {
crypto.pbkdf2(password, salt,
this.iterations, this.keylength, function (err, buff) {
if (err) {
return callback(err);
}
callback(null, buff.toString('base64'));
});
},

/**
* createSalt(callback)
*
* Generates a cryptographically secure random string for
* use as a password salt using Node's built-in
* crypto.randomBytes().
*
* @param {Function} callback [description]
* @return {[type]} [description]
*/
createSalt = function createSalt(callback) {
crypto.randomBytes(this.keylength, function (err, buff) {
if (err) {
return callback(err);
}
callback(null, buff.toString('base64'));
});
},

/**
* toHash(password, callback)
*
* Takes a new password and creates a unique salt and hash
* combination in the form `salt$hash`, suitable for storing
* in a single text field.
*
* @param {[type]} password
* @param {Function} callback
*/
toHash = function toHash(password,
callback) {

// Create the salt
createSalt.call(this, function (err, salt) {
if (err) {
return callback(err);
}

salt = salt.toString('base64');

// Then create the hash
pbkdf2.call(this, password, salt, function (err, hash) {
if (err) {
return callback(err);
}

callback(null, salt + '$' + hash.toString('base64'));
});
}.bind(this));
},

/**
* verify(hash, input, callback)
*
* Takes a stored hash, password input from the user,
* and a callback, and determines whether or not the
* user's input matches the stored password.
*
* @param {[type]} hash stored password hash
* @param {[type]} input user's password input
* @param {Function} callback callback(err, isValid)
*/
verify = function verify(hash, input, callback) {
var oldHash = hash,
salt = hash.slice(0, 88);

pbkdf2.call(this, input, salt, function (err, newHash) {
var result;
if (err) {
return callback(err);
}
callback(null, (salt + '$' + newHash === oldHash));
});
},

/**
* configure(options)
*
* Alter defaults for `keylength` or `iterations`.
* Warning: Decreasing these values can make your password
* database less secure.
*
* @param {Object} options Options object.
* @param {Number} options.keylength
* @param {Number} options.iterations
* @return {Object} credential object
*/
configure = function configure(options) {
var overrides = pick(options, ['keylength', 'iterations']);
mixIn(this, overrides);
return this;
};

module.exports = {
hash: toHash,
verify: verify,
configure: configure,
keylength: 66,
iterations: 80000
};
9 changes: 9 additions & 0 deletions license.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
The MIT License (MIT)

Copyright (c) 2013 Eric Elliott

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
50 changes: 50 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "credential",
"version": "0.1.0",
"description": "Fortify your user's passwords against rainbow table and brute force attacks using Node's built in crypto functions to do key stretching and strong unique salts.",
"main": "credential.js",
"directories": {
"test": "test"
},
"dependencies": {
"tape": "~1.0.4",
"mout": "~0.6.0"
},
"devDependencies": {},
"scripts": {
"test": "node test/credential-test.js"
},
"repository": {
"type": "git",
"url": "git@github.com:dilvie/credential.git"
},
"keywords": [
"password",
"passwords",
"salt",
"rainbow",
"table",
"brute",
"force",
"security",
"login",
"auth",
"authorization",
"sign",
"in",
"log",
"in",
"key",
"stretching",
"PBKDF2",
"hash",
"password",
"hash"
],
"author": "Eric Elliott",
"license": "MIT",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/dilvie/credential/issues"
}
}
90 changes: 90 additions & 0 deletions test/credential-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
var test = require('tape'),
pw = require('../credential.js');

test('hash', function (t) {

pw.hash('foo', function (err, hash) {

t.equal(typeof hash, 'string',
'should produce a hash string.');

t.equal(hash.length, 177,
'should produce an 177 character hash string.');

t.end();
});

});

test('hash with different passwords', function (t) {

pw.hash('foo', function (err, fooHash) {

pw.hash('bar', function (err, barHash) {

t.notEqual(fooHash, barHash,
'should produce a different hash.');

t.end();
});
});
});

test('hash with same passwords', function (t) {

pw.hash('foo', function (err, fooHash) {

pw.hash('foo', function (err, barHash) {

t.notEqual(fooHash, barHash,
'should produce a different hash.');

t.end();
});
});
});


test('verify with right pw', function (t) {
var pass = 'foo';

pw.hash(pass, function (err, storedHash) {
pw.verify(storedHash, pass, function (err, isValid) {
t.ok(isValid,
'should return true for matching password.');
t.end();
});
});

});

test('verify with wrong pw', function (t) {
var pass = 'foo';

pw.hash(pass, function (err, storedHash) {
pw.verify(storedHash, 'bar', function (err, isValid) {
t.ok(!isValid,
'should return false for matching password.');
t.end();
});
});

});


test('overrides', function (t) {
pw.configure({
iterations: 1,
keylength: 12
});

pw.hash('foo', function (err, hash) {

t.equal(pw.iterations, 1,
'should allow iterations override');

t.equal(hash.length, 33,
'should allow keylength override');
t.end();
});
});

0 comments on commit 0ae79e3

Please sign in to comment.