├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── bin └── nodepw ├── lib └── password-hash.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | node_modules/* 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 4 | - 0.6 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 David Wood 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @NODE_ENV=test ./node_modules/.bin/mocha 3 | 4 | .PHONY: test 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated: Use [bcrypt](https://github.com/ncb000gt/node.bcrypt.js) or [scrypt](https://github.com/barrysteyn/node-scrypt) 2 | 3 | ## node-password-hash[![Build Status](https://secure.travis-ci.org/davidwood/node-password-hash.png)](http://travis-ci.org/davidwood/node-password-hash) 4 | 5 | password-hash is a node.js library to simplify use of hashed passwords. 6 | 7 | Storing passwords in plain-text is bad. This library makes the storing of passwords (and subsequent validation of) hashed passwords a bit easier. 8 | 9 | password-hash provides functions for generating a hashed passwords and verifying a plain-text password against a hashed password. For a bit of added strength, a random salt is generated when the password is hashed. The hashed password contains both the cryptographic algorithm that was used as well the salt, so all that is needed to verify a plain-text password is the hashed password itself. 10 | 11 | ## Installation 12 | 13 | npm install password-hash 14 | 15 | ## Usage 16 | 17 | ### generate(password, [options]) 18 | 19 | Generates a hash of the required `password` argument. Hashing behavior can be modified with the optional `options` object: 20 | 21 | * `algorithm` - A valid cryptographic algorithm for use with the `crypto.createHmac` function, defaults to 'sha1'. 22 | * `saltLength` - The length of the salt that will be generated when the password is hashed, defaults to 8. 23 | * `iterations` - The number of times the hashing algorithm should be applied, defaults to 1. 24 | 25 | Errors are thrown if: 26 | 27 | * `password` is not a string 28 | * `options.algorithm` is specified but not a valid cryptographic algorithm 29 | * `options.saltLength` is specified but not a positive integer 30 | 31 | The hashed password will be in the format `algorithm$salt$hash`. 32 | 33 | Example: 34 |
35 |     var passwordHash = require('password-hash');
36 | 
37 |     var hashedPassword = passwordHash.generate('password123');
38 | 
39 |     console.log(hashedPassword); // sha1$3I7HRwy7$cbfdac6008f9cab4083784cbd1874f76618d2a97
40 | 
41 | 42 | ### verify(password, hashedPassword) 43 | 44 | Compares a plain-text password (`password`) to a hashed password (`hashedPassword`) and returns a boolean. Both arguments are required. 45 | 46 | Example: 47 |
48 |     var passwordHash = require('./lib/password-hash');
49 | 
50 |     var hashedPassword = 'sha1$3I7HRwy7$cbfdac6008f9cab4083784cbd1874f76618d2a97';
51 |     
52 |     console.log(passwordHash.verify('password123', hashedPassword)); // true
53 |     console.log(passwordHash.verify('Password0', hashedPassword)); // false
54 | 
55 | 56 | ### isHashed(password) 57 | 58 | Check if a password (`password`) is hashed. Returns a boolean. 59 | 60 | Example: 61 |
62 |     var passwordHash = require('./lib/password-hash');
63 | 
64 |     var hashedPassword = 'sha1$3I7HRwy7$cbfdac6008f9cab4083784cbd1874f76618d2a97';
65 |     
66 |     console.log(passwordHash.isHashed('password123')); // false
67 |     console.log(passwordHash.isHashed(hashedPassword)); // true
68 | 
69 | 70 | ## Salt Generation 71 | 72 | node 0.5.8 introduced `crypto.randomBytes`, which generates cryptographically strong pseudo-random data. If the version of node supports `crypto.randomBytes` it is used to generate the salt, otherwise `Math.random`, which is not cryptographically strong, is used. This is handled transparently within the salt generation function and does not impact the module's API. 73 | 74 | ## Inspired by 75 | 76 | password-hash is inspired by the password hashing found in [Werkzeug](http://werkzeug.pocoo.org/). 77 | -------------------------------------------------------------------------------- /bin/nodepw: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | 5 | Usage: 6 | 7 | $ nodepw 'foo' 8 | -> logs to stdout: sha1$J8NYON83$0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33 9 | 10 | $ nodepw -v 'foo' 'sha1$J8NYON83$0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' 11 | -> logs to stdout: Does the string verify the hash? Yes. 12 | 13 | */ 14 | 15 | var passwordHash = require('password-hash') 16 | , str = '' 17 | , hash = '' 18 | , usage = '' 19 | + '\n' 20 | + ' \033[33mUsage: nodepw [options] [string] [hash]\033[37m\n\n' 21 | + ' NOTE: Make sure you wrap your [string] and [hash] in single quotes (e.g. \'sha1$Vxh63erc$da39a3ee5e6b4b0d3255bfef95601890afd80709\')\n' 22 | + '\n' 23 | + ' Options:\n' 24 | + ' -h, --help Output help information\n' 25 | + ' -v, --verify Verify a string against a hash.\n' 26 | , args = process.argv.slice(2) 27 | 28 | 29 | // Check for colors module (should be a cleaner way, yeah?) -- could remove this dependency if need be 30 | try{ 31 | require('colors') 32 | } 33 | catch(e){ 34 | abort("\nPlease install the \033[33mcolors\033[37m module globally with npm.\n\n[sudo] npm install colors -g\n") 35 | } 36 | 37 | // Sort out the args 38 | while (args.length) { 39 | var arg = args.shift() 40 | switch (arg) { 41 | case '-h': 42 | case '--help': 43 | abort(usage); 44 | break; 45 | case '-v': 46 | case '--verify': 47 | // if there is a verify flag set, the last arg is the hash 48 | hash = args.pop() 49 | break; 50 | default: 51 | // str will be set to arg the first, time and ignored (or set to itself after that) 52 | str = str || arg; 53 | break; 54 | } 55 | } 56 | 57 | // What to do... 58 | (hash) ? verifyHash() : echoHash() 59 | 60 | /** 61 | * Encrypt the given string and output it. 62 | * 63 | * @return {Void} 64 | */ 65 | 66 | function echoHash(){ 67 | 68 | var enc = passwordHash.generate(str) 69 | 70 | console.log('\n' + "Encoded string: " + enc.green + '\n') 71 | 72 | process.exit(0) 73 | 74 | } 75 | 76 | /** 77 | * Verify the string and hash and output its verification state. 78 | * 79 | * @return {Void} 80 | */ 81 | 82 | function verifyHash(){ 83 | 84 | var isVerified = ( passwordHash.verify(str, hash) ) ? "Yes.".green : "No.".red 85 | 86 | console.log("\nDoes the string verify the hash? " + isVerified + '\n') 87 | 88 | process.exit(1) 89 | } 90 | 91 | 92 | /** 93 | * Exit with the given `str`. 94 | * 95 | * @param {String} str 96 | */ 97 | 98 | function abort(str) { 99 | console.error(str); 100 | process.exit(1); 101 | } -------------------------------------------------------------------------------- /lib/password-hash.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'); 2 | 3 | var saltChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 4 | var saltCharsCount = saltChars.length; 5 | 6 | function generateSalt(len) { 7 | if (typeof len != 'number' || len <= 0 || len !== parseInt(len, 10)) throw new Error('Invalid salt length'); 8 | if (crypto.randomBytes) { 9 | return crypto.randomBytes(Math.ceil(len / 2)).toString('hex').substring(0, len); 10 | } else { 11 | for (var i = 0, salt = ''; i < len; i++) { 12 | salt += saltChars.charAt(Math.floor(Math.random() * saltCharsCount)); 13 | } 14 | return salt; 15 | } 16 | } 17 | 18 | function generateHash(algorithm, salt, password, iterations) { 19 | iterations = iterations || 1; 20 | try { 21 | var hash = password; 22 | for(var i=0; i", 6 | "repository": { 7 | "type": "git", 8 | "url": "http://github.com/davidwood/node-password-hash.git" 9 | }, 10 | "keywords": ["password", "hash", "utilities", "cli"], 11 | "devDependencies": { 12 | "mocha": "*", 13 | "colors": ">=0.5.0" 14 | }, 15 | "main": "./lib/password-hash.js", 16 | "scripts": { 17 | "test": "make test" 18 | }, 19 | "bin": { "nodepw": "./bin/nodepw" }, 20 | "engines": { "node": ">= 0.4.0" } 21 | } 22 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'), 2 | passwordHash = require('../lib/password-hash'); 3 | 4 | describe('password-hash', function() { 5 | 6 | describe('.generate(password, [options])', function() { 7 | 8 | it('should throw an error if the password is not a valid string', function() { 9 | var invalid = [null, undefined, true, false, 123, 456.78, new Date(), {}, [], function() {}]; 10 | invalid.forEach(function(value) { 11 | var err; 12 | try { 13 | passwordHash.generate(value); 14 | } catch (e) { 15 | err = e; 16 | } 17 | assert.ok(err instanceof Error); 18 | assert.equal(err.message, 'Invalid password'); 19 | }); 20 | }); 21 | 22 | it('should throw an error if an invalid message digest algorithm is specified', function() { 23 | var err; 24 | try { 25 | passwordHash.generate('password123', { algorithm: 'foo' }); 26 | } catch (e) { 27 | err = e; 28 | } 29 | assert.ok(err instanceof Error); 30 | assert.equal(err.message, 'Invalid message digest algorithm'); 31 | }); 32 | 33 | it('should throw an error if the salt length is invalid', function() { 34 | var invalid = [0, -10, 'abc', 5.5, [], {}]; 35 | invalid.forEach(function(value) { 36 | var err; 37 | try { 38 | passwordHash.generate('password123', { saltLength: value }); 39 | } catch (e) { 40 | err = e; 41 | } 42 | assert.ok(err instanceof Error); 43 | assert.equal(err.message, 'Invalid salt length'); 44 | }); 45 | }); 46 | 47 | it('should generate unique hashed passwords', function() { 48 | var password = 'password123', 49 | hash1 = passwordHash.generate(password), 50 | hash2 = passwordHash.generate(password); 51 | assert.notEqual(hash1, hash2); 52 | assert.ok(passwordHash.verify(password, hash1)); 53 | assert.ok(passwordHash.verify(password, hash2)); 54 | }); 55 | 56 | it('should store the algorithm in the hashed password', function() { 57 | var password = 'password123', 58 | hash = passwordHash.generate(password, { algorithm: 'md5' }); 59 | assert.ok(passwordHash.verify(password, hash)); 60 | var parts = hash.split('$'); 61 | assert.equal(parts[0], 'md5'); 62 | }); 63 | 64 | it('should store the salt length in the hashed password', function() { 65 | var password = 'password123', 66 | len = 20, 67 | hash = passwordHash.generate(password, { algorithm: 'md5', saltLength: len }); 68 | assert.ok(passwordHash.verify(password, hash)); 69 | var parts = hash.split('$'); 70 | assert.equal(parts.length, 4); 71 | assert.equal(parts[1].length, len); 72 | }); 73 | 74 | it('should apply the hashing algorith mutliple times if iterations are specified', function() { 75 | var password = 'password123', 76 | hash = passwordHash.generate(password, { algorithm: 'md5', iterations: 1000}); 77 | assert.ok(passwordHash.verify(password, hash)); 78 | var parts = hash.split('$'); 79 | assert.equal(parts[0], 'md5'); 80 | assert.equal(parts[2], '1000'); 81 | }); 82 | 83 | }); 84 | 85 | describe('.verify(password, hashedPassword)', function() { 86 | 87 | it('should return true if the password matches the hash', function() { 88 | var password = 'password123', 89 | hash = passwordHash.generate(password); 90 | assert.ok(passwordHash.verify(password, hash)); 91 | }); 92 | 93 | it('should return false if the password does not match the hash', function() { 94 | var password = 'password123', 95 | hash = passwordHash.generate(password), 96 | index = hash.indexOf('$'); 97 | assert.equal(passwordHash.verify(password, hash.substr(index + 1)), false); 98 | assert.equal(passwordHash.verify(password, hash.substr(index)), false); 99 | }); 100 | 101 | it('should verify legacy 3 token hashes', function() { 102 | assert.ok(passwordHash.verify('password123', 'md5$qel5rKU7$9c9fecf00e965aab1e7801da6e241112')); 103 | }); 104 | 105 | }); 106 | 107 | describe('.isHashed(password)', function() { 108 | 109 | it('should return true if the string is a hashed password', function() { 110 | var hash = passwordHash.generate('password123'); 111 | assert.ok(passwordHash.isHashed(hash)); 112 | }); 113 | 114 | it('should return false if the string is not a hashed password', function() { 115 | assert.ok(!passwordHash.isHashed('password123')); 116 | }); 117 | 118 | }); 119 | 120 | }); 121 | 122 | --------------------------------------------------------------------------------