You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
78 lines
3.4 KiB
78 lines
3.4 KiB
var sjcl = require('./sjcl');
|
|
var RNCryptor = {};
|
|
/*
|
|
Takes password string and salt WordArray
|
|
Returns key bitArray
|
|
*/
|
|
RNCryptor.KeyForPassword = function (password, salt) {
|
|
var hmacSHA1 = function (key) {
|
|
var hasher = new sjcl.misc.hmac(key, sjcl.hash.sha1);
|
|
this.encrypt = function () {
|
|
return hasher.encrypt.apply(hasher, arguments);
|
|
};
|
|
};
|
|
return sjcl.misc.pbkdf2(password, salt, 10000, 32 * 8, hmacSHA1);
|
|
};
|
|
/*
|
|
Takes password string and plaintext base64
|
|
options:
|
|
iv
|
|
encryption_salt
|
|
html_salt
|
|
Returns ciphertext base64
|
|
*/
|
|
RNCryptor.Encrypt = function (password, plaintextBase64, options) {
|
|
var plaintext = sjcl.codec.base64.toBits(plaintextBase64);
|
|
options = options || {};
|
|
var encryption_salt = options["encryption_salt"] || sjcl.random.randomWords(8 / 4); // FIXME: Need to seed PRNG
|
|
var encryption_key = RNCryptor.KeyForPassword(password, encryption_salt);
|
|
var hmac_salt = options["hmac_salt"] || sjcl.random.randomWords(8 / 4);
|
|
var hmac_key = RNCryptor.KeyForPassword(password, hmac_salt);
|
|
var iv = options["iv"] || sjcl.random.randomWords(16 / 4);
|
|
var version = sjcl.codec.hex.toBits("03");
|
|
var options = sjcl.codec.hex.toBits("01");
|
|
var message = sjcl.bitArray.concat(version, options);
|
|
message = sjcl.bitArray.concat(message, encryption_salt);
|
|
message = sjcl.bitArray.concat(message, hmac_salt);
|
|
message = sjcl.bitArray.concat(message, iv);
|
|
var aes = new sjcl.cipher.aes(encryption_key);
|
|
sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."]();
|
|
var encrypted = sjcl.mode.cbc.encrypt(aes, plaintext, iv);
|
|
message = sjcl.bitArray.concat(message, encrypted);
|
|
var hmac = new sjcl.misc.hmac(hmac_key).encrypt(message);
|
|
message = sjcl.bitArray.concat(message, hmac);
|
|
return sjcl.codec.base64.fromBits(message);
|
|
};
|
|
/*
|
|
Takes password string and message (ciphertext) base64
|
|
options:
|
|
iv
|
|
encryption_salt
|
|
html_salt
|
|
Returns plaintext base64
|
|
*/
|
|
RNCryptor.Decrypt = function (password, messageBase64, options) {
|
|
var message = sjcl.codec.base64.toBits(messageBase64);
|
|
options = options || {};
|
|
var version = sjcl.bitArray.extract(message, 0 * 8, 8);
|
|
var options = sjcl.bitArray.extract(message, 1 * 8, 8);
|
|
var encryption_salt = sjcl.bitArray.bitSlice(message, 2 * 8, 10 * 8);
|
|
var encryption_key = RNCryptor.KeyForPassword(password, encryption_salt);
|
|
var hmac_salt = sjcl.bitArray.bitSlice(message, 10 * 8, 18 * 8);
|
|
var hmac_key = RNCryptor.KeyForPassword(password, hmac_salt);
|
|
var iv = sjcl.bitArray.bitSlice(message, 18 * 8, 34 * 8);
|
|
var ciphertext_end = sjcl.bitArray.bitLength(message) - (32 * 8);
|
|
var ciphertext = sjcl.bitArray.bitSlice(message, 34 * 8, ciphertext_end);
|
|
var hmac = sjcl.bitArray.bitSlice(message, ciphertext_end);
|
|
var expected_hmac = new sjcl.misc.hmac(hmac_key).encrypt(sjcl.bitArray.bitSlice(message, 0, ciphertext_end));
|
|
// .equal is of consistent time
|
|
if (!sjcl.bitArray.equal(hmac, expected_hmac)) {
|
|
throw new sjcl.exception.corrupt("HMAC mismatch or bad password.");
|
|
}
|
|
var aes = new sjcl.cipher.aes(encryption_key);
|
|
sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."]();
|
|
var decrypted = sjcl.mode.cbc.decrypt(aes, ciphertext, iv);
|
|
return sjcl.codec.base64.fromBits(decrypted);
|
|
};
|
|
module.exports = RNCryptor;
|
|
//# sourceMappingURL=rncryptor.js.map
|