/** @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, 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") { 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 */ p.ct = sjcl.mode[p.mode].encrypt(prp, plaintext, p.iv, adata, p.ts); //return j.encode(j._subtract(p, j.defaults)); return j.encode(p); }, /** 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, 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 */ 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; 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],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