diff --git a/index.js b/index.js index 77e75f9..1de11ad 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ var v1 = require('./src/v1'); var v2 = require('./src/v2'); +var pbkdf2 = require('./src/pbkdf2'); module.exports = { encryptLogin: v1.encryptLogin, @@ -21,4 +22,6 @@ module.exports = { _insertStringPseudoRandomly: v2._insertStringPseudoRandomly, _getOneCharPerRule: v2._getOneCharPerRule, _renderPassword: v2._renderPassword, + + pbkdf2: pbkdf2 }; \ No newline at end of file diff --git a/lib/lesspass.js b/lib/lesspass.js index 9ed1892..3b334c4 100644 --- a/lib/lesspass.js +++ b/lib/lesspass.js @@ -1,6 +1,7 @@ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.LessPass = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= maxLength) { - return {value: generatedPassword, entropy: quotient} + return {value: generatedPassword, entropy: quotient}; } var longDivision = quotient.divmod(setOfCharacters.length); generatedPassword += setOfCharacters[longDivision.remainder]; - return consumeEntropy(generatedPassword, longDivision.quotient, setOfCharacters, maxLength) + return consumeEntropy(generatedPassword, longDivision.quotient, setOfCharacters, maxLength); } function insertStringPseudoRandomly(generatedPassword, entropy, string) { @@ -13746,14 +13792,14 @@ function insertStringPseudoRandomly(generatedPassword, entropy, string) { function getOneCharPerRule(entropy, rules) { var oneCharPerRules = ''; rules.forEach(function (rule) { - var password = consumeEntropy('', entropy, subsetOfChars[rule], 1); + var password = consumeEntropy('', entropy, characterSubsets[rule], 1); oneCharPerRules += password.value; entropy = password.entropy; }); return {value: oneCharPerRules, entropy: entropy}; } -function validRules(passwordProfile) { +function getConfiguredRules(passwordProfile) { return ['lowercase', 'uppercase', 'digits', 'symbols'].filter(function (rule) { return passwordProfile[rule]; }); @@ -13761,12 +13807,12 @@ function validRules(passwordProfile) { function renderPassword(entropy, passwordProfile) { var _passwordProfile = objectAssign({}, defaultPasswordProfile, passwordProfile); - var rules = validRules(_passwordProfile); + var rules = getConfiguredRules(_passwordProfile); var setOfCharacters = getSetOfCharacters(rules); var password = consumeEntropy('', bigInt(entropy, 16), setOfCharacters, _passwordProfile.length - rules.length); var charactersToAdd = getOneCharPerRule(password.entropy, rules); return insertStringPseudoRandomly(password.value, charactersToAdd.entropy, charactersToAdd.value); } -},{"big-integer":3,"bluebird":4,"object-assign":19,"pbkdf2":20}]},{},[1])(1) +},{"./pbkdf2":47,"big-integer":3,"object-assign":19}]},{},[1])(1) }); \ No newline at end of file diff --git a/src/pbkdf2.js b/src/pbkdf2.js new file mode 100644 index 0000000..4506582 --- /dev/null +++ b/src/pbkdf2.js @@ -0,0 +1,51 @@ +var pbkdf2 = require('pbkdf2'); +var Promise = require("bluebird"); + + +function shouldUseNative() { + return !!(typeof window !== 'undefined' && window.crypto && window.crypto.subtle); +} + +function pbkdf2Native(password, salt, iterations, keylen, digest) { + var algorithms = { + 'sha1':'SHA-1', + 'sha-1':'SHA-1', + 'sha256':'SHA-256', + 'sha-256':'SHA-256', + 'sha512':'SHA-512', + 'sha-512':'SHA-512', + }; + return window.crypto.subtle.importKey('raw', Unibabel.utf8ToBuffer(password), 'PBKDF2', false, ['deriveKey']) + .then(function (key) { + var algo = { + name: 'PBKDF2', + salt: Unibabel.utf8ToBuffer(salt), + iterations: iterations, + hash: algorithms[digest.toLowerCase()], + }; + return window.crypto.subtle.deriveKey(algo, key, { + name: 'AES-CTR', + length: keylen * 8 + }, true, ['encrypt', 'decrypt']); + }) + .then(function (derivedKey) { + return window.crypto.subtle.exportKey('raw', derivedKey).then(function (keyArrayBuffer) { + return Unibabel.bufferToHex(new Uint8Array(keyArrayBuffer)); + }); + }); +} + +function pbkdf2Browserified(password, salt, iterations, keylen, digest) { + return new Promise(function (resolve, reject) { + pbkdf2.pbkdf2(password, salt, iterations, keylen, digest, function (error, key) { + if (error) { + reject('error in pbkdf2'); + } else { + resolve(key.toString('hex')); + } + }); + }); +} + + +module.exports = shouldUseNative() ? pbkdf2Native : pbkdf2Browserified; \ No newline at end of file diff --git a/src/v2.js b/src/v2.js index 714bf75..f0e4102 100644 --- a/src/v2.js +++ b/src/v2.js @@ -1,5 +1,4 @@ -var Promise = require("bluebird"); -var pbkdf2 = require('pbkdf2'); +var pbkdf2 = require('./pbkdf2'); var bigInt = require("big-integer"); var objectAssign = require('object-assign'); @@ -15,8 +14,9 @@ module.exports = { }; function generatePassword(site, login, masterPassword, passwordProfile) { - return calcEntropy(site, login, masterPassword, passwordProfile).then(function (entropy) { - return renderPassword(entropy, passwordProfile); + var _passwordProfile = objectAssign({}, defaultPasswordProfile, passwordProfile); + return calcEntropy(site, login, masterPassword, _passwordProfile).then(function (entropy) { + return renderPassword(entropy, _passwordProfile); }); } @@ -33,17 +33,8 @@ var defaultPasswordProfile = { }; function calcEntropy(site, login, masterPassword, passwordProfile) { - var _passwordProfile = objectAssign({}, defaultPasswordProfile, passwordProfile); - return new Promise(function (resolve, reject) { - var salt = site + login + _passwordProfile.index.toString(16); - pbkdf2.pbkdf2(masterPassword, salt, _passwordProfile.iterations, _passwordProfile.keylen, _passwordProfile.digest, function (error, key) { - if (error) { - reject('error in pbkdf2'); - } else { - resolve(key.toString('hex')); - } - }); - }); + var salt = site + login + passwordProfile.index.toString(16); + return pbkdf2(masterPassword, salt, passwordProfile.iterations, passwordProfile.keylen, passwordProfile.digest); } var characterSubsets = { diff --git a/tests/helper.js b/tests/helper.js index f7b3ea6..a8c194a 100644 --- a/tests/helper.js +++ b/tests/helper.js @@ -1,4 +1,4 @@ // globals global.chai = require('chai'); global.LessPass = require('../index'); -global.bigInt = require("big-integer"); +global.bigInt = require("big-integer"); \ No newline at end of file diff --git a/tests/karma.config.js b/tests/karma.config.js index 97309ea..775a1d0 100644 --- a/tests/karma.config.js +++ b/tests/karma.config.js @@ -5,6 +5,8 @@ module.exports = function (config) { files: [ 'node_modules/bluebird/js/browser/bluebird.core.min.js', 'node_modules/big-integer/BigInteger.min.js', + 'node_modules/unibabel/index.js', + 'node_modules/unibabel/unibabel.hex.js', 'lib/lesspass.js', 'tests/**/*.js' ], @@ -14,7 +16,7 @@ module.exports = function (config) { 'tests/karma.webcrypto.config.js', ], preprocessors: {}, - reporters: ['progress'], + reporters: ['spec'], port: 9876, colors: true, logLevel: config.LOG_INFO, diff --git a/tests/pbkdf2.tests.js b/tests/pbkdf2.tests.js new file mode 100644 index 0000000..c3f692d --- /dev/null +++ b/tests/pbkdf2.tests.js @@ -0,0 +1,16 @@ +var assert = chai.assert; + +describe('LessPass', function () { + describe('pbkdf2', function () { + it('should secret, salt, 2, 32, sha256', function () { + return LessPass.pbkdf2('secret', 'salt', 2, 32, 'sha256').then(function (key) { + assert.equal('f92f45f9df4c2aeabae1ed3c16f7b64660c1f8e377fa9b4699b23c2c3a29f569', key); + }) + }); + }); +}); + + + + +