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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
|
/** @fileOverview Convenince functions centered around JSON encapsulation.
*
* @author Emily Stark
* @author Mike Hamburg
* @author Dan Boneh
*/
/**
* JSON encapsulation
* @namespace
*/
sjcl.json = {
/** Default values for encryption */
defaults: { v:1, iter:10000, ks:128, ts:64, mode:"ccm", adata:"", cipher:"aes" },
/** Simple encryption function.
* @param {String|bitArray} password The password or key.
* @param {String} plaintext The data to encrypt.
* @param {Object} [params] The parameters including tag, iv and salt.
* @param {Object} [rp] A returned version with filled-in parameters.
* @return {Object} The cipher raw data.
* @throws {sjcl.exception.invalid} if a parameter is invalid.
*/
_encrypt: function (password, plaintext, params, rp) {
params = params || {};
rp = rp || {};
var j = sjcl.json, p = j._add({ iv: sjcl.random.randomWords(4,0) },
j.defaults), tmp, prp, adata;
j._add(p, params);
adata = p.adata;
if (typeof p.salt === "string") {
p.salt = sjcl.codec.base64.toBits(p.salt);
}
if (typeof p.iv === "string") {
p.iv = sjcl.codec.base64.toBits(p.iv);
}
if (!sjcl.mode[p.mode] ||
!sjcl.cipher[p.cipher] ||
(typeof password === "string" && p.iter <= 100) ||
(p.ts !== 64 && p.ts !== 96 && p.ts !== 128) ||
(p.ks !== 128 && p.ks !== 192 && p.ks !== 256) ||
(p.iv.length < 2 || p.iv.length > 4)) {
throw new sjcl.exception.invalid("json encrypt: invalid parameters");
}
if (typeof password === "string") {
tmp = sjcl.misc.cachedPbkdf2(password, p);
password = tmp.key.slice(0,p.ks/32);
p.salt = tmp.salt;
} else if (sjcl.ecc && password instanceof sjcl.ecc.elGamal.publicKey) {
tmp = password.kem();
p.kemtag = tmp.tag;
password = tmp.key.slice(0,p.ks/32);
}
if (typeof plaintext === "string") {
plaintext = sjcl.codec.utf8String.toBits(plaintext);
}
if (typeof adata === "string") {
p.adata = adata = sjcl.codec.utf8String.toBits(adata);
}
prp = new sjcl.cipher[p.cipher](password);
/* return the json data */
j._add(rp, p);
rp.key = password;
/* do the encryption */
if (p.mode === "ccm" && sjcl.arrayBuffer && sjcl.arrayBuffer.ccm && plaintext instanceof ArrayBuffer) {
p.ct = sjcl.arrayBuffer.ccm.encrypt(prp, plaintext, p.iv, adata, p.ts);
} else {
p.ct = sjcl.mode[p.mode].encrypt(prp, plaintext, p.iv, adata, p.ts);
}
//return j.encode(j._subtract(p, j.defaults));
return p;
},
/** Simple encryption function.
* @param {String|bitArray} password The password or key.
* @param {String} plaintext The data to encrypt.
* @param {Object} [params] The parameters including tag, iv and salt.
* @param {Object} [rp] A returned version with filled-in parameters.
* @return {String} The ciphertext serialized data.
* @throws {sjcl.exception.invalid} if a parameter is invalid.
*/
encrypt: function (password, plaintext, params, rp) {
var j = sjcl.json, p = j._encrypt.apply(j, arguments);
return j.encode(p);
},
/** Simple decryption function.
* @param {String|bitArray} password The password or key.
* @param {Object} ciphertext The cipher raw data to decrypt.
* @param {Object} [params] Additional non-default parameters.
* @param {Object} [rp] A returned object with filled parameters.
* @return {String} The plaintext.
* @throws {sjcl.exception.invalid} if a parameter is invalid.
* @throws {sjcl.exception.corrupt} if the ciphertext is corrupt.
*/
_decrypt: function (password, ciphertext, params, rp) {
params = params || {};
rp = rp || {};
var j = sjcl.json, p = j._add(j._add(j._add({},j.defaults),ciphertext), params, true), ct, tmp, prp, adata=p.adata;
if (typeof p.salt === "string") {
p.salt = sjcl.codec.base64.toBits(p.salt);
}
if (typeof p.iv === "string") {
p.iv = sjcl.codec.base64.toBits(p.iv);
}
if (!sjcl.mode[p.mode] ||
!sjcl.cipher[p.cipher] ||
(typeof password === "string" && p.iter <= 100) ||
(p.ts !== 64 && p.ts !== 96 && p.ts !== 128) ||
(p.ks !== 128 && p.ks !== 192 && p.ks !== 256) ||
(!p.iv) ||
(p.iv.length < 2 || p.iv.length > 4)) {
throw new sjcl.exception.invalid("json decrypt: invalid parameters");
}
if (typeof password === "string") {
tmp = sjcl.misc.cachedPbkdf2(password, p);
password = tmp.key.slice(0,p.ks/32);
p.salt = tmp.salt;
} else if (sjcl.ecc && password instanceof sjcl.ecc.elGamal.secretKey) {
password = password.unkem(sjcl.codec.base64.toBits(p.kemtag)).slice(0,p.ks/32);
}
if (typeof adata === "string") {
adata = sjcl.codec.utf8String.toBits(adata);
}
prp = new sjcl.cipher[p.cipher](password);
/* do the decryption */
if (p.mode === "ccm" && sjcl.arrayBuffer && sjcl.arrayBuffer.ccm && p.ct instanceof ArrayBuffer) {
ct = sjcl.arrayBuffer.ccm.decrypt(prp, p.ct, p.iv, p.tag, adata, p.ts);
} else {
ct = sjcl.mode[p.mode].decrypt(prp, p.ct, p.iv, adata, p.ts);
}
/* return the json data */
j._add(rp, p);
rp.key = password;
if (params.raw === 1) {
return ct;
} else {
return sjcl.codec.utf8String.fromBits(ct);
}
},
/** Simple decryption function.
* @param {String|bitArray} password The password or key.
* @param {String} ciphertext The ciphertext to decrypt.
* @param {Object} [params] Additional non-default parameters.
* @param {Object} [rp] A returned object with filled parameters.
* @return {String} The plaintext.
* @throws {sjcl.exception.invalid} if a parameter is invalid.
* @throws {sjcl.exception.corrupt} if the ciphertext is corrupt.
*/
decrypt: function (password, ciphertext, params, rp) {
var j = sjcl.json;
return j._decrypt(password, j.decode(ciphertext), params, rp);
},
/** Encode a flat structure into a JSON string.
* @param {Object} obj The structure to encode.
* @return {String} A JSON string.
* @throws {sjcl.exception.invalid} if obj has a non-alphanumeric property.
* @throws {sjcl.exception.bug} if a parameter has an unsupported type.
*/
encode: function (obj) {
var i, out='{', comma='';
for (i in obj) {
if (obj.hasOwnProperty(i)) {
if (!i.match(/^[a-z0-9]+$/i)) {
throw new sjcl.exception.invalid("json encode: invalid property name");
}
out += comma + '"' + i + '":';
comma = ',';
switch (typeof obj[i]) {
case 'number':
case 'boolean':
out += obj[i];
break;
case 'string':
out += '"' + escape(obj[i]) + '"';
break;
case 'object':
out += '"' + sjcl.codec.base64.fromBits(obj[i],0) + '"';
break;
default:
throw new sjcl.exception.bug("json encode: unsupported type");
}
}
}
return out+'}';
},
/** Decode a simple (flat) JSON string into a structure. The ciphertext,
* adata, salt and iv will be base64-decoded.
* @param {String} str The string.
* @return {Object} The decoded structure.
* @throws {sjcl.exception.invalid} if str isn't (simple) JSON.
*/
decode: function (str) {
str = str.replace(/\s/g,'');
if (!str.match(/^\{.*\}$/)) {
throw new sjcl.exception.invalid("json decode: this isn't json!");
}
var a = str.replace(/^\{|\}$/g, '').split(/,/), out={}, i, m;
for (i=0; i<a.length; i++) {
if (!(m=a[i].match(/^\s*(?:(["']?)([a-z][a-z0-9]*)\1)\s*:\s*(?:(-?\d+)|"([a-z0-9+\/%*_.@=\-]*)"|(true|false))$/i))) {
throw new sjcl.exception.invalid("json decode: this isn't json!");
}
if (m[3] != null) {
out[m[2]] = parseInt(m[3],10);
} else if (m[4] != null) {
out[m[2]] = m[2].match(/^(ct|adata|salt|iv)$/) ? sjcl.codec.base64.toBits(m[4]) : unescape(m[4]);
} else if (m[5] != null) {
out[m[2]] = m[5] === 'true';
}
}
return out;
},
/** Insert all elements of src into target, modifying and returning target.
* @param {Object} target The object to be modified.
* @param {Object} src The object to pull data from.
* @param {boolean} [requireSame=false] If true, throw an exception if any field of target differs from corresponding field of src.
* @return {Object} target.
* @private
*/
_add: function (target, src, requireSame) {
if (target === undefined) { target = {}; }
if (src === undefined) { return target; }
var i;
for (i in src) {
if (src.hasOwnProperty(i)) {
if (requireSame && target[i] !== undefined && target[i] !== src[i]) {
throw new sjcl.exception.invalid("required parameter overridden");
}
target[i] = src[i];
}
}
return target;
},
/** Remove all elements of minus from plus. Does not modify plus.
* @private
*/
_subtract: function (plus, minus) {
var out = {}, i;
for (i in plus) {
if (plus.hasOwnProperty(i) && plus[i] !== minus[i]) {
out[i] = plus[i];
}
}
return out;
},
/** Return only the specified elements of src.
* @private
*/
_filter: function (src, filter) {
var out = {}, i;
for (i=0; i<filter.length; i++) {
if (src[filter[i]] !== undefined) {
out[filter[i]] = src[filter[i]];
}
}
return out;
}
};
/** Simple encryption function; convenient shorthand for sjcl.json.encrypt.
* @param {String|bitArray} password The password or key.
* @param {String} plaintext The data to encrypt.
* @param {Object} [params] The parameters including tag, iv and salt.
* @param {Object} [rp] A returned version with filled-in parameters.
* @return {String} The ciphertext.
*/
sjcl.encrypt = sjcl.json.encrypt;
/** Simple decryption function; convenient shorthand for sjcl.json.decrypt.
* @param {String|bitArray} password The password or key.
* @param {String} ciphertext The ciphertext to decrypt.
* @param {Object} [params] Additional non-default parameters.
* @param {Object} [rp] A returned object with filled parameters.
* @return {String} The plaintext.
*/
sjcl.decrypt = sjcl.json.decrypt;
/** The cache for cachedPbkdf2.
* @private
*/
sjcl.misc._pbkdf2Cache = {};
/** Cached PBKDF2 key derivation.
* @param {String} password The password.
* @param {Object} [obj] The derivation params (iteration count and optional salt).
* @return {Object} The derived data in key, the salt in salt.
*/
sjcl.misc.cachedPbkdf2 = function (password, obj) {
var cache = sjcl.misc._pbkdf2Cache, c, cp, str, salt, iter;
obj = obj || {};
iter = obj.iter || 1000;
/* open the cache for this password and iteration count */
cp = cache[password] = cache[password] || {};
c = cp[iter] = cp[iter] || { firstSalt: (obj.salt && obj.salt.length) ?
obj.salt.slice(0) : sjcl.random.randomWords(2,0) };
salt = (obj.salt === undefined) ? c.firstSalt : obj.salt;
c[salt] = c[salt] || sjcl.misc.pbkdf2(password, salt, obj.iter);
return { key: c[salt].slice(0), salt:salt.slice(0) };
};
|