Skip to content

Instantly share code, notes, and snippets.

@toolness
Created March 24, 2014 12:13
Show Gist options
  • Save toolness/9739023 to your computer and use it in GitHub Desktop.
Save toolness/9739023 to your computer and use it in GitHub Desktop.
Noobish attempt at making a password-based encryption/decryption mini-library in node.
var crypto = require('crypto');
var CIPHERS = {
'aes256': {
blockSize: 128 / 8,
keySize: 256 / 8
}
};
var HASHES = {
'sha256': {
keySize: 256 / 8
}
};
var DEFAULT_CIPHER = 'aes256';
var DEFAULT_HASH = 'sha256';
function getCipherInfo(algorithm) {
algorithm = algorithm || DEFAULT_CIPHER;
if (!(algorithm in CIPHERS))
throw new Error('unknown algorithm: ' + algorithm);
CIPHERS[algorithm].name = algorithm;
return CIPHERS[algorithm];
}
function getHmacInfo(algorithm) {
algorithm = algorithm || DEFAULT_HASH;
if (!(algorithm in HASHES))
throw new Error('unknown hmac algorithm: ' + algorithm);
HASHES[algorithm].name = algorithm;
return HASHES[algorithm];
}
function makeSignature(hmacInfo, hmacKey, salt, cipherText) {
var hmac = crypto.createHmac(hmacInfo.name, hmacKey);
hmac.update(salt);
hmac.update('.' + cipherText);
return hmac.digest();
}
// Taken from https://github.com/mozilla/node-client-sessions.
function constantTimeEquals(a, b) {
// Ideally this would be a native function, so it's less sensitive to how the
// JS engine might optimize.
var ret = 0;
if (a.length !== b.length)
return false;
for (var i = 0; i < a.length; i++)
ret |= a.readUInt8(i) ^ b.readUInt8(i);
return ret === 0;
}
function zeroFill() {
[].slice.call(arguments).forEach(function(buf) {
if (buf instanceof Buffer) buf.fill(0, 0, buf.length);
});
}
function zeroConcat() {
var args = [].slice.call(arguments);
var result = Buffer.concat(args);
zeroFill.apply(null, args);
return result;
}
function Crypter(options) {
if (!(this instanceof Crypter)) return new Crypter(options);
var password = options.password;
var iterations = options.iterations;
var cipherInfo = getCipherInfo(options.algorithm);
var hmacInfo = getHmacInfo(options.hmacAlgorithm);
this.encrypt = function encrypt(value, encoding) {
try {
var salt = crypto.randomBytes(cipherInfo.blockSize);
var encKey = crypto.pbkdf2Sync(password, salt, iterations,
cipherInfo.keySize);
var hmacKey = crypto.pbkdf2Sync(password, salt, iterations + 1,
hmacInfo.keySize);
var cipher = crypto.createCipheriv(cipherInfo.name, encKey, salt);
var encrypted = cipher.update(value, encoding, 'base64') +
cipher.final('base64');
var signature = makeSignature(hmacInfo, hmacKey, salt, encrypted);
return [salt.toString('hex'), encrypted,
signature.toString('hex')].join(':');
} finally {
zeroFill(encKey, hmacKey);
}
};
this.decrypt = function decrypt(value) {
try {
var parts = value.split(':');
var salt = new Buffer(parts[0], 'hex');
var encrypted = parts[1];
var theirSignature = new Buffer(parts[2], 'hex');
var encKey = crypto.pbkdf2Sync(password, salt, iterations,
cipherInfo.keySize);
var hmacKey = crypto.pbkdf2Sync(password, salt, iterations + 1,
hmacInfo.keySize);
var decipher = crypto.createDecipheriv(cipherInfo.name, encKey, salt);
var decrypted = zeroConcat(decipher.update(encrypted, 'base64'),
decipher.final());
var ourSignature = makeSignature(hmacInfo, hmacKey, salt, encrypted);
if (!constantTimeEquals(theirSignature, ourSignature))
throw new Error('Invalid signature');
return decrypted;
} finally {
zeroFill(encKey, hmacKey);
}
};
return this;
}
module.exports = Crypter;
if (!module.parent) (function() {
var crypter = new Crypter({password: 'lol', iterations: 5000});
var encrypted = crypter.encrypt('self-test successful.', 'utf8');
console.log(crypter.decrypt(encrypted).toString('utf8'));
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment