Skip to content

Commit 0ae79e3

Browse files
author
Eric Elliott
committed
Initial commit.
0 parents  commit 0ae79e3

6 files changed

+349
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

README.md

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Credential
2+
3+
Fortify your user's passwords against rainbow table and brute force attacks using Node's built in crypto functions.
4+
5+
Employs cryptographically secure, password unique salts to prevent rainbow table attacks.
6+
7+
Key stretching is used to make brute force attacks impractical.
8+
9+
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.
10+
11+
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).
12+
13+
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.
14+
15+
16+
## Motivation
17+
18+
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.
19+
20+
Passwords are vulnerable to the following common attacks:
21+
22+
* Rainbow tables
23+
* Brute force
24+
* Passwords stolen from third parties
25+
26+
### Rainbow tables
27+
28+
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.
29+
30+
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.
31+
32+
33+
#### Password Salts
34+
35+
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.
36+
37+
Salting can protect passwords in a couple of ways.
38+
39+
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.
40+
41+
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.
42+
43+
44+
### Brute force
45+
46+
A brute force attack will attempt to crack a password by attempting a match using every possible character combination.
47+
48+
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.
49+
50+
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.
51+
52+
*Created by Eric Elliott for the book, "Programming JavaScript Applications" (O'Reilly)*

credential.js

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/**
2+
* credential
3+
*
4+
* Fortify your user's passwords against rainbow table and
5+
* brute force attacks using Node's built in crypto functions.
6+
*
7+
* Employs cryptographically secure, password unique salts to
8+
* prevent rainbow table attacks.
9+
*
10+
* Key stretching is used to make brute force attacks
11+
* impractical.
12+
*
13+
* Created by Eric Elliott for the book,
14+
* "Programming JavaScript Applications" (O'Reilly)
15+
*
16+
* MIT license http://opensource.org/licenses/MIT
17+
*/
18+
19+
var crypto = require('crypto'),
20+
mixIn = require('mout/object/mixIn'),
21+
pick = require('mout/object/pick'),
22+
23+
/**
24+
* pbkdf2(password, salt, callback)
25+
*
26+
* A standard to employ hashing and key stretching to
27+
* prevent rainbow table and brute-force attacks, even
28+
* if an attacker steals your password database.
29+
*
30+
* This function is a thin wrapper around Node's built-in
31+
* crypto.pbkdf2().
32+
*
33+
* Internet Engineering Task Force's RFC 2898
34+
* @param {[type]} password
35+
* @param {[type]} salt
36+
* @param {Function} callback err, buffer
37+
*/
38+
pbkdf2 = function pbkdf2(password, salt, callback) {
39+
crypto.pbkdf2(password, salt,
40+
this.iterations, this.keylength, function (err, buff) {
41+
if (err) {
42+
return callback(err);
43+
}
44+
callback(null, buff.toString('base64'));
45+
});
46+
},
47+
48+
/**
49+
* createSalt(callback)
50+
*
51+
* Generates a cryptographically secure random string for
52+
* use as a password salt using Node's built-in
53+
* crypto.randomBytes().
54+
*
55+
* @param {Function} callback [description]
56+
* @return {[type]} [description]
57+
*/
58+
createSalt = function createSalt(callback) {
59+
crypto.randomBytes(this.keylength, function (err, buff) {
60+
if (err) {
61+
return callback(err);
62+
}
63+
callback(null, buff.toString('base64'));
64+
});
65+
},
66+
67+
/**
68+
* toHash(password, callback)
69+
*
70+
* Takes a new password and creates a unique salt and hash
71+
* combination in the form `salt$hash`, suitable for storing
72+
* in a single text field.
73+
*
74+
* @param {[type]} password
75+
* @param {Function} callback
76+
*/
77+
toHash = function toHash(password,
78+
callback) {
79+
80+
// Create the salt
81+
createSalt.call(this, function (err, salt) {
82+
if (err) {
83+
return callback(err);
84+
}
85+
86+
salt = salt.toString('base64');
87+
88+
// Then create the hash
89+
pbkdf2.call(this, password, salt, function (err, hash) {
90+
if (err) {
91+
return callback(err);
92+
}
93+
94+
callback(null, salt + '$' + hash.toString('base64'));
95+
});
96+
}.bind(this));
97+
},
98+
99+
/**
100+
* verify(hash, input, callback)
101+
*
102+
* Takes a stored hash, password input from the user,
103+
* and a callback, and determines whether or not the
104+
* user's input matches the stored password.
105+
*
106+
* @param {[type]} hash stored password hash
107+
* @param {[type]} input user's password input
108+
* @param {Function} callback callback(err, isValid)
109+
*/
110+
verify = function verify(hash, input, callback) {
111+
var oldHash = hash,
112+
salt = hash.slice(0, 88);
113+
114+
pbkdf2.call(this, input, salt, function (err, newHash) {
115+
var result;
116+
if (err) {
117+
return callback(err);
118+
}
119+
callback(null, (salt + '$' + newHash === oldHash));
120+
});
121+
},
122+
123+
/**
124+
* configure(options)
125+
*
126+
* Alter defaults for `keylength` or `iterations`.
127+
* Warning: Decreasing these values can make your password
128+
* database less secure.
129+
*
130+
* @param {Object} options Options object.
131+
* @param {Number} options.keylength
132+
* @param {Number} options.iterations
133+
* @return {Object} credential object
134+
*/
135+
configure = function configure(options) {
136+
var overrides = pick(options, ['keylength', 'iterations']);
137+
mixIn(this, overrides);
138+
return this;
139+
};
140+
141+
module.exports = {
142+
hash: toHash,
143+
verify: verify,
144+
configure: configure,
145+
keylength: 66,
146+
iterations: 80000
147+
};

license.txt

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2013 Eric Elliott
4+
5+
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:
6+
7+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8+
9+
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.

package.json

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"name": "credential",
3+
"version": "0.1.0",
4+
"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.",
5+
"main": "credential.js",
6+
"directories": {
7+
"test": "test"
8+
},
9+
"dependencies": {
10+
"tape": "~1.0.4",
11+
"mout": "~0.6.0"
12+
},
13+
"devDependencies": {},
14+
"scripts": {
15+
"test": "node test/credential-test.js"
16+
},
17+
"repository": {
18+
"type": "git",
19+
"url": "git@github.com:dilvie/credential.git"
20+
},
21+
"keywords": [
22+
"password",
23+
"passwords",
24+
"salt",
25+
"rainbow",
26+
"table",
27+
"brute",
28+
"force",
29+
"security",
30+
"login",
31+
"auth",
32+
"authorization",
33+
"sign",
34+
"in",
35+
"log",
36+
"in",
37+
"key",
38+
"stretching",
39+
"PBKDF2",
40+
"hash",
41+
"password",
42+
"hash"
43+
],
44+
"author": "Eric Elliott",
45+
"license": "MIT",
46+
"readmeFilename": "README.md",
47+
"bugs": {
48+
"url": "https://github.com/dilvie/credential/issues"
49+
}
50+
}

