initial commit for GitHub
This commit is contained in:
271
core/convenience.js
Normal file
271
core/convenience.js
Normal file
@@ -0,0 +1,271 @@
|
||||
/** @fileOverview Convenince functions centered around JSON encapsulation.
|
||||
*
|
||||
* @author Emily Stark
|
||||
* @author Mike Hamburg
|
||||
* @author Dan Boneh
|
||||
*/
|
||||
|
||||
/** @namespace JSON encapsulation */
|
||||
sjcl.json = {
|
||||
/** Default values for encryption */
|
||||
defaults: { v:1, iter:1000, 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 {String} The ciphertext.
|
||||
* @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;
|
||||
j._add(p, params);
|
||||
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;
|
||||
}
|
||||
if (typeof plaintext === "string") {
|
||||
plaintext = sjcl.codec.utf8String.toBits(plaintext);
|
||||
}
|
||||
prp = new sjcl.cipher[p.cipher](password);
|
||||
|
||||
/* return the json data */
|
||||
j._add(rp, p);
|
||||
rp.key = password;
|
||||
|
||||
/* do the encryption */
|
||||
p.ct = sjcl.mode[p.mode].encrypt(prp, plaintext, p.iv, p.adata, p.tag);
|
||||
|
||||
return j.encode(j._subtract(p, j.defaults));
|
||||
},
|
||||
|
||||
/** 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) {
|
||||
params = params || {};
|
||||
rp = rp || {};
|
||||
|
||||
var j = sjcl.json, p = j._add(j._add(j._add({},j.defaults),j.decode(ciphertext)), params, true), ct, tmp, prp;
|
||||
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;
|
||||
}
|
||||
prp = new sjcl.cipher[p.cipher](password);
|
||||
|
||||
/* do the decryption */
|
||||
ct = sjcl.mode[p.mode].decrypt(prp, p.ct, p.iv, p.adata, p.tag);
|
||||
|
||||
/* return the json data */
|
||||
j._add(rp, p);
|
||||
rp.key = password;
|
||||
|
||||
return sjcl.codec.utf8String.fromBits(ct);
|
||||
},
|
||||
|
||||
/** 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],1) + '"';
|
||||
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(/^([a-z][a-z0-9]*):(?:(\d+)|"([a-z0-9+\/%*_.@=\-]*)")$/i))) {
|
||||
throw new sjcl.exception.invalid("json decode: this isn't json!");
|
||||
}
|
||||
if (m[2]) {
|
||||
out[m[1]] = parseInt(m[2],10);
|
||||
} else {
|
||||
out[m[1]] = m[1].match(/^(ct|salt|iv)$/) ? sjcl.codec.base64.toBits(m[3]) : unescape(m[3]);
|
||||
}
|
||||
}
|
||||
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} The password.
|
||||
* @param {Object} 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) };
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user