/** @fileOverview Javascript cryptography implementation. * * Crush to remove comments, shorten variable names and * generally reduce transmission size. * * @author Emily Stark * @author Mike Hamburg * @author Dan Boneh */ "use strict"; /*jslint indent: 2, bitwise: false, nomen: false, plusplus: false, white: false, regexp: false */ /*global document, window, escape, unescape, module, require, Uint32Array */ /** * The Stanford Javascript Crypto Library, top-level namespace. * @namespace */ var sjcl = { /** * Symmetric ciphers. * @namespace */ cipher: {}, /** * Hash functions. Right now only SHA256 is implemented. * @namespace */ hash: {}, /** * Key exchange functions. Right now only SRP is implemented. * @namespace */ keyexchange: {}, /** * Cipher modes of operation. * @namespace */ mode: {}, /** * Miscellaneous. HMAC and PBKDF2. * @namespace */ misc: {}, /** * Bit array encoders and decoders. * @namespace * * @description * The members of this namespace are functions which translate between * SJCL's bitArrays and other objects (usually strings). Because it * isn't always clear which direction is encoding and which is decoding, * the method names are "fromBits" and "toBits". */ codec: {}, /** * Exceptions. * @namespace */ exception: { /** * Ciphertext is corrupt. * @constructor */ corrupt: function(message) { this.toString = function() { return "CORRUPT: "+this.message; }; this.message = message; }, /** * Invalid parameter. * @constructor */ invalid: function(message) { this.toString = function() { return "INVALID: "+this.message; }; this.message = message; }, /** * Bug or missing feature in SJCL. * @constructor */ bug: function(message) { this.toString = function() { return "BUG: "+this.message; }; this.message = message; }, /** * Something isn't ready. * @constructor */ notReady: function(message) { this.toString = function() { return "NOT READY: "+this.message; }; this.message = message; } } }; /** @fileOverview Low-level AES implementation. * * This file contains a low-level implementation of AES, optimized for * size and for efficiency on several browsers. It is based on * OpenSSL's aes_core.c, a public-domain implementation by Vincent * Rijmen, Antoon Bosselaers and Paulo Barreto. * * An older version of this implementation is available in the public * domain, but this one is (c) Emily Stark, Mike Hamburg, Dan Boneh, * Stanford University 2008-2010 and BSD-licensed for liability * reasons. * * @author Emily Stark * @author Mike Hamburg * @author Dan Boneh */ /** * Schedule out an AES key for both encryption and decryption. This * is a low-level class. Use a cipher mode to do bulk encryption. * * @constructor * @param {Array} key The key as an array of 4, 6 or 8 words. */ sjcl.cipher.aes = function (key) { if (!this._tables[0][0][0]) { this._precompute(); } var i, j, tmp, encKey, decKey, sbox = this._tables[0][4], decTable = this._tables[1], keyLen = key.length, rcon = 1; if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { throw new sjcl.exception.invalid("invalid aes key size"); } this._key = [encKey = key.slice(0), decKey = []]; // schedule encryption keys for (i = keyLen; i < 4 * keyLen + 28; i++) { tmp = encKey[i-1]; // apply sbox if (i%keyLen === 0 || (keyLen === 8 && i%keyLen === 4)) { tmp = sbox[tmp>>>24]<<24 ^ sbox[tmp>>16&255]<<16 ^ sbox[tmp>>8&255]<<8 ^ sbox[tmp&255]; // shift rows and add rcon if (i%keyLen === 0) { tmp = tmp<<8 ^ tmp>>>24 ^ rcon<<24; rcon = rcon<<1 ^ (rcon>>7)*283; } } encKey[i] = encKey[i-keyLen] ^ tmp; } // schedule decryption keys for (j = 0; i; j++, i--) { tmp = encKey[j&3 ? i : i - 4]; if (i<=4 || j<4) { decKey[j] = tmp; } else { decKey[j] = decTable[0][sbox[tmp>>>24 ]] ^ decTable[1][sbox[tmp>>16 & 255]] ^ decTable[2][sbox[tmp>>8 & 255]] ^ decTable[3][sbox[tmp & 255]]; } } }; sjcl.cipher.aes.prototype = { // public /* Something like this might appear here eventually name: "AES", blockSize: 4, keySizes: [4,6,8], */ /** * Encrypt an array of 4 big-endian words. * @param {Array} data The plaintext. * @return {Array} The ciphertext. */ encrypt:function (data) { return this._crypt(data,0); }, /** * Decrypt an array of 4 big-endian words. * @param {Array} data The ciphertext. * @return {Array} The plaintext. */ decrypt:function (data) { return this._crypt(data,1); }, /** * The expanded S-box and inverse S-box tables. These will be computed * on the client so that we don't have to send them down the wire. * * There are two tables, _tables[0] is for encryption and * _tables[1] is for decryption. * * The first 4 sub-tables are the expanded S-box with MixColumns. The * last (_tables[01][4]) is the S-box itself. * * @private */ _tables: [[[],[],[],[],[]],[[],[],[],[],[]]], /** * Expand the S-box tables. * * @private */ _precompute: function () { var encTable = this._tables[0], decTable = this._tables[1], sbox = encTable[4], sboxInv = decTable[4], i, x, xInv, d=[], th=[], x2, x4, x8, s, tEnc, tDec; // Compute double and third tables for (i = 0; i < 256; i++) { th[( d[i] = i<<1 ^ (i>>7)*283 )^i]=i; } for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { // Compute sbox s = xInv ^ xInv<<1 ^ xInv<<2 ^ xInv<<3 ^ xInv<<4; s = s>>8 ^ s&255 ^ 99; sbox[x] = s; sboxInv[s] = x; // Compute MixColumns x8 = d[x4 = d[x2 = d[x]]]; tDec = x8*0x1010101 ^ x4*0x10001 ^ x2*0x101 ^ x*0x1010100; tEnc = d[s]*0x101 ^ s*0x1010100; for (i = 0; i < 4; i++) { encTable[i][x] = tEnc = tEnc<<24 ^ tEnc>>>8; decTable[i][s] = tDec = tDec<<24 ^ tDec>>>8; } } // Compactify. Considerable speedup on Firefox. for (i = 0; i < 5; i++) { encTable[i] = encTable[i].slice(0); decTable[i] = decTable[i].slice(0); } }, /** * Encryption and decryption core. * @param {Array} input Four words to be encrypted or decrypted. * @param dir The direction, 0 for encrypt and 1 for decrypt. * @return {Array} The four encrypted or decrypted words. * @private */ _crypt:function (input, dir) { if (input.length !== 4) { throw new sjcl.exception.invalid("invalid aes block size"); } var key = this._key[dir], // state variables a,b,c,d are loaded with pre-whitened data a = input[0] ^ key[0], b = input[dir ? 3 : 1] ^ key[1], c = input[2] ^ key[2], d = input[dir ? 1 : 3] ^ key[3], a2, b2, c2, nInnerRounds = key.length/4 - 2, i, kIndex = 4, out = [0,0,0,0], table = this._tables[dir], // load up the tables t0 = table[0], t1 = table[1], t2 = table[2], t3 = table[3], sbox = table[4]; // Inner rounds. Cribbed from OpenSSL. for (i = 0; i < nInnerRounds; i++) { a2 = t0[a>>>24] ^ t1[b>>16 & 255] ^ t2[c>>8 & 255] ^ t3[d & 255] ^ key[kIndex]; b2 = t0[b>>>24] ^ t1[c>>16 & 255] ^ t2[d>>8 & 255] ^ t3[a & 255] ^ key[kIndex + 1]; c2 = t0[c>>>24] ^ t1[d>>16 & 255] ^ t2[a>>8 & 255] ^ t3[b & 255] ^ key[kIndex + 2]; d = t0[d>>>24] ^ t1[a>>16 & 255] ^ t2[b>>8 & 255] ^ t3[c & 255] ^ key[kIndex + 3]; kIndex += 4; a=a2; b=b2; c=c2; } // Last round. for (i = 0; i < 4; i++) { out[dir ? 3&-i : i] = sbox[a>>>24 ]<<24 ^ sbox[b>>16 & 255]<<16 ^ sbox[c>>8 & 255]<<8 ^ sbox[d & 255] ^ key[kIndex++]; a2=a; a=b; b=c; c=d; d=a2; } return out; } }; /** @fileOverview Arrays of bits, encoded as arrays of Numbers. * * @author Emily Stark * @author Mike Hamburg * @author Dan Boneh */ /** * Arrays of bits, encoded as arrays of Numbers. * @namespace * @description *

* These objects are the currency accepted by SJCL's crypto functions. *

* *

* Most of our crypto primitives operate on arrays of 4-byte words internally, * but many of them can take arguments that are not a multiple of 4 bytes. * This library encodes arrays of bits (whose size need not be a multiple of 8 * bits) as arrays of 32-bit words. The bits are packed, big-endian, into an * array of words, 32 bits at a time. Since the words are double-precision * floating point numbers, they fit some extra data. We use this (in a private, * possibly-changing manner) to encode the number of bits actually present * in the last word of the array. *

* *

* Because bitwise ops clear this out-of-band data, these arrays can be passed * to ciphers like AES which want arrays of words. *

*/ sjcl.bitArray = { /** * Array slices in units of bits. * @param {bitArray} a The array to slice. * @param {Number} bstart The offset to the start of the slice, in bits. * @param {Number} bend The offset to the end of the slice, in bits. If this is undefined, * slice until the end of the array. * @return {bitArray} The requested slice. */ bitSlice: function (a, bstart, bend) { a = sjcl.bitArray._shiftRight(a.slice(bstart/32), 32 - (bstart & 31)).slice(1); return (bend === undefined) ? a : sjcl.bitArray.clamp(a, bend-bstart); }, /** * Extract a number packed into a bit array. * @param {bitArray} a The array to slice. * @param {Number} bstart The offset to the start of the slice, in bits. * @param {Number} blength The length of the number to extract. * @return {Number} The requested slice. */ extract: function(a, bstart, blength) { // FIXME: this Math.floor is not necessary at all, but for some reason // seems to suppress a bug in the Chromium JIT. var x, sh = Math.floor((-bstart-blength) & 31); if ((bstart + blength - 1 ^ bstart) & -32) { // it crosses a boundary x = (a[bstart/32|0] << (32 - sh)) ^ (a[bstart/32+1|0] >>> sh); } else { // within a single word x = a[bstart/32|0] >>> sh; } return x & ((1< 0 && len) { a[l-1] = sjcl.bitArray.partial(len, a[l-1] & 0x80000000 >> (len-1), 1); } return a; }, /** * Make a partial word for a bit array. * @param {Number} len The number of bits in the word. * @param {Number} x The bits. * @param {Number} [_end=0] Pass 1 if x has already been shifted to the high side. * @return {Number} The partial word. */ partial: function (len, x, _end) { if (len === 32) { return x; } return (_end ? x|0 : x << (32-len)) + len * 0x10000000000; }, /** * Get the number of bits used by a partial word. * @param {Number} x The partial word. * @return {Number} The number of bits used by the partial word. */ getPartial: function (x) { return Math.round(x/0x10000000000) || 32; }, /** * Compare two arrays for equality in a predictable amount of time. * @param {bitArray} a The first array. * @param {bitArray} b The second array. * @return {boolean} true if a == b; false otherwise. */ equal: function (a, b) { if (sjcl.bitArray.bitLength(a) !== sjcl.bitArray.bitLength(b)) { return false; } var x = 0, i; for (i=0; i= 32; shift -= 32) { out.push(carry); carry = 0; } if (shift === 0) { return out.concat(a); } for (i=0; i>>shift); carry = a[i] << (32-shift); } last2 = a.length ? a[a.length-1] : 0; shift2 = sjcl.bitArray.getPartial(last2); out.push(sjcl.bitArray.partial(shift+shift2 & 31, (shift + shift2 > 32) ? carry : out.pop(),1)); return out; }, /** xor a block of 4 words together. * @private */ _xor4: function(x,y) { return [x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]]; }, /** byteswap a word array inplace. * (does not handle partial words) * @param {sjcl.bitArray} a word array * @return {sjcl.bitArray} byteswapped array */ byteswapM: function(a) { var i, v, m = 0xff00; for (i = 0; i < a.length; ++i) { v = a[i]; a[i] = (v >>> 24) | ((v >>> 8) & m) | ((v & m) << 8) | (v << 24); } return a; } }; /** @fileOverview Bit array codec implementations. * * @author Emily Stark * @author Mike Hamburg * @author Dan Boneh */ /** * UTF-8 strings * @namespace */ sjcl.codec.utf8String = { /** Convert from a bitArray to a UTF-8 string. */ fromBits: function (arr) { var out = "", bl = sjcl.bitArray.bitLength(arr), i, tmp; for (i=0; i>> 8 >>> 8 >>> 8); tmp <<= 8; } return decodeURIComponent(escape(out)); }, /** Convert from a UTF-8 string to a bitArray. */ toBits: function (str) { str = unescape(encodeURIComponent(str)); var out = [], i, tmp=0; for (i=0; i>>bits) >>> REMAINING); if (bits < BASE) { ta = arr[i] << (BASE-bits); bits += REMAINING; i++; } else { ta <<= BASE; bits -= BASE; } } while ((out.length & 7) && !_noEquals) { out += "="; } return out; }, /** Convert from a base32 string to a bitArray */ toBits: function(str, _hex) { str = str.replace(/\s|=/g,'').toUpperCase(); var BITS = sjcl.codec.base32.BITS, BASE = sjcl.codec.base32.BASE, REMAINING = sjcl.codec.base32.REMAINING; var out = [], i, bits=0, c = sjcl.codec.base32._chars, ta=0, x, format="base32"; if (_hex) { c = sjcl.codec.base32._hexChars; format = "base32hex"; } for (i=0; i REMAINING) { bits -= REMAINING; out.push(ta ^ x>>>bits); ta = x << (BITS-bits); } else { bits += BASE; ta ^= x << (BITS-bits); } } if (bits&56) { out.push(sjcl.bitArray.partial(bits&56, ta, 1)); } return out; } }; sjcl.codec.base32hex = { fromBits: function (arr, _noEquals) { return sjcl.codec.base32.fromBits(arr,_noEquals,1); }, toBits: function (str) { return sjcl.codec.base32.toBits(str,1); } }; /** @fileOverview Bit array codec implementations. * * @author Emily Stark * @author Mike Hamburg * @author Dan Boneh */ /** * Base64 encoding/decoding * @namespace */ sjcl.codec.base64 = { /** The base64 alphabet. * @private */ _chars: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", /** Convert from a bitArray to a base64 string. */ fromBits: function (arr, _noEquals, _url) { var out = "", i, bits=0, c = sjcl.codec.base64._chars, ta=0, bl = sjcl.bitArray.bitLength(arr); if (_url) { c = c.substr(0,62) + '-_'; } for (i=0; out.length * 6 < bl; ) { out += c.charAt((ta ^ arr[i]>>>bits) >>> 26); if (bits < 6) { ta = arr[i] << (6-bits); bits += 26; i++; } else { ta <<= 6; bits -= 6; } } while ((out.length & 3) && !_noEquals) { out += "="; } return out; }, /** Convert from a base64 string to a bitArray */ toBits: function(str, _url) { str = str.replace(/\s|=/g,''); var out = [], i, bits=0, c = sjcl.codec.base64._chars, ta=0, x; if (_url) { c = c.substr(0,62) + '-_'; } for (i=0; i 26) { bits -= 26; out.push(ta ^ x>>>bits); ta = x << (32-bits); } else { bits += 6; ta ^= x << (32-bits); } } if (bits&56) { out.push(sjcl.bitArray.partial(bits&56, ta, 1)); } return out; } }; sjcl.codec.base64url = { fromBits: function (arr) { return sjcl.codec.base64.fromBits(arr,1,1); }, toBits: function (str) { return sjcl.codec.base64.toBits(str,1); } }; /** @fileOverview Bit array codec implementations. * * @author Emily Stark * @author Mike Hamburg * @author Dan Boneh */ /** * Arrays of bytes * @namespace */ sjcl.codec.bytes = { /** Convert from a bitArray to an array of bytes. */ fromBits: function (arr) { var out = [], bl = sjcl.bitArray.bitLength(arr), i, tmp; for (i=0; i>> 24); tmp <<= 8; } return out; }, /** Convert from an array of bytes to a bitArray. */ toBits: function (bytes) { var out = [], i, tmp=0; for (i=0; i 9007199254740991){ throw new sjcl.exception.invalid("Cannot hash more than 2^53 - 1 bits"); } if (typeof Uint32Array !== 'undefined') { var c = new Uint32Array(b); var j = 0; for (i = 512+ol - ((512+ol) & 511); i <= nl; i+= 512) { this._block(c.subarray(16 * j, 16 * (j+1))); j += 1; } b.splice(0, 16 * j); } else { for (i = 512+ol - ((512+ol) & 511); i <= nl; i+= 512) { this._block(b.splice(0,16)); } } return this; }, /** * Complete hashing and output the hash value. * @return {bitArray} The hash value, an array of 8 big-endian words. */ finalize:function () { var i, b = this._buffer, h = this._h; // Round out and push the buffer b = sjcl.bitArray.concat(b, [sjcl.bitArray.partial(1,1)]); // Round out the buffer to a multiple of 16 words, less the 2 length words. for (i = b.length + 2; i & 15; i++) { b.push(0); } // append the length b.push(Math.floor(this._length / 0x100000000)); b.push(this._length | 0); while (b.length) { this._block(b.splice(0,16)); } this.reset(); return h; }, /** * The SHA-256 initialization vector, to be precomputed. * @private */ _init:[], /* _init:[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19], */ /** * The SHA-256 hash key, to be precomputed. * @private */ _key:[], /* _key: [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2], */ /** * Function to precompute _init and _key. * @private */ _precompute: function () { var i = 0, prime = 2, factor, isPrime; function frac(x) { return (x-Math.floor(x)) * 0x100000000 | 0; } for (; i<64; prime++) { isPrime = true; for (factor=2; factor*factor <= prime; factor++) { if (prime % factor === 0) { isPrime = false; break; } } if (isPrime) { if (i<8) { this._init[i] = frac(Math.pow(prime, 1/2)); } this._key[i] = frac(Math.pow(prime, 1/3)); i++; } } }, /** * Perform one cycle of SHA-256. * @param {Uint32Array|bitArray} w one block of words. * @private */ _block:function (w) { var i, tmp, a, b, h = this._h, k = this._key, h0 = h[0], h1 = h[1], h2 = h[2], h3 = h[3], h4 = h[4], h5 = h[5], h6 = h[6], h7 = h[7]; /* Rationale for placement of |0 : * If a value can overflow is original 32 bits by a factor of more than a few * million (2^23 ish), there is a possibility that it might overflow the * 53-bit mantissa and lose precision. * * To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that * propagates around the loop, and on the hash state h[]. I don't believe * that the clamps on h4 and on h0 are strictly necessary, but it's close * (for h4 anyway), and better safe than sorry. * * The clamps on h[] are necessary for the output to be correct even in the * common case and for short inputs. */ for (i=0; i<64; i++) { // load up the input word for this round if (i<16) { tmp = w[i]; } else { a = w[(i+1 ) & 15]; b = w[(i+14) & 15]; tmp = w[i&15] = ((a>>>7 ^ a>>>18 ^ a>>>3 ^ a<<25 ^ a<<14) + (b>>>17 ^ b>>>19 ^ b>>>10 ^ b<<15 ^ b<<13) + w[i&15] + w[(i+9) & 15]) | 0; } tmp = (tmp + h7 + (h4>>>6 ^ h4>>>11 ^ h4>>>25 ^ h4<<26 ^ h4<<21 ^ h4<<7) + (h6 ^ h4&(h5^h6)) + k[i]); // | 0; // shift register h7 = h6; h6 = h5; h5 = h4; h4 = h3 + tmp | 0; h3 = h2; h2 = h1; h1 = h0; h0 = (tmp + ((h1&h2) ^ (h3&(h1^h2))) + (h1>>>2 ^ h1>>>13 ^ h1>>>22 ^ h1<<30 ^ h1<<19 ^ h1<<10)) | 0; } h[0] = h[0]+h0 | 0; h[1] = h[1]+h1 | 0; h[2] = h[2]+h2 | 0; h[3] = h[3]+h3 | 0; h[4] = h[4]+h4 | 0; h[5] = h[5]+h5 | 0; h[6] = h[6]+h6 | 0; h[7] = h[7]+h7 | 0; } }; /** @fileOverview CCM mode implementation. * * Special thanks to Roy Nicholson for pointing out a bug in our * implementation. * * @author Emily Stark * @author Mike Hamburg * @author Dan Boneh */ /** * CTR mode with CBC MAC. * @namespace */ sjcl.mode.ccm = { /** The name of the mode. * @constant */ name: "ccm", _progressListeners: [], listenProgress: function (cb) { sjcl.mode.ccm._progressListeners.push(cb); }, unListenProgress: function (cb) { var index = sjcl.mode.ccm._progressListeners.indexOf(cb); if (index > -1) { sjcl.mode.ccm._progressListeners.splice(index, 1); } }, _callProgressListener: function (val) { var p = sjcl.mode.ccm._progressListeners.slice(), i; for (i = 0; i < p.length; i += 1) { p[i](val); } }, /** Encrypt in CCM mode. * @static * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes. * @param {bitArray} plaintext The plaintext data. * @param {bitArray} iv The initialization value. * @param {bitArray} [adata=[]] The authenticated data. * @param {Number} [tlen=64] the desired tag length, in bits. * @return {bitArray} The encrypted data, an array of bytes. */ encrypt: function(prf, plaintext, iv, adata, tlen) { var L, out = plaintext.slice(0), tag, w=sjcl.bitArray, ivl = w.bitLength(iv) / 8, ol = w.bitLength(out) / 8; tlen = tlen || 64; adata = adata || []; if (ivl < 7) { throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes"); } // compute the length of the length for (L=2; L<4 && ol >>> 8*L; L++) {} if (L < 15 - ivl) { L = 15-ivl; } iv = w.clamp(iv,8*(15-L)); // compute the tag tag = sjcl.mode.ccm._computeTag(prf, plaintext, iv, adata, tlen, L); // encrypt out = sjcl.mode.ccm._ctrMode(prf, out, iv, tag, tlen, L); return w.concat(out.data, out.tag); }, /** Decrypt in CCM mode. * @static * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes. * @param {bitArray} ciphertext The ciphertext data. * @param {bitArray} iv The initialization value. * @param {bitArray} [adata=[]] adata The authenticated data. * @param {Number} [tlen=64] tlen the desired tag length, in bits. * @return {bitArray} The decrypted data. */ decrypt: function(prf, ciphertext, iv, adata, tlen) { tlen = tlen || 64; adata = adata || []; var L, w=sjcl.bitArray, ivl = w.bitLength(iv) / 8, ol = w.bitLength(ciphertext), out = w.clamp(ciphertext, ol - tlen), tag = w.bitSlice(ciphertext, ol - tlen), tag2; ol = (ol - tlen) / 8; if (ivl < 7) { throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes"); } // compute the length of the length for (L=2; L<4 && ol >>> 8*L; L++) {} if (L < 15 - ivl) { L = 15-ivl; } iv = w.clamp(iv,8*(15-L)); // decrypt out = sjcl.mode.ccm._ctrMode(prf, out, iv, tag, tlen, L); // check the tag tag2 = sjcl.mode.ccm._computeTag(prf, out.data, iv, adata, tlen, L); if (!w.equal(out.tag, tag2)) { throw new sjcl.exception.corrupt("ccm: tag doesn't match"); } return out.data; }, _macAdditionalData: function (prf, adata, iv, tlen, ol, L) { var mac, tmp, i, macData = [], w=sjcl.bitArray, xor = w._xor4; // mac the flags mac = [w.partial(8, (adata.length ? 1<<6 : 0) | (tlen-2) << 2 | L-1)]; // mac the iv and length mac = w.concat(mac, iv); mac[3] |= ol; mac = prf.encrypt(mac); if (adata.length) { // mac the associated data. start with its length... tmp = w.bitLength(adata)/8; if (tmp <= 0xFEFF) { macData = [w.partial(16, tmp)]; } else if (tmp <= 0xFFFFFFFF) { macData = w.concat([w.partial(16,0xFFFE)], [tmp]); } // else ... // mac the data itself macData = w.concat(macData, adata); for (i=0; i 16) { throw new sjcl.exception.invalid("ccm: invalid tag length"); } if (adata.length > 0xFFFFFFFF || plaintext.length > 0xFFFFFFFF) { // I don't want to deal with extracting high words from doubles. throw new sjcl.exception.bug("ccm: can't deal with 4GiB or more data"); } mac = sjcl.mode.ccm._macAdditionalData(prf, adata, iv, tlen, w.bitLength(plaintext)/8, L); // mac the plaintext for (i=0; i n) { sjcl.mode.ccm._callProgressListener(i/l); n += p; } ctr[3]++; enc = prf.encrypt(ctr); data[i] ^= enc[0]; data[i+1] ^= enc[1]; data[i+2] ^= enc[2]; data[i+3] ^= enc[3]; } return { tag:tag, data:w.clamp(data,bl) }; } }; /** @fileOverview OCB 2.0 implementation * * @author Emily Stark * @author Mike Hamburg * @author Dan Boneh */ /** * Phil Rogaway's Offset CodeBook mode, version 2.0. * May be covered by US and international patents. * * @namespace * @author Emily Stark * @author Mike Hamburg * @author Dan Boneh */ sjcl.mode.ocb2 = { /** The name of the mode. * @constant */ name: "ocb2", /** Encrypt in OCB mode, version 2.0. * @param {Object} prp The block cipher. It must have a block size of 16 bytes. * @param {bitArray} plaintext The plaintext data. * @param {bitArray} iv The initialization value. * @param {bitArray} [adata=[]] The authenticated data. * @param {Number} [tlen=64] the desired tag length, in bits. * @param {boolean} [premac=false] true if the authentication data is pre-macced with PMAC. * @return The encrypted data, an array of bytes. * @throws {sjcl.exception.invalid} if the IV isn't exactly 128 bits. */ encrypt: function(prp, plaintext, iv, adata, tlen, premac) { if (sjcl.bitArray.bitLength(iv) !== 128) { throw new sjcl.exception.invalid("ocb iv must be 128 bits"); } var i, times2 = sjcl.mode.ocb2._times2, w = sjcl.bitArray, xor = w._xor4, checksum = [0,0,0,0], delta = times2(prp.encrypt(iv)), bi, bl, output = [], pad; adata = adata || []; tlen = tlen || 64; for (i=0; i+4 < plaintext.length; i+=4) { /* Encrypt a non-final block */ bi = plaintext.slice(i,i+4); checksum = xor(checksum, bi); output = output.concat(xor(delta,prp.encrypt(xor(delta, bi)))); delta = times2(delta); } /* Chop out the final block */ bi = plaintext.slice(i); bl = w.bitLength(bi); pad = prp.encrypt(xor(delta,[0,0,0,bl])); bi = w.clamp(xor(bi.concat([0,0,0]),pad), bl); /* Checksum the final block, and finalize the checksum */ checksum = xor(checksum,xor(bi.concat([0,0,0]),pad)); checksum = prp.encrypt(xor(checksum,xor(delta,times2(delta)))); /* MAC the header */ if (adata.length) { checksum = xor(checksum, premac ? adata : sjcl.mode.ocb2.pmac(prp, adata)); } return output.concat(w.concat(bi, w.clamp(checksum, tlen))); }, /** Decrypt in OCB mode. * @param {Object} prp The block cipher. It must have a block size of 16 bytes. * @param {bitArray} ciphertext The ciphertext data. * @param {bitArray} iv The initialization value. * @param {bitArray} [adata=[]] The authenticated data. * @param {Number} [tlen=64] the desired tag length, in bits. * @param {boolean} [premac=false] true if the authentication data is pre-macced with PMAC. * @return The decrypted data, an array of bytes. * @throws {sjcl.exception.invalid} if the IV isn't exactly 128 bits. * @throws {sjcl.exception.corrupt} if if the message is corrupt. */ decrypt: function(prp, ciphertext, iv, adata, tlen, premac) { if (sjcl.bitArray.bitLength(iv) !== 128) { throw new sjcl.exception.invalid("ocb iv must be 128 bits"); } tlen = tlen || 64; var i, times2 = sjcl.mode.ocb2._times2, w = sjcl.bitArray, xor = w._xor4, checksum = [0,0,0,0], delta = times2(prp.encrypt(iv)), bi, bl, len = sjcl.bitArray.bitLength(ciphertext) - tlen, output = [], pad; adata = adata || []; for (i=0; i+4 < len/32; i+=4) { /* Decrypt a non-final block */ bi = xor(delta, prp.decrypt(xor(delta, ciphertext.slice(i,i+4)))); checksum = xor(checksum, bi); output = output.concat(bi); delta = times2(delta); } /* Chop out and decrypt the final block */ bl = len-i*32; pad = prp.encrypt(xor(delta,[0,0,0,bl])); bi = xor(pad, w.clamp(ciphertext.slice(i),bl).concat([0,0,0])); /* Checksum the final block, and finalize the checksum */ checksum = xor(checksum, bi); checksum = prp.encrypt(xor(checksum, xor(delta, times2(delta)))); /* MAC the header */ if (adata.length) { checksum = xor(checksum, premac ? adata : sjcl.mode.ocb2.pmac(prp, adata)); } if (!w.equal(w.clamp(checksum, tlen), w.bitSlice(ciphertext, len))) { throw new sjcl.exception.corrupt("ocb: tag doesn't match"); } return output.concat(w.clamp(bi,bl)); }, /** PMAC authentication for OCB associated data. * @param {Object} prp The block cipher. It must have a block size of 16 bytes. * @param {bitArray} adata The authenticated data. */ pmac: function(prp, adata) { var i, times2 = sjcl.mode.ocb2._times2, w = sjcl.bitArray, xor = w._xor4, checksum = [0,0,0,0], delta = prp.encrypt([0,0,0,0]), bi; delta = xor(delta,times2(times2(delta))); for (i=0; i+4>>31, x[1]<<1 ^ x[2]>>>31, x[2]<<1 ^ x[3]>>>31, x[3]<<1 ^ (x[0]>>>31)*0x87]; } }; /** @fileOverview GCM mode implementation. * * @author Juho Vähä-Herttua */ /** * Galois/Counter mode. * @namespace */ sjcl.mode.gcm = { /** * The name of the mode. * @constant */ name: "gcm", /** Encrypt in GCM mode. * @static * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes. * @param {bitArray} plaintext The plaintext data. * @param {bitArray} iv The initialization value. * @param {bitArray} [adata=[]] The authenticated data. * @param {Number} [tlen=128] The desired tag length, in bits. * @return {bitArray} The encrypted data, an array of bytes. */ encrypt: function (prf, plaintext, iv, adata, tlen) { var out, data = plaintext.slice(0), w=sjcl.bitArray; tlen = tlen || 128; adata = adata || []; // encrypt and tag out = sjcl.mode.gcm._ctrMode(true, prf, data, adata, iv, tlen); return w.concat(out.data, out.tag); }, /** Decrypt in GCM mode. * @static * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes. * @param {bitArray} ciphertext The ciphertext data. * @param {bitArray} iv The initialization value. * @param {bitArray} [adata=[]] The authenticated data. * @param {Number} [tlen=128] The desired tag length, in bits. * @return {bitArray} The decrypted data. */ decrypt: function (prf, ciphertext, iv, adata, tlen) { var out, data = ciphertext.slice(0), tag, w=sjcl.bitArray, l=w.bitLength(data); tlen = tlen || 128; adata = adata || []; // Slice tag out of data if (tlen <= l) { tag = w.bitSlice(data, l-tlen); data = w.bitSlice(data, 0, l-tlen); } else { tag = data; data = []; } // decrypt and tag out = sjcl.mode.gcm._ctrMode(false, prf, data, adata, iv, tlen); if (!w.equal(out.tag, tag)) { throw new sjcl.exception.corrupt("gcm: tag doesn't match"); } return out.data; }, /* Compute the galois multiplication of X and Y * @private */ _galoisMultiply: function (x, y) { var i, j, xi, Zi, Vi, lsb_Vi, w=sjcl.bitArray, xor=w._xor4; Zi = [0,0,0,0]; Vi = y.slice(0); // Block size is 128 bits, run 128 times to get Z_128 for (i=0; i<128; i++) { xi = (x[Math.floor(i/32)] & (1 << (31-i%32))) !== 0; if (xi) { // Z_i+1 = Z_i ^ V_i Zi = xor(Zi, Vi); } // Store the value of LSB(V_i) lsb_Vi = (Vi[3] & 1) !== 0; // V_i+1 = V_i >> 1 for (j=3; j>0; j--) { Vi[j] = (Vi[j] >>> 1) | ((Vi[j-1]&1) << 31); } Vi[0] = Vi[0] >>> 1; // If LSB(V_i) is 1, V_i+1 = (V_i >> 1) ^ R if (lsb_Vi) { Vi[0] = Vi[0] ^ (0xe1 << 24); } } return Zi; }, _ghash: function(H, Y0, data) { var Yi, i, l = data.length; Yi = Y0.slice(0); for (i=0; i bs) { key = Hash.hash(key); } for (i=0; iUse sjcl.random as a singleton for this class! *

* This random number generator is a derivative of Ferguson and Schneier's * generator Fortuna. It collects entropy from various events into several * pools, implemented by streaming SHA-256 instances. It differs from * ordinary Fortuna in a few ways, though. *

* *

* Most importantly, it has an entropy estimator. This is present because * there is a strong conflict here between making the generator available * as soon as possible, and making sure that it doesn't "run on empty". * In Fortuna, there is a saved state file, and the system is likely to have * time to warm up. *

* *

* Second, because users are unlikely to stay on the page for very long, * and to speed startup time, the number of pools increases logarithmically: * a new pool is created when the previous one is actually used for a reseed. * This gives the same asymptotic guarantees as Fortuna, but gives more * entropy to early reseeds. *

* *

* The entire mechanism here feels pretty klunky. Furthermore, there are * several improvements that should be made, including support for * dedicated cryptographic functions that may be present in some browsers; * state files in local storage; cookies containing randomness; etc. So * look for improvements in future versions. *

* @constructor */ sjcl.prng = function(defaultParanoia) { /* private */ this._pools = [new sjcl.hash.sha256()]; this._poolEntropy = [0]; this._reseedCount = 0; this._robins = {}; this._eventId = 0; this._collectorIds = {}; this._collectorIdNext = 0; this._strength = 0; this._poolStrength = 0; this._nextReseed = 0; this._key = [0,0,0,0,0,0,0,0]; this._counter = [0,0,0,0]; this._cipher = undefined; this._defaultParanoia = defaultParanoia; /* event listener stuff */ this._collectorsStarted = false; this._callbacks = {progress: {}, seeded: {}}; this._callbackI = 0; /* constants */ this._NOT_READY = 0; this._READY = 1; this._REQUIRES_RESEED = 2; this._MAX_WORDS_PER_BURST = 65536; this._PARANOIA_LEVELS = [0,48,64,96,128,192,256,384,512,768,1024]; this._MILLISECONDS_PER_RESEED = 30000; this._BITS_PER_RESEED = 80; }; sjcl.prng.prototype = { /** Generate several random words, and return them in an array. * A word consists of 32 bits (4 bytes) * @param {Number} nwords The number of words to generate. */ randomWords: function (nwords, paranoia) { var out = [], i, readiness = this.isReady(paranoia), g; if (readiness === this._NOT_READY) { throw new sjcl.exception.notReady("generator isn't seeded"); } else if (readiness & this._REQUIRES_RESEED) { this._reseedFromPools(!(readiness & this._READY)); } for (i=0; i0) { estimatedEntropy++; tmp = tmp >>> 1; } } } this._pools[robin].update([id,this._eventId++,2,estimatedEntropy,t,data.length].concat(data)); } break; case "string": if (estimatedEntropy === undefined) { /* English text has just over 1 bit per character of entropy. * But this might be HTML or something, and have far less * entropy than English... Oh well, let's just say one bit. */ estimatedEntropy = data.length; } this._pools[robin].update([id,this._eventId++,3,estimatedEntropy,t,data.length]); this._pools[robin].update(data); break; default: err=1; } if (err) { throw new sjcl.exception.bug("random: addEntropy only supports number, array of numbers or string"); } /* record the new strength */ this._poolEntropy[robin] += estimatedEntropy; this._poolStrength += estimatedEntropy; /* fire off events */ if (oldReady === this._NOT_READY) { if (this.isReady() !== this._NOT_READY) { this._fireEvent("seeded", Math.max(this._strength, this._poolStrength)); } this._fireEvent("progress", this.getProgress()); } }, /** Is the generator ready? */ isReady: function (paranoia) { var entropyRequired = this._PARANOIA_LEVELS[ (paranoia !== undefined) ? paranoia : this._defaultParanoia ]; if (this._strength && this._strength >= entropyRequired) { return (this._poolEntropy[0] > this._BITS_PER_RESEED && (new Date()).valueOf() > this._nextReseed) ? this._REQUIRES_RESEED | this._READY : this._READY; } else { return (this._poolStrength >= entropyRequired) ? this._REQUIRES_RESEED | this._NOT_READY : this._NOT_READY; } }, /** Get the generator's progress toward readiness, as a fraction */ getProgress: function (paranoia) { var entropyRequired = this._PARANOIA_LEVELS[ paranoia ? paranoia : this._defaultParanoia ]; if (this._strength >= entropyRequired) { return 1.0; } else { return (this._poolStrength > entropyRequired) ? 1.0 : this._poolStrength / entropyRequired; } }, /** start the built-in entropy collectors */ startCollectors: function () { if (this._collectorsStarted) { return; } this._eventListener = { loadTimeCollector: this._bind(this._loadTimeCollector), mouseCollector: this._bind(this._mouseCollector), keyboardCollector: this._bind(this._keyboardCollector), accelerometerCollector: this._bind(this._accelerometerCollector), touchCollector: this._bind(this._touchCollector) }; if (window.addEventListener) { window.addEventListener("load", this._eventListener.loadTimeCollector, false); window.addEventListener("mousemove", this._eventListener.mouseCollector, false); window.addEventListener("keypress", this._eventListener.keyboardCollector, false); window.addEventListener("devicemotion", this._eventListener.accelerometerCollector, false); window.addEventListener("touchmove", this._eventListener.touchCollector, false); } else if (document.attachEvent) { document.attachEvent("onload", this._eventListener.loadTimeCollector); document.attachEvent("onmousemove", this._eventListener.mouseCollector); document.attachEvent("keypress", this._eventListener.keyboardCollector); } else { throw new sjcl.exception.bug("can't attach event"); } this._collectorsStarted = true; }, /** stop the built-in entropy collectors */ stopCollectors: function () { if (!this._collectorsStarted) { return; } if (window.removeEventListener) { window.removeEventListener("load", this._eventListener.loadTimeCollector, false); window.removeEventListener("mousemove", this._eventListener.mouseCollector, false); window.removeEventListener("keypress", this._eventListener.keyboardCollector, false); window.removeEventListener("devicemotion", this._eventListener.accelerometerCollector, false); window.removeEventListener("touchmove", this._eventListener.touchCollector, false); } else if (document.detachEvent) { document.detachEvent("onload", this._eventListener.loadTimeCollector); document.detachEvent("onmousemove", this._eventListener.mouseCollector); document.detachEvent("keypress", this._eventListener.keyboardCollector); } this._collectorsStarted = false; }, /* use a cookie to store entropy. useCookie: function (all_cookies) { throw new sjcl.exception.bug("random: useCookie is unimplemented"); },*/ /** add an event listener for progress or seeded-ness. */ addEventListener: function (name, callback) { this._callbacks[name][this._callbackI++] = callback; }, /** remove an event listener for progress or seeded-ness */ removeEventListener: function (name, cb) { var i, j, cbs=this._callbacks[name], jsTemp=[]; /* I'm not sure if this is necessary; in C++, iterating over a * collection and modifying it at the same time is a no-no. */ for (j in cbs) { if (cbs.hasOwnProperty(j) && cbs[j] === cb) { jsTemp.push(j); } } for (i=0; i= 1 << this._pools.length) { this._pools.push(new sjcl.hash.sha256()); this._poolEntropy.push(0); } /* how strong was this reseed? */ this._poolStrength -= strength; if (strength > this._strength) { this._strength = strength; } this._reseedCount ++; this._reseed(reseedData); }, _keyboardCollector: function () { this._addCurrentTimeToEntropy(1); }, _mouseCollector: function (ev) { var x, y; try { x = ev.x || ev.clientX || ev.offsetX || 0; y = ev.y || ev.clientY || ev.offsetY || 0; } catch (err) { // Event originated from a secure element. No mouse position available. x = 0; y = 0; } if (x != 0 && y!= 0) { this.addEntropy([x,y], 2, "mouse"); } this._addCurrentTimeToEntropy(0); }, _touchCollector: function(ev) { var touch = ev.touches[0] || ev.changedTouches[0]; var x = touch.pageX || touch.clientX, y = touch.pageY || touch.clientY; this.addEntropy([x,y],1,"touch"); this._addCurrentTimeToEntropy(0); }, _loadTimeCollector: function () { this._addCurrentTimeToEntropy(2); }, _addCurrentTimeToEntropy: function (estimatedEntropy) { if (typeof window !== 'undefined' && window.performance && typeof window.performance.now === "function") { //how much entropy do we want to add here? this.addEntropy(window.performance.now(), estimatedEntropy, "loadtime"); } else { this.addEntropy((new Date()).valueOf(), estimatedEntropy, "loadtime"); } }, _accelerometerCollector: function (ev) { var ac = ev.accelerationIncludingGravity.x||ev.accelerationIncludingGravity.y||ev.accelerationIncludingGravity.z; if(window.orientation){ var or = window.orientation; if (typeof or === "number") { this.addEntropy(or, 1, "accelerometer"); } } if (ac) { this.addEntropy(ac, 2, "accelerometer"); } this._addCurrentTimeToEntropy(0); }, _fireEvent: function (name, arg) { var j, cbs=sjcl.random._callbacks[name], cbsTemp=[]; /* TODO: there is a race condition between removing collectors and firing them */ /* I'm not sure if this is necessary; in C++, iterating over a * collection and modifying it at the same time is a no-no. */ for (j in cbs) { if (cbs.hasOwnProperty(j)) { cbsTemp.push(cbs[j]); } } for (j=0; j 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