test/credential-test.js

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
var test = require('tape'),
2+
pw = require('../credential.js');
3+
4+
test('hash', function (t) {
5+
6+
pw.hash('foo', function (err, hash) {
7+
8+
t.equal(typeof hash, 'string',
9+
'should produce a hash string.');
10+
11+
t.equal(hash.length, 177,
12+
'should produce an 177 character hash string.');
13+
14+
t.end();
15+
});
16+
17+
});
18+
19+
test('hash with different passwords', function (t) {
20+
21+
pw.hash('foo', function (err, fooHash) {
22+
23+
pw.hash('bar', function (err, barHash) {
24+
25+
t.notEqual(fooHash, barHash,
26+
'should produce a different hash.');
27+
28+
t.end();
29+
});
30+
});
31+
});
32+
33+
test('hash with same passwords', function (t) {
34+
35+
pw.hash('foo', function (err, fooHash) {
36+
37+
pw.hash('foo', function (err, barHash) {
38+
39+
t.notEqual(fooHash, barHash,
40+
'should produce a different hash.');
41+
42+
t.end();
43+
});
44+
});
45+
});
46+
47+
48+
test('verify with right pw', function (t) {
49+
var pass = 'foo';
50+
51+
pw.hash(pass, function (err, storedHash) {
52+
pw.verify(storedHash, pass, function (err, isValid) {
53+
t.ok(isValid,
54+
'should return true for matching password.');
55+
t.end();
56+
});
57+
});
58+
59+
});
60+
61+
test('verify with wrong pw', function (t) {
62+
var pass = 'foo';
63+
64+
pw.hash(pass, function (err, storedHash) {
65+
pw.verify(storedHash, 'bar', function (err, isValid) {
66+
t.ok(!isValid,
67+
'should return false for matching password.');
68+
t.end();
69+
});
70+
});
71+
72+
});
73+
74+
75+
test('overrides', function (t) {
76+
pw.configure({
77+
iterations: 1,
78+
keylength: 12
79+
});
80+
81+
pw.hash('foo', function (err, hash) {
82+
83+
t.equal(pw.iterations, 1,
84+
'should allow iterations override');
85+
86+
t.equal(hash.length, 33,
87+
'should allow keylength override');
88+
t.end();
89+
});
90+
});

0 commit comments

Comments
 (0)