1 | | // ----- dependencies |
2 | | // --------------------------------------- |
3 | 1 | var expect = require('chai').expect; |
4 | 1 | var password = require('../index.js'); |
5 | 1 | var helpers = require('../src/helpers.js'); |
6 | | |
7 | | |
8 | | // ----- tests |
9 | | // --------------------------------------- |
10 | 1 | describe('defaults', function() { |
11 | | |
12 | 1 | it('should have correct defaults', function() { |
13 | 1 | expect(password.defaults.hashLength).to.equal(128); |
14 | 1 | expect(password.defaults.iterations).to.deep.equal([12000, 15000]); |
15 | 1 | expect(password.defaults.key).to.equal('ENCRYPTION KEY'); |
16 | 1 | expect(password.defaults.unencryptedSaltMinLength).to.equal(32); |
17 | | }); |
18 | | |
19 | 1 | it('should have correct defaults configured', function() { |
20 | 1 | expect(password.hashLength).to.equal(128); |
21 | 1 | expect(password.iterations).to.deep.equal([12000, 15000]); |
22 | 1 | expect(password.key).to.equal('ENCRYPTION KEY'); |
23 | 1 | expect(password.unencryptedSaltMinLength).to.equal(32); |
24 | | }); |
25 | | |
26 | | }); |
27 | | |
28 | 1 | describe('configure', function() { |
29 | | |
30 | 1 | it('should be possible to change defaults', function() { |
31 | | |
32 | 1 | expect(password.configure).to.be.a.function; |
33 | | |
34 | 1 | password.configure({ |
35 | | hashLength: 256, |
36 | | iterations: [12500, 13500], |
37 | | key: 'test key', |
38 | | unencryptedSaltMinLength: 64 |
39 | | }); |
40 | | |
41 | 1 | expect(password.hashLength).to.equal(256); |
42 | 1 | expect(password.iterations).to.deep.equal([12500, 13500]); |
43 | 1 | expect(password.key).to.equal('test key'); |
44 | 1 | expect(password.unencryptedSaltMinLength).to.equal(64); |
45 | | |
46 | | }); |
47 | | |
48 | 1 | it('should not be possible to create new keys in defaults', function() { |
49 | | |
50 | 1 | password.configure({ |
51 | | foo: 'bar', |
52 | | key: 'test key 2' |
53 | | }); |
54 | | |
55 | 1 | expect(password.foo).to.be.undefined; |
56 | 1 | expect(password.key).to.equal('test key 2'); |
57 | | |
58 | | }); |
59 | | |
60 | 1 | it('should not be possible to set defaults to wrong type', function() { |
61 | | |
62 | 1 | expect(function() { |
63 | 1 | password.configure({ hashLength: 'foo' }); |
64 | | }).to.throw(); |
65 | | |
66 | 1 | expect(function() { |
67 | 1 | password.configure({ iterations: {} }); |
68 | | }).to.throw(); |
69 | | |
70 | 1 | expect(function() { |
71 | 1 | password.configure({ key: 123 }); |
72 | | }).to.throw(); |
73 | | |
74 | 1 | expect(function() { |
75 | 1 | password.configure({ unencryptedSaltMinLength: 'foo' }); |
76 | | }).to.throw(); |
77 | | |
78 | | }); |
79 | | |
80 | | }); |
81 | | |
82 | | |
83 | 1 | describe('hash', function() { |
84 | | |
85 | 1 | it('should throw if missing parameters', function() { |
86 | | |
87 | 1 | expect(function() { |
88 | 1 | password.hash(); |
89 | | }).to.throw(); |
90 | | |
91 | 1 | expect(function() { |
92 | 1 | password.hash('foo', 'bar', function() {}); |
93 | | }).to.throw(); |
94 | | |
95 | | |
96 | | }); |
97 | | |
98 | 1 | it('should not throw for empty input', function() { |
99 | | |
100 | 1 | expect(function() { |
101 | 1 | password.hash(null, function() {}); |
102 | | }).to.not.throw(); |
103 | | |
104 | 1 | expect(function() { |
105 | 1 | password.hash('', function() {}); |
106 | | }).to.not.throw(); |
107 | | |
108 | | }); |
109 | | |
110 | 1 | it('should return function with error for null input', function(done) { |
111 | 1 | password.hash(null, function(err, salt, hash) { |
112 | 1 | expect(err.message).to.include('invalid input'); |
113 | 1 | expect(salt).to.not.exist; |
114 | 1 | expect(hash).to.not.exist; |
115 | 1 | done(); |
116 | | }); |
117 | | }); |
118 | | |
119 | 1 | it('should return function with error for undefined input', function(done) { |
120 | 1 | password.hash(undefined, function(err, salt, hash) { |
121 | 1 | expect(err.message).to.include('invalid input'); |
122 | 1 | expect(salt).to.not.exist; |
123 | 1 | expect(hash).to.not.exist; |
124 | 1 | done(); |
125 | | }); |
126 | | }); |
127 | | |
128 | 1 | it('should return function with error for other input', function(done) { |
129 | 1 | password.hash(12, function(err, salt, hash) { |
130 | 1 | expect(err.message).to.include('invalid input'); |
131 | 1 | expect(salt).to.not.exist; |
132 | 1 | expect(hash).to.not.exist; |
133 | 1 | done(); |
134 | | }); |
135 | | }); |
136 | | |
137 | 1 | it('should return a hash and salt', function(done) { |
138 | 1 | password.hash('foo', function(err, salt, hash) { |
139 | 1 | expect(err).to.not.exist; |
140 | 1 | expect(salt).to.be.a.string; |
141 | 1 | expect(salt.length).to.be.at.least(32); |
142 | 1 | expect(hash).to.be.a.string; |
143 | 1 | expect(hash).to.not.equal('foo'); |
144 | 1 | expect(hash.length).to.be.at.least(128); |
145 | 1 | done(); |
146 | | }); |
147 | | }); |
148 | | |
149 | 1 | it('should return a salt that includes iteration count in range', function(done) { |
150 | | |
151 | 1 | password.hash('foo', function(err, salt, hash) { |
152 | 1 | var unencryptedSalt = helpers._decrypt('aes256', password.key, salt); |
153 | 1 | var iterations = helpers._getIterations(unencryptedSalt, password.unencryptedSaltMinLength); |
154 | 1 | expect(iterations).to.be.a.number; |
155 | 1 | expect(iterations).to.be.at.least(password.iterations[0]); |
156 | 1 | expect(iterations).to.be.at.most(password.iterations[1]); |
157 | 1 | done(); |
158 | | }); |
159 | | |
160 | | }); |
161 | | |
162 | 1 | it('should return an error if iterations are incorrect', function(done) { |
163 | 1 | password.iterations = [100, 50]; |
164 | 1 | password.hash('foo', function(err, salt, hash) { |
165 | 1 | expect(err.message).to.include('invalid min and max values'); |
166 | 1 | expect(salt).to.not.exist; |
167 | 1 | expect(hash).to.not.exist; |
168 | 1 | done(); |
169 | | }); |
170 | | }); |
171 | | |
172 | 1 | it('should return an error if iterations are not numbers', function(done) { |
173 | 1 | password.iterations = [undefined, 'foo']; |
174 | 1 | password.hash('foo', function(err, salt, hash) { |
175 | 1 | expect(err.message).to.include('min and max values'); |
176 | 1 | expect(salt).to.not.exist; |
177 | 1 | expect(hash).to.not.exist; |
178 | 1 | done(); |
179 | | }); |
180 | | }); |
181 | | |
182 | 1 | it('should return an error if randomBytes fails in helpers._salt', function(done) { |
183 | 1 | password.iterations = [12000, 15000]; |
184 | 1 | password.unencryptedSaltMinLength = null; |
185 | 1 | password.hash('foo', function(err, salt, hash) { |
186 | 1 | expect(err.message).exist; |
187 | 1 | expect(salt).to.not.exist; |
188 | 1 | expect(hash).to.not.exist; |
189 | 1 | done() |
190 | | }); |
191 | | }); |
192 | | |
193 | 1 | it('should return an error if helpers._encrypt fails', function(done) { |
194 | 1 | password.unencryptedSaltMinLength = 32; |
195 | 1 | password.key = 12; |
196 | 1 | password.hash('foo', function(err, salt, hash) { |
197 | 1 | expect(err.message).to.include('key'); |
198 | 1 | expect(salt).to.not.exist; |
199 | 1 | expect(hash).to.not.exist; |
200 | 1 | done(); |
201 | | }); |
202 | | }); |
203 | | |
204 | 1 | it('should return an error if pbkdf2 fails', function(done) { |
205 | 1 | password.configure({ |
206 | | hashLength: 128, |
207 | | iterations: [12000, 15000], |
208 | | key: 'ENCRYPTION KEY', |
209 | | unencryptedSaltMinLength: 32 |
210 | | }); |
211 | 1 | password.hashLength = null; |
212 | 1 | password.hash('foo', function(err, salt, hash) { |
213 | 1 | expect(err.message).to.include('Key length not a number'); |
214 | 1 | expect(salt).to.not.exist; |
215 | 1 | expect(hash).to.not.exist; |
216 | 1 | done(); |
217 | | }); |
218 | | }); |
219 | | |
220 | 1 | it('should throw if no callback', function() { |
221 | 1 | expect(function() { |
222 | 1 | password.hash('input', null); |
223 | | }).to.throw(); |
224 | | }); |
225 | | |
226 | | }); |
227 | | |
228 | 1 | describe('compare', function() { |
229 | | |
230 | 1 | it('should return an error if not given an input', function(done) { |
231 | 1 | password.compare(null, 'salt', function(err, hash) { |
232 | 1 | expect(err.message).to.include('invalid input'); |
233 | 1 | expect(hash).to.not.exist; |
234 | 1 | done(); |
235 | | }); |
236 | | }); |
237 | | |
238 | 1 | it('should return an error if not given a salt', function(done) { |
239 | 1 | password.compare('input', null, function(err, hash) { |
240 | 1 | expect(err.message).to.include('invalid salt'); |
241 | 1 | expect(hash).to.not.exist; |
242 | 1 | done(); |
243 | | }); |
244 | | }); |
245 | | |
246 | 1 | it ('should return an error if input is empty string', function(done) { |
247 | 1 | password.compare('', 'salt', function(err, hash) { |
248 | 1 | expect(err.message).to.include('invalid input'); |
249 | 1 | expect(hash).to.not.exist; |
250 | 1 | done(); |
251 | | }); |
252 | | }); |
253 | | |
254 | 1 | it ('should return an error if salt is empty string', function(done) { |
255 | 1 | password.compare('input', '', function(err, hash) { |
256 | 1 | expect(err.message).to.include('invalid salt'); |
257 | 1 | expect(hash).to.not.exist; |
258 | 1 | done(); |
259 | | }); |
260 | | }); |
261 | | |
262 | 1 | it('should throw if no callback', function() { |
263 | 1 | expect(function() { |
264 | 1 | password.compare('input', 'salt', null); |
265 | | }).to.throw(); |
266 | | }); |
267 | | |
268 | 1 | it('should throw if no input', function() { |
269 | 1 | expect(function() { |
270 | 1 | password.compare('salt', function(err, hash) {}); |
271 | | }).to.throw(); |
272 | | }); |
273 | | |
274 | 1 | it('should return an error if decryption fails', function(done) { |
275 | 1 | password.key = null; |
276 | 1 | password.compare('input', 'salt', function(err, hash) { |
277 | 1 | expect(err.message).to.include('cipher'); |
278 | 1 | expect(hash).to.not.exist; |
279 | 1 | done(); |
280 | | }); |
281 | | }); |
282 | | |
283 | 1 | it('should return an error if getIterations fails', function(done) { |
284 | 1 | var test = helpers._encrypt('aes256', 'encryption_key', 'test'); |
285 | 1 | password.key = 'encryption_key'; |
286 | 1 | password.compare('input', test, function(err, hash) { |
287 | 1 | expect(err.message).to.include('iterations'); |
288 | 1 | expect(hash).to.not.exist; |
289 | 1 | done(); |
290 | | }); |
291 | | }); |
292 | | |
293 | 1 | it('should hash correctly', function(done) { |
294 | 1 | password.configure({ |
295 | | hashLength: 128, |
296 | | iterations: [12000, 15000], |
297 | | key: 'ENCRYPTION KEY', |
298 | | unencryptedSaltMinLength: 32 |
299 | | }); |
300 | 1 | password.hash('test', function(err, salt, hash) { |
301 | 1 | password.compare('test', salt, function(err, hash2) { |
302 | 1 | expect(err).to.not.exist; |
303 | 1 | expect(hash2).to.equal(hash); |
304 | 1 | done(); |
305 | | }); |
306 | | }); |
307 | | }); |
308 | | |
309 | 1 | it('should return an error if pbkdf2 fails', function(done) { |
310 | 1 | password.configure({ |
311 | | hashLength: 128, |
312 | | iterations: [12000, 15000], |
313 | | key: 'ENCRYPTION KEY', |
314 | | unencryptedSaltMinLength: 32 |
315 | | }); |
316 | 1 | password.hash('test', function(err, salt, hash) { |
317 | 1 | password.hashLength = null; |
318 | 1 | password.compare('test', salt, function(err, hash2) { |
319 | 1 | expect(err.message).to.include('length'); |
320 | 1 | expect(hash2).to.not.exist; |
321 | 1 | done(); |
322 | | }); |
323 | | }); |
324 | | }); |
325 | | |
326 | | }); |
327 | | |
328 | 1 | describe('using other minLengths', function() { |
329 | | |
330 | 1 | it('should work with a minLength of 0 (but... this is still bad)', function(done) { |
331 | 1 | password.configure({ |
332 | | hashLength: 128, |
333 | | iterations: [12000, 15000], |
334 | | key: 'ENCRYPTION KEY', |
335 | | unencryptedSaltMinLength: 0 |
336 | | }); |
337 | | |
338 | 1 | password.hash('test', function(err, salt, hash) { |
339 | 1 | password.compare('test', salt, function(err, hash2) { |
340 | 1 | expect(err).to.not.exist; |
341 | 1 | expect(hash2).to.equal(hash); |
342 | 1 | done(); |
343 | | }); |
344 | | }); |
345 | | |
346 | | }); |
347 | | |
348 | 1 | it('should work with a high minLength', function(done) { |
349 | 1 | password.configure({ |
350 | | hashLength: 128, |
351 | | iterations: [10, 20], |
352 | | key: 'ENCRYPTION KEY', |
353 | | unencryptedSaltMinLength: 1000000 |
354 | | }); |
355 | 1 | password.hash('test', function(err, salt, hash) { |
356 | 1 | password.compare('test', salt, function(err, hash2) { |
357 | 1 | expect(err).to.not.exist; |
358 | 1 | expect(hash2).to.equal(hash); |
359 | 1 | done(); |
360 | | }); |
361 | | }); |
362 | | |
363 | | }); |
364 | | |
365 | | }); |
366 | | |
367 | 1 | describe('using other hashLengths', function() { |
368 | | |
369 | 1 | it('should work with a hashLength of 0 (but... this is still bad)', function(done) { |
370 | 1 | password.configure({ |
371 | | hashLength: 0, |
372 | | iterations: [12000, 15000], |
373 | | key: 'ENCRYPTION KEY', |
374 | | unencryptedSaltMinLength: 32 |
375 | | }); |
376 | 1 | password.hash('test', function(err, salt, hash) { |
377 | 1 | password.compare('test', salt, function(err, hash2) { |
378 | 1 | expect(err).to.not.exist; |
379 | 1 | expect(hash2).to.equal(hash); |
380 | 1 | expect(hash).to.equal(''); |
381 | 1 | done(); |
382 | | }); |
383 | | }); |
384 | | }); |
385 | | |
386 | 1 | it('should work with a high hashLength', function(done) { |
387 | 1 | password.configure({ |
388 | | hashLength: 1000, |
389 | | iterations: [100, 200], |
390 | | key: 'ENCRYPTION KEY', |
391 | | unencryptedSaltMinLength: 32 |
392 | | }); |
393 | 1 | password.hash('test', function(err, salt, hash) { |
394 | 1 | password.compare('test', salt, function(err, hash2) { |
395 | 1 | expect(err).to.not.exist; |
396 | 1 | expect(hash2).to.equal(hash); |
397 | 1 | done(); |
398 | | }); |
399 | | }); |
400 | | }); |
401 | | |
402 | | }); |
403 | | |
404 | 1 | describe('using other encryption keys', function() { |
405 | | |
406 | 1 | it('should work with symbols in the key', function(done) { |
407 | 1 | password.configure({ |
408 | | hashLength: 128, |
409 | | iterations: [100, 200], |
410 | | key: '~!@#$%^&*()_+`1234567890-=\'][{}|,./<>?"asd"', |
411 | | unencryptedSaltMinLength: 32 |
412 | | }); |
413 | 1 | password.hash('test', function(err, salt, hash) { |
414 | 1 | password.compare('test', salt, function(err, hash2) { |
415 | 1 | expect(err).to.not.exist; |
416 | 1 | expect(hash2).to.equal(hash); |
417 | 1 | done(); |
418 | | }); |
419 | | }); |
420 | | }); |
421 | | |
422 | 1 | it('should work with UTF-8 characters in the key', function(done) { |
423 | 1 | password.configure({ |
424 | | hashLength: 128, |
425 | | iterations: [100, 200], |
426 | | key: '° © ® ™ • ½ ¼ ¾ ⅓ ⅔ † ‡ µ ¢ £ € « » ♤ ♧ ♥ ♢ ¿ � 汉语 漢語 华语 華語 中文', |
427 | | unencryptedSaltMinLength: 32 |
428 | | }); |
429 | 1 | password.hash('test', function(err, salt, hash) { |
430 | 1 | password.compare('test', salt, function(err, hash2) { |
431 | 1 | expect(err).to.not.exist; |
432 | 1 | expect(hash2).to.equal(hash); |
433 | 1 | done(); |
434 | | }); |
435 | | }); |
436 | | }); |
437 | | |
438 | 1 | it('should work with a short key', function(done) { |
439 | 1 | password.configure({ |
440 | | hashLength: 128, |
441 | | iterations: [100, 200], |
442 | | key: '0', |
443 | | unencryptedSaltMinLength: 32 |
444 | | }); |
445 | 1 | password.hash('test', function(err, salt, hash) { |
446 | 1 | password.compare('test', salt, function(err, hash2) { |
447 | 1 | expect(err).to.not.exist; |
448 | 1 | expect(hash2).to.equal(hash); |
449 | 1 | done(); |
450 | | }); |
451 | | }); |
452 | | }); |
453 | | |
454 | 1 | it('should work with a long key', function(done) { |
455 | 1 | var key = new Array(10000).join('test '); |
456 | 1 | password.configure({ |
457 | | hashLength: 128, |
458 | | iterations: [100, 200], |
459 | | key: key, |
460 | | unencryptedSaltMinLength: 32 |
461 | | }); |
462 | 1 | password.hash('test', function(err, salt, hash) { |
463 | 1 | password.compare('test', salt, function(err, hash2) { |
464 | 1 | expect(err).to.not.exist; |
465 | 1 | expect(hash2).to.equal(hash); |
466 | 1 | done(); |
467 | | }); |
468 | | }); |
469 | | }); |
470 | | |
471 | | }); |