forked from RNCryptor/rncryptor-js
-
Notifications
You must be signed in to change notification settings - Fork 1
/
rncryptor.js
96 lines (73 loc) · 3.06 KB
/
rncryptor.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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 bitArray
options:
iv
encryption_salt
html_salt
Returns ciphertext bitArray
*/
RNCryptor.Encrypt = function(password, plaintext, options) {
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 message;
}
/*
Takes password string and message (ciphertext) bitArray
options:
iv
encryption_salt
html_salt
Returns plaintext bitArray
*/
RNCryptor.Decrypt = function(password, message, options) {
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 decrypted;
}