Created
March 24, 2014 12:13
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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