-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathpasshash.js
executable file
·199 lines (186 loc) · 6.39 KB
/
passhash.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#!/usr/bin/env node
/*
* passhash
*
* password hashing using sha512 and random salt
*
* As of 0.0.7 support for bcrypt
* Author: Alexander Shagla-McKotch <[email protected]>
*
* Contributor: Dave Eddy <[email protected]> github.com/bahamas10
*
* License: MIT
*/
// initial variables and requires
var prom = require('prompt'),
crypto = require('crypto'),
getopt = require('posix-getopt'),
bcrypt = require('bcrypt'),
package = require('./package.json');
var format = '{username}:{salt}:{hash}:{iterations}'
/*
* Usage statement
*
* return the usage statement
*/
function usage() {
return [
'Usage: passhash',
'',
'Easily and securely hash passwords with a variable amount of iterations of SHA512.',
'',
'You can also specify to use bcrypt and the number of rounds to use',
'',
'Takes in an optional username and number of iterations and then prompts for a password.',
'',
'If a username or number of iterations is not provided it will prompt for them.',
'',
'-t --hash-type <number> 1 for SHA512 or 2 for bcrypt',
'-i, --iterations <number> number of SHA512 iterations (default is set to 5000)',
'-r, --rounds <number> number of rounds for bcrypt',
'-b, --bytes <number> number of bytes to use for crypto random salt, must be >= 128 (default 128)',
'-h, --help print this message and exit',
'-u, --username <name> username to use for entry',
'-U, --updates check for available updates',
'-f, --format change format of output. Not supported if using bcrypt as hash type.',
'-v, --version print the version number and exit',
'',
'Deafault output',
'',
'username:salt:hash:iterations',
'',
'Example format options',
'',
'node passhash.js -u test -i 22 -f \'{username} <<>> {salt} <<>> {hash}\'',
'',
'Output',
'',
'username <<>> salt <<>> hash'
].join('\n');
}
/* schema for prompt
* checks if the username is only letters and numbers
* prompts for password twice, hidding input
*/
var schema = {
properties: {
hash_type: {
description: 'Enter 1 for SHA512 or 2 for bcrypt'.magenta,
pattern: /^1|2$/,
message: 'Must be either 1 or 2',
required: true
},
user_name: {
description: 'username'.magenta,
pattern: /^[a-z0-9\.@_]+$/,
message: 'Username can only be lower case letters, numbers, periods, underscores and @ signs.',
required: true
},
password: {
description: 'Please enter a password'.magenta,
hidden: true,
required: true
},
repeat_password: {
description: 'Please re-enter your password'.magenta,
hidden: true,
required: true
},
iterations: {
description: 'Number of iterations for SHA512 or number of rounds for bcrypt '.magenta,
pattern: /^[0-9]+$/,
message: 'Must be a number'
}
}
};
// get command line arguments
var options = [
't:(hash-type)',
'f:(format)',
'r:(rounds)',
'b:(bytes)',
'i:(iterations)',
'h(help)',
'u:(username)',
'U(updates)',
'v(version)'
].join('');
var parser = new getopt.BasicParser(options, process.argv);
var iterations = 1;
var rounds = 1;
var username;
var type;
var bytes = 128;
while ((option = parser.getopt()) !== undefined) {
switch (option.option) {
case 'f': format = option.optarg; break;
case 't': type = option.optarg; if (type != 1 && type != 2) {
console.log('ERROR: Hash type must be either 1 or 2.');
process.exit(1);
} delete schema.properties.hash_type; break;
case 'r': rounds = option.optarg; if (+rounds < 0) {
console.log('ERROR: Number of rounds must be larger than 0.');
process.exit(1);
} delete schema.properties.iterations; break;
case 'b': bytes = option.optarg; if (+bytes < 128) {
console.log('ERROR: Number of bytes must be larger than 128.');
process.exit(1);
} break;
case 'i': iterations = option.optarg; delete schema.properties.iterations; break;
case 'h': console.log(usage()); process.exit(0);
case 'u': username = option.optarg; delete schema.properties.user_name; break;
case 'U':require('latest').checkupdate(package, function(ret, msg) {
console.log(msg);
process.exit(ret);
});
return;
case 'v': console.log(package.version); process.exit(0);
default: console.error(usage()); process.exit(1); break;
}
}
var args = process.argv.slice(parser.optind());
prom.message = 'passhash'.cyan;
// start the prompt
prom.start();
// prompt user for input
prom.get(schema, function (err, result) {
// if hash type was not provided on command line set type from prompt
if (result.hash_type) type = result.hash_type;
// if username was not provided on command line set username from prompt
if (result.user_name) username = result.user_name;
// if rounds is not provided on command line set rounds from prompt
// the same prompt is used for rounds and iterations
if (result.iterations) rounds = result.iterations;
// if iterations is not provided on command line set iterations from prompt
if (result.iterations) iterations = result.iterations;
// check for password mismatch and exits on mismatch
if (result.password !== result.repeat_password) {
console.log('ERROR: Password mismatch.');
process.exit(1);
}
if (type == 1) {
/* generate 128 bit crypto random bytes
* save the byte buffer as a base64 encoded salt
* using the salt and password create a salted sha512 hash
* output the username, salt, and salted hash ':' delimited
*/
crypto.randomBytes(+bytes, function(err, buf) {
if (err) throw err;
var salt = buf.toString('base64');
var hash = result.password;
for (var i = 0; i<=iterations; i++) {
hash = crypto.createHmac('sha512', salt).update(hash).digest('hex');
}
var s = format
.replace('{username}', username)
.replace('{salt}', salt)
.replace('{hash}', hash)
.replace('{iterations}', iterations);
console.log(s);
});
}else{
var salt = bcrypt.genSaltSync(+rounds);
var hash = bcrypt.hashSync(result.password, salt);
console.log('%s:%s', username, hash);
}
});