Skip to content

Instantly share code, notes, and snippets.

@donpark
Created March 7, 2011 05:59
Show Gist options
  • Save donpark/858134 to your computer and use it in GitHub Desktop.
Save donpark/858134 to your computer and use it in GitHub Desktop.
async PBKDF2 with optional progress callback
// original, synch version, is at:
// http://code.google.com/p/crypto-js/source/browse/branches/2.0.x/src/PBKDF2.js
(function(){
// Shortcuts
var C = Crypto,
util = C.util,
charenc = C.charenc,
UTF8 = charenc.UTF8,
Binary = charenc.Binary;
if (!C.nextTick) {
// node.js has setTime out but prefer process.nextTick
if (typeof process != 'undefined' && typeof process.nextTick !== 'undefined') {
C.nextTick = process.nextTick;
} else if (typeof setTimeout !== 'undefined') {
C.nextTick = function (callback) {
setTimeout(callback, 0);
};
}
}
C.PBKDF2Async = function (password, salt, keylen, options, callback, progress) {
// Convert to byte arrays
if (password.constructor == String) password = UTF8.stringToBytes(password);
if (salt.constructor == String) salt = UTF8.stringToBytes(salt);
/* else, assume byte arrays already */
// Defaults
var hasher = options && options.hasher || C.SHA1,
iterations = options && options.iterations || 1;
// Pseudo-random function
function PRF(password, salt) {
return C.HMAC(hasher, salt, password, { asBytes: true });
}
var nextTick = C.nextTick;
// Generate key
var derivedKeyBytes = [],
blockindex = 1;
var outer, inner, percent;
nextTick(outer = function () {
if (derivedKeyBytes.length < keylen) {
if (progress) {
progress(derivedKeyBytes.length / keylen * 100 | 0);
}
var block = PRF(password, salt.concat(util.wordsToBytes([blockindex])));
var u = block, i = 1;
nextTick(inner = function () {
if (i < iterations) {
u = PRF(password, u);
for (var j = 0; j < block.length; j++) block[j] ^= u[j];
i++;
nextTick(inner);
} else {
derivedKeyBytes = derivedKeyBytes.concat(block);
blockindex++;
nextTick(outer);
}
});
} else {
if (progress) {
progress(100);
}
// Truncate excess bytes
derivedKeyBytes.length = keylen;
callback(
options && options.asBytes ? derivedKeyBytes :
options && options.asString ? Binary.bytesToString(derivedKeyBytes) :
util.bytesToHex(derivedKeyBytes));
}
});
};
})();
@donpark
Copy link
Author

donpark commented Mar 7, 2011

Sample code:
var Crypto = require('../lib/crypto').Crypto;

var password = '123456';
var salt = 'abcdef';
var keylen = 1024;
var options = { iterations: 1000 };

function test (title) {
    var syncResult = Crypto.PBKDF2(password, salt, keylen, options);
    //console.log('PBKDF2 returned: ' + JSON.stringify(syncResult));

    Crypto.PBKDF2Async(password, salt, keylen, options, function (asyncResult) {
        //console.log('PBKDF2Async returned: ' + JSON.stringify(asyncResult));
        if (syncResult === asyncResult) {
            console.log(title + ' - PBKDF2 and PBKDF2Async result match');
        }
        else {
            console.log(title + ' - PBKDF2 and PBKDF2Async result mismatch');
            console.log('expected: ' + syncResult);
            console.log('got: ' + asyncResult);
        }
    }, function (percent) {
        console.log('percent complete: ' + percent + '%');
    });
}

// test('default nextTick');
Crypto.nextTick = process.nextTick;
test('process.nextTick');

@donpark
Copy link
Author

donpark commented Mar 7, 2011

Sample output:
$ node test-PBKDF2Async.js
percent complete: 0%
percent complete: 7%
percent complete: 15%
percent complete: 23%
percent complete: 31%
percent complete: 39%
percent complete: 46%
percent complete: 54%
percent complete: 62%
percent complete: 70%
percent complete: 78%
percent complete: 85%
percent complete: 93%
percent complete: 100%
process.nextTick - PBKDF2 and PBKDF2Async result match

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment