From c932cd295d414f78c65167e7717b04f61e93122a Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Sun, 9 Oct 2016 01:54:34 +0200 Subject: Initial tests --- README.md | 27 ++++++ index.html | 19 +++++ sjcl-front.js | 264 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sjcl.js | 59 +++++++++++++ 4 files changed, 369 insertions(+) create mode 100644 README.md create mode 100644 index.html create mode 100644 sjcl-front.js create mode 100644 sjcl.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..a2eb441 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# Simple AES-256-CCM-PSK drop in front end script for the sjcl library + +This script is intended to be just included in any HTML document via a simple +and provide ad-hoc group crypto for that site. + +The script walks through all text nodes and checks for the sjcl-front signature sjcl:// +and tries to retrieve a key from localStorage or sessionStorage whose name matches the +one encoded as associated data in the encoded sjcl dictionary. If it can decrypt the +string, the text node's content is replaced by the plaintext and a class sjcl-decrypted +is added to its parent node. + +If the respective key can not be found, the class sjcl-undecrypted is added to the +parent node, which also receives a copy of the ciphertext, before the text node is +replaced by an informative message. A click handler for sjcl-undecrypted objects allows +to later re-decrypt the text by providing a key. + +During initialisation, the script also walks through all TEXTAREA nodes and injects a +hook revealing a contextual drop down menu that allows encrypting the TEXTAREA's content, +decrypting it and adding/deleting keys in localStorage. Future versions may allow adding +that hook to text input fields or filtering by class or id. + +Finally the script injects CSS to the document's head to get the injected nodes working. + +You can try the script at [https://erdgeist.org/webcrypto/]. + +The library that does the heavy lifting can be found here: https://github.com/bitwiseshiftleft/sjcl + diff --git a/index.html b/index.html new file mode 100644 index 0000000..edf8f38 --- /dev/null +++ b/index.html @@ -0,0 +1,19 @@ + + + + + title + + + + + + + +
Hint: passphrase for the key named Schlüssel => Insgeheim, Schlüssel2 => Nochgeheimer
+
sjcl://{"iv":"7EZ8MFvT0UQe7PPF8R1X8w==","v":1,"iter":1000,"ks":256,"ts":128,"mode":"ccm","adata":"U2NobMO8c3NlbA==","cipher":"aes","salt":"O7XZXQtj0U8=","ct":"NBKhmDDDJtiimILFoJm2CDY3lqnb"}
+
sjcl://{"iv":"MshCIxkW8VnzCw9DNKXrzw==","v":1,"iter":1000,"ks":256,"ts":128,"mode":"ccm","adata":"U2NobMO8c3NlbDI=","cipher":"aes","salt":"PfGQ9go1EUM=","ct":"nGA0OFDq5xSfLTHTE8QLqw+IjbIr2g=="}
+
sjcl://{"iv":"rj3F/Y3D6+h7aPUjYTdFfQ==","v":1,"iter":1000,"ks":256,"ts":128,"mode":"ccm","adata":"U2NobMO8c3NlbA==","cipher":"aes","salt":"O7XZXQtj0U8=","ct":"509+BfGlvASJqYGCVdae1kqdXm76+e4kxKfeOw=="}
+ + + diff --git a/sjcl-front.js b/sjcl-front.js new file mode 100644 index 0000000..1a67d64 --- /dev/null +++ b/sjcl-front.js @@ -0,0 +1,264 @@ +function inject_css() { + var s = document.createElement('style'); + s.setAttribute('type', 'text/css'); + s.appendChild(document.createTextNode("\ +@font-face { \n\ +font-family: 'fontello'; src: url('data:application/octet-stream;base64,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') format('woff'); } \n\ +.sjcl-undecrypted { background-color: rgba(255, 0, 0, 0.2); cursor: pointer; } \n\ +.sjcl-decrypted { background-color: rgba(0, 255, 0, 0.2); } \n\ +.sjcl-hook { display: inline-block; position: relative; vertical-align: top; } \n\ +.sjcl-hook:before, .sjcl-decrypted:before, .sjcl-undecrypted:before { content: '\\e801 '; font-family: 'fontello'; opacity: 0.5; font-size: 1.1em; padding: 0 0.3em 0 0.3em; margin-right: 0.2em; box-shadow: 0 0 0.3em currentColor; border-radius: 50%; } \n\ +.sjcl-dropdown, .sjcl-addkey-sub { display: none; border-radius: 0.4em; } \n\ +.sjcl-hook:hover .sjcl-dropdown { display: block; position: absolute; min-width: 300px; background-color: #f9f9f9; box-shadow: 0px 1em 2em 0 rgba(0,0,0,0.2); color: black; } \n\ +.sjcl-decrypt, .sjcl-usekey, .sjcl-addkey { padding: 2px 3px 2px 6px; cursor: pointer; border-radius: 0 0 0.4em 0.4em; } \n\ +.sjcl-decrypt:hover, .sjcl-usekey:hover, .sjcl-addkey:hover { border-radius: 0.4em; background-color: #f1f1f1; } \n\ +.sjcl-addkey:hover .sjcl-addkey-sub { display: block; } \n\ +.sjcl-addkey-sub label { display: inline-block !important; width: 40%; } \n\ +.sjcl-addkey-button, .sjcl-delkey-button { display: inline-block; text-align: center; margin-right: 6px; width: 40%; border-radius: 0.4em; border: solid 0.1em lime; } \n\ +.sjcl-delkey-button { border: solid 0.1em red; } \n\ +.sjcl-addkey-button:hover { background-color: lime; } \n\ +.sjcl-delkey-button:hover { background-color: red; } \ +")); + document.getElementsByTagName('head')[0].appendChild(s); +} + +function inject_textarea_hook() { + // Get rid of old hooks + var hooks = document.getElementsByClassName("sjcl-hook"); + for(var i = hooks.length - 1; i >= 0; i--) { + var n = hooks.item(i); + n.parentNode.removeChild(n); + } + // And insert new hooks + var walk=document.createTreeWalker(document.body,NodeFilter.SHOW_ELEMENT,null,false); + while(n = walk.nextNode()) + if (n.type == 'textarea') { + var keys = list_keys(); + var h = document.createElement('div'); + h.className='sjcl-hook'; + var dd = '
' + for (var i in keys) + dd += '
Encrypt with: "'+keys[i]+'"
'; + h.innerHTML = dd + '
Decrypt

Manage keys
add!
delete!
'; + n.parentNode.insertBefore(h,n.nextSibling); + } +} + +function decrypt_string(text, target_node, do_prompt) { + var rp, ct, plain, key_name, key, do_store = false; + + /* Clear sjcl classes from target node */ + target_node.className = target_node.className.replace(/(^| )sjcl-[a-z]+($| )/, ''); + + /* Check sjcl signature and bail out */ + if (text.substring(0,7) != 'sjcl://') + throw 'Text is not encrypted.'; + + /* Skip signature */ + text = text.substring(7); + /* Retrieve key name */ + try { + ct = JSON.parse(text); + } catch (e) { + target_node.className += ' sjcl-garbled'; + throw 'Cipher text is garbled.'; + } + try { + key_name = sjcl.codec.utf8String.fromBits(sjcl.codec.base64.toBits(ct.adata)); + } catch(e) { + target_node.className += ' sjcl-garbled'; + throw 'Can not extract key name'; + } + + key = retrieve_key(key_name); + if (!key && do_prompt) { + key = prompt( 'Enter password for the key ' + key_name); + do_store = true; + } + if (!key) { + target_node.className += ' sjcl-undecrypted'; + throw 'You need the key "' + key_name + '" to decrypt.'; + } + try { + plain = sjcl.decrypt(key, text, {}, rp); + } catch (e) { + if (do_store) + alert('Could not decrypt'); + target_node.className += ' sjcl-undecrytable'; + throw 'Your key "' + key_name + '" can not decrypt.'; + } + if (do_store) + store_key(key_name, key); + + target_node.className += ' sjcl-decrypted'; + return plain.replace(/\s+$/gm,''); +} + +function decrypt_element(n) { + try { + n.data = decrypt_string(n.data, n.parentElement, false); + } catch(e) { + n.parentElement.ciphertext = n.data; + n.parentElement.ciphertext_node = n; + n.data = e; + } +} + +function decrypt_undecrypted(n) { + if(!n.ciphertext || n.ciphertext_node==null) + return; + try { + n.ciphertext_node.data = decrypt_string(n.ciphertext, n, true); + inject_textarea_hook(); + find_undecrypted_nodes(); + } catch(e) { + n.ciphertext_node.data = e; + } +} + +function decrypt_textarea(n) { + try { + n.value = decrypt_string(n.value, n, false); + } catch(e) { + alert(e); + } +} + +function encrypt_textarea(n,name,key) { + var t = n.value + (" ".repeat(17 + Math.floor((Math.random() * 64)))); + var p = {adata:name, iter:1000, mode:'ccm', ts:128, ks:256, iter: 1000 }; + n.value = 'sjcl://'+sjcl.encrypt(key, t || '', p); + n.className = n.className.replace(/(^| )sjcl-[a-z]+($| )/, '')+' sjcl-undecrypted'; +} + +/* Auto node operation */ +function find_encrypted_nodes() { + var n, walk = document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT,null,false); + while(n = walk.nextNode()) + if (n.data.substring(0,7) == 'sjcl://') + decrypt_element(n, false); +} + +function find_undecrypted_nodes() { + var undec = document.getElementsByClassName("sjcl-undecrypted"); + for(var i = 0; i < undec.length; i++) + decrypt_undecrypted(undec.item(i), false); +} + +/* Passphrase accessors */ +function manage_key(n, do_add) { + var name = n.parentNode.getElementsByClassName('sjcl-addkey-name')[0]; + var key = n.parentNode.getElementsByClassName('sjcl-addkey-key')[0]; + if (!name.value) + alert( 'Missing key name.'); + else + store_key(name.value, do_add ? key.value : ''); + inject_textarea_hook(); +} + +/* Try to use the most logliving method to store key array */ +function store_key(name, key) { + /* Collect keys from all storage methods */ + var obj = {}; + try { + obj = JSON.parse(localStorage['frab-sjcl']); + } catch(e) {} + try { + var sso = JSON.parse(sessionStorage['frab-sjcl']); + for (var k in sso) { obj[k] = sso[k]; } + delete sessionStorage['frab-sjcl']; + } catch(e) {} + try { + wo = JSON.parse(window['frab-sjcl']); + for (var k in wo) { obj[k] = wo[k]; } + delete window['frab-sjcl']; + } catch(e) {} + + if (key) + obj[name] = key; + else + delete obj[name]; + var out = JSON.stringify(obj); + + /* Try to store in local/sessionStorage and fall back to + the window object, so at least we can decrypt all elements + locally */ + try { + localStorage['frab-sjcl'] = out; + if (localStorage['frab-sjcl'] != out) + throw 0; + return; + } catch(e) {} + try { + sessionStorage['frab-sjcl'] = out; + if (sessionStorage['frab-sjcl'] != out) + throw 0; + return; + } catch(e) {} + + window['frab-sjcl'] = out; +} + +function retrieve_key(name) { + try { + var obj = JSON.parse(localStorage['frab-sjcl'] || '{}'); + if (obj && obj[name]) + return obj[name]; + } catch(e) {} + try { + var obj = JSON.parse(sessionStorage['frab-sjcl'] || '{}'); + if (obj && obj[name]) + return obj[name]; + } catch(e) {} + try { + var obj = JSON.parse(window['frab-sjcl'] || '{}'); + if (obj && obj[name]) + return obj[name]; + } catch(e) {} + return ''; +} + +function list_keys() { + var key_list = []; + try { + key_list = (Object.keys(JSON.parse(localStorage['frab-sjcl']))); + } catch(e) {} + try { + key_list = key_list.concat(Object.keys(JSON.parse(sessionStorage['frab-sjcl']))); + } catch(e) {} + try { + key_list = key_list.concat(Object.keys(JSON.parse(window['frab-sjcl']))); + } catch(e) {} + return key_list; +} + +/* Event handler plumbing */ +function click_body(e) { + var n = e.target; + + if (n.className=='sjcl-usekey') { + var name = n.getAttribute('keyname'); + encrypt_textarea(n.parentNode.parentNode.previousSibling, name, retrieve_key(name)); + } + if (n.className=='sjcl-decrypt') + decrypt_textarea(n.parentNode.parentNode.previousSibling); + if ((' '+n.className+' ').indexOf(' sjcl-undecrypted ') > -1) + decrypt_undecrypted(n); + if (n.className=='sjcl-addkey-button') + manage_key(n,true); + if (n.className=='sjcl-delkey-button') + manage_key(n,false); +} + +function loaded() { + sjcl.random.startCollectors(); + document.body.addEventListener("click", click_body) + + inject_css(); + inject_textarea_hook(); + + /* Try to decrypt all encrypted nodes */ + find_encrypted_nodes(); +} + +window.addEventListener('load', loaded); diff --git a/sjcl.js b/sjcl.js new file mode 100644 index 0000000..6f7668e --- /dev/null +++ b/sjcl.js @@ -0,0 +1,59 @@ +"use strict";var sjcl={cipher:{},hash:{},keyexchange:{},mode:{},misc:{},codec:{},exception:{corrupt:function(a){this.toString=function(){return"CORRUPT: "+this.message};this.message=a},invalid:function(a){this.toString=function(){return"INVALID: "+this.message};this.message=a},bug:function(a){this.toString=function(){return"BUG: "+this.message};this.message=a},notReady:function(a){this.toString=function(){return"NOT READY: "+this.message};this.message=a}}}; +"undefined"!==typeof module&&module.exports&&(module.exports=sjcl);"function"===typeof define&&define([],function(){return sjcl}); +sjcl.cipher.aes=function(a){this.s[0][0][0]||this.O();var b,c,d,e,g=this.s[0][4],f=this.s[1];b=a.length;var h=1;if(4!==b&&6!==b&&8!==b)throw new sjcl.exception.invalid("invalid aes key size");this.b=[d=a.slice(0),e=[]];for(a=b;a<4*b+28;a++){c=d[a-1];if(0===a%b||8===b&&4===a%b)c=g[c>>>24]<<24^g[c>>16&255]<<16^g[c>>8&255]<<8^g[c&255],0===a%b&&(c=c<<8^c>>>24^h<<24,h=h<<1^283*(h>>7));d[a]=d[a-b]^c}for(b=0;a;b++,a--)c=d[b&3?a:a-4],e[b]=4>=a||4>b?c:f[0][g[c>>>24]]^f[1][g[c>>16&255]]^f[2][g[c>>8&255]]^f[3][g[c& +255]]}; +sjcl.cipher.aes.prototype={encrypt:function(a){return u(this,a,0)},decrypt:function(a){return u(this,a,1)},s:[[[],[],[],[],[]],[[],[],[],[],[]]],O:function(){var a=this.s[0],b=this.s[1],c=a[4],d=b[4],e,g,f,h=[],l=[],k,m,n,p;for(e=0;0x100>e;e++)l[(h[e]=e<<1^283*(e>>7))^e]=e;for(g=f=0;!c[g];g^=k||1,f=l[f]||1)for(n=f^f<<1^f<<2^f<<3^f<<4,n=n>>8^n&255^99,c[g]=n,d[n]=g,m=h[e=h[k=h[g]]],p=0x1010101*m^0x10001*e^0x101*k^0x1010100*g,m=0x101*h[n]^0x1010100*n,e=0;4>e;e++)a[e][g]=m=m<<24^m>>>8,b[e][n]=p=p<<24^p>>>8;for(e= +0;5>e;e++)a[e]=a[e].slice(0),b[e]=b[e].slice(0)}}; +function u(a,b,c){if(4!==b.length)throw new sjcl.exception.invalid("invalid aes block size");var d=a.b[c],e=b[0]^d[0],g=b[c?3:1]^d[1],f=b[2]^d[2];b=b[c?1:3]^d[3];var h,l,k,m=d.length/4-2,n,p=4,r=[0,0,0,0];h=a.s[c];a=h[0];var q=h[1],t=h[2],w=h[3],x=h[4];for(n=0;n>>24]^q[g>>16&255]^t[f>>8&255]^w[b&255]^d[p],l=a[g>>>24]^q[f>>16&255]^t[b>>8&255]^w[e&255]^d[p+1],k=a[f>>>24]^q[b>>16&255]^t[e>>8&255]^w[g&255]^d[p+2],b=a[b>>>24]^q[e>>16&255]^t[g>>8&255]^w[f&255]^d[p+3],p+=4,e=h,g=l,f=k;for(n= +0;4>n;n++)r[c?3&-n:n]=x[e>>>24]<<24^x[g>>16&255]<<16^x[f>>8&255]<<8^x[b&255]^d[p++],h=e,e=g,g=f,f=b,b=h;return r} +sjcl.bitArray={bitSlice:function(a,b,c){a=sjcl.bitArray.$(a.slice(b/32),32-(b&31)).slice(1);return void 0===c?a:sjcl.bitArray.clamp(a,c-b)},extract:function(a,b,c){var d=Math.floor(-b-c&31);return((b+c-1^b)&-32?a[b/32|0]<<32-d^a[b/32+1|0]>>>d:a[b/32|0]>>>d)&(1<>b-1,1));return a},partial:function(a,b,c){return 32===a?b:(c?b|0:b<<32-a)+0x10000000000*a},getPartial:function(a){return Math.round(a/0x10000000000)||32},equal:function(a,b){if(sjcl.bitArray.bitLength(a)!==sjcl.bitArray.bitLength(b))return!1;var c=0,d;for(d=0;d>>b),c=a[e]<<32-b;e=a.length?a[a.length-1]:0;a=sjcl.bitArray.getPartial(e);d.push(sjcl.bitArray.partial(b+a&31,32>>24|c>>>8&0xff00|(c&0xff00)<<8|c<<24;return a}}; +sjcl.codec.utf8String={fromBits:function(a){var b="",c=sjcl.bitArray.bitLength(a),d,e;for(d=0;d>>24),e<<=8;return decodeURIComponent(escape(b))},toBits:function(a){a=unescape(encodeURIComponent(a));var b=[],c,d=0;for(c=0;c>>f)>>>e),fm){if(!b)try{return sjcl.codec.base32hex.toBits(a)}catch(p){}throw new sjcl.exception.invalid("this isn't "+n+"!");}h>e?(h-=e,g.push(k^m>>>h),k=m<>>e)>>>26),6>e?(f=a[c]<<6-e,e+=26,c++):(f<<=6,e-=6);for(;d.length&3&&!b;)d+="=";return d},toBits:function(a,b){a=a.replace(/\s|=/g,"");var c=[],d,e=0,g=sjcl.codec.base64.B,f=0,h;b&&(g=g.substr(0,62)+"-_");for(d=0;dh)throw new sjcl.exception.invalid("this isn't base64!");26>>e),f=h<<32-e):(e+=6,f^=h<<32-e)}e&56&&c.push(sjcl.bitArray.partial(e&56,f,1));return c}};sjcl.codec.base64url={fromBits:function(a){return sjcl.codec.base64.fromBits(a,1,1)},toBits:function(a){return sjcl.codec.base64.toBits(a,1)}};sjcl.hash.sha256=function(a){this.b[0]||this.O();a?(this.F=a.F.slice(0),this.A=a.A.slice(0),this.l=a.l):this.reset()};sjcl.hash.sha256.hash=function(a){return(new sjcl.hash.sha256).update(a).finalize()}; +sjcl.hash.sha256.prototype={blockSize:512,reset:function(){this.F=this.Y.slice(0);this.A=[];this.l=0;return this},update:function(a){"string"===typeof a&&(a=sjcl.codec.utf8String.toBits(a));var b,c=this.A=sjcl.bitArray.concat(this.A,a);b=this.l;a=this.l=b+sjcl.bitArray.bitLength(a);for(b=512+b&-512;b<=a;b+=512)v(this,c.splice(0,16));return this},finalize:function(){var a,b=this.A,c=this.F,b=sjcl.bitArray.concat(b,[sjcl.bitArray.partial(1,1)]);for(a=b.length+2;a&15;a++)b.push(0);b.push(Math.floor(this.l/ +4294967296));for(b.push(this.l|0);b.length;)v(this,b.splice(0,16));this.reset();return c},Y:[],b:[],O:function(){function a(a){return 0x100000000*(a-Math.floor(a))|0}var b=0,c=2,d;a:for(;64>b;c++){for(d=2;d*d<=c;d++)if(0===c%d)continue a;8>b&&(this.Y[b]=a(Math.pow(c,.5)));this.b[b]=a(Math.pow(c,1/3));b++}}}; +function v(a,b){var c,d,e,g=b.slice(0),f=a.F,h=a.b,l=f[0],k=f[1],m=f[2],n=f[3],p=f[4],r=f[5],q=f[6],t=f[7];for(c=0;64>c;c++)16>c?d=g[c]:(d=g[c+1&15],e=g[c+14&15],d=g[c&15]=(d>>>7^d>>>18^d>>>3^d<<25^d<<14)+(e>>>17^e>>>19^e>>>10^e<<15^e<<13)+g[c&15]+g[c+9&15]|0),d=d+t+(p>>>6^p>>>11^p>>>25^p<<26^p<<21^p<<7)+(q^p&(r^q))+h[c],t=q,q=r,r=p,p=n+d|0,n=m,m=k,k=l,l=d+(k&m^n&(k^m))+(k>>>2^k>>>13^k>>>22^k<<30^k<<19^k<<10)|0;f[0]=f[0]+l|0;f[1]=f[1]+k|0;f[2]=f[2]+m|0;f[3]=f[3]+n|0;f[4]=f[4]+p|0;f[5]=f[5]+r|0;f[6]= +f[6]+q|0;f[7]=f[7]+t|0} +sjcl.mode.ccm={name:"ccm",G:[],listenProgress:function(a){sjcl.mode.ccm.G.push(a)},unListenProgress:function(a){a=sjcl.mode.ccm.G.indexOf(a);-1l)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(g=2;4>g&&k>>>8*g;g++);g<15-l&&(g=15-l);c=h.clamp(c, +8*(15-g));b=sjcl.mode.ccm.V(a,b,c,d,e,g);f=sjcl.mode.ccm.C(a,f,c,b,e,g);return h.concat(f.data,f.tag)},decrypt:function(a,b,c,d,e){e=e||64;d=d||[];var g=sjcl.bitArray,f=g.bitLength(c)/8,h=g.bitLength(b),l=g.clamp(b,h-e),k=g.bitSlice(b,h-e),h=(h-e)/8;if(7>f)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(b=2;4>b&&h>>>8*b;b++);b<15-f&&(b=15-f);c=g.clamp(c,8*(15-b));l=sjcl.mode.ccm.C(a,l,c,k,e,b);a=sjcl.mode.ccm.V(a,l.data,c,d,e,b);if(!g.equal(l.tag,a))throw new sjcl.exception.corrupt("ccm: tag doesn't match"); +return l.data},na:function(a,b,c,d,e,g){var f=[],h=sjcl.bitArray,l=h.i;d=[h.partial(8,(b.length?64:0)|d-2<<2|g-1)];d=h.concat(d,c);d[3]|=e;d=a.encrypt(d);if(b.length)for(c=h.bitLength(b)/8,65279>=c?f=[h.partial(16,c)]:0xffffffff>=c&&(f=h.concat([h.partial(16,65534)],[c])),f=h.concat(f,b),b=0;be||16m&&(sjcl.mode.ccm.fa(f/ +l),m+=n),c[3]++,e=a.encrypt(c),b[f]^=e[0],b[f+1]^=e[1],b[f+2]^=e[2],b[f+3]^=e[3];return{tag:d,data:h.clamp(b,k)}}}; +sjcl.mode.ocb2={name:"ocb2",encrypt:function(a,b,c,d,e,g){if(128!==sjcl.bitArray.bitLength(c))throw new sjcl.exception.invalid("ocb iv must be 128 bits");var f,h=sjcl.mode.ocb2.S,l=sjcl.bitArray,k=l.i,m=[0,0,0,0];c=h(a.encrypt(c));var n,p=[];d=d||[];e=e||64;for(f=0;f+4e.bitLength(c)&&(h=g(h,d(h)),c=e.concat(c,[-2147483648,0,0,0]));f=g(f,c); +return a.encrypt(g(d(g(h,d(h))),f))},S:function(a){return[a[0]<<1^a[1]>>>31,a[1]<<1^a[2]>>>31,a[2]<<1^a[3]>>>31,a[3]<<1^135*(a[0]>>>31)]}}; +sjcl.mode.gcm={name:"gcm",encrypt:function(a,b,c,d,e){var g=b.slice(0);b=sjcl.bitArray;d=d||[];a=sjcl.mode.gcm.C(!0,a,g,d,c,e||128);return b.concat(a.data,a.tag)},decrypt:function(a,b,c,d,e){var g=b.slice(0),f=sjcl.bitArray,h=f.bitLength(g);e=e||128;d=d||[];e<=h?(b=f.bitSlice(g,h-e),g=f.bitSlice(g,0,h-e)):(b=g,g=[]);a=sjcl.mode.gcm.C(!1,a,g,d,c,e);if(!f.equal(a.tag,b))throw new sjcl.exception.corrupt("gcm: tag doesn't match");return a.data},ka:function(a,b){var c,d,e,g,f,h=sjcl.bitArray.i;e=[0,0, +0,0];g=b.slice(0);for(c=0;128>c;c++){(d=0!==(a[Math.floor(c/32)]&1<<31-c%32))&&(e=h(e,g));f=0!==(g[3]&1);for(d=3;0>>1|(g[d-1]&1)<<31;g[0]>>>=1;f&&(g[0]^=-0x1f000000)}return e},j:function(a,b,c){var d,e=c.length;b=b.slice(0);for(d=0;de&&(a=b.hash(a));for(d=0;dd||0>c)throw sjcl.exception.invalid("invalid params to pbkdf2");"string"===typeof a&&(a=sjcl.codec.utf8String.toBits(a));"string"===typeof b&&(b=sjcl.codec.utf8String.toBits(b));e=e||sjcl.misc.hmac;a=new e(a);var g,f,h,l,k=[],m=sjcl.bitArray;for(l=1;32*k.length<(d||1);l++){e=g=a.encrypt(m.concat(b,[l]));for(f=1;ff;f++)e.push(0x100000000*Math.random()|0);for(f=0;f=1<this.o&&(this.o= +g);this.P++;this.b=sjcl.hash.sha256.hash(this.b.concat(e));this.L=new sjcl.cipher.aes(this.b);for(d=0;4>d&&(this.h[d]=this.h[d]+1|0,!this.h[d]);d++);}for(d=0;d>>1;this.c[f].update([d,this.N++,2,b,g,a.length].concat(a))}break;case "string":void 0===b&&(b=a.length);this.c[f].update([d,this.N++,3,b,g,a.length]);this.c[f].update(a);break;default:l=1}if(l)throw new sjcl.exception.bug("random: addEntropy only supports number, array of numbers or string");this.m[f]+=b;this.f+=b;h===this.u&&(this.isReady()!==this.u&&A("seeded",Math.max(this.o,this.f)),A("progress",this.getProgress()))},isReady:function(a){a=this.T[void 0!== +a?a:this.M];return this.o&&this.o>=a?this.m[0]>this.ba&&(new Date).valueOf()>this.Z?this.J|this.I:this.I:this.f>=a?this.J|this.u:this.u},getProgress:function(a){a=this.T[a?a:this.M];return this.o>=a?1:this.f>a?1:this.f/a},startCollectors:function(){if(!this.D){this.a={loadTimeCollector:B(this,this.ma),mouseCollector:B(this,this.oa),keyboardCollector:B(this,this.la),accelerometerCollector:B(this,this.ea),touchCollector:B(this,this.qa)};if(window.addEventListener)window.addEventListener("load",this.a.loadTimeCollector, +!1),window.addEventListener("mousemove",this.a.mouseCollector,!1),window.addEventListener("keypress",this.a.keyboardCollector,!1),window.addEventListener("devicemotion",this.a.accelerometerCollector,!1),window.addEventListener("touchmove",this.a.touchCollector,!1);else if(document.attachEvent)document.attachEvent("onload",this.a.loadTimeCollector),document.attachEvent("onmousemove",this.a.mouseCollector),document.attachEvent("keypress",this.a.keyboardCollector);else throw new sjcl.exception.bug("can't attach event"); +this.D=!0}},stopCollectors:function(){this.D&&(window.removeEventListener?(window.removeEventListener("load",this.a.loadTimeCollector,!1),window.removeEventListener("mousemove",this.a.mouseCollector,!1),window.removeEventListener("keypress",this.a.keyboardCollector,!1),window.removeEventListener("devicemotion",this.a.accelerometerCollector,!1),window.removeEventListener("touchmove",this.a.touchCollector,!1)):document.detachEvent&&(document.detachEvent("onload",this.a.loadTimeCollector),document.detachEvent("onmousemove", +this.a.mouseCollector),document.detachEvent("keypress",this.a.keyboardCollector)),this.D=!1)},addEventListener:function(a,b){this.K[a][this.ga++]=b},removeEventListener:function(a,b){var c,d,e=this.K[a],g=[];for(d in e)e.hasOwnProperty(d)&&e[d]===b&&g.push(d);for(c=0;cb&&(a.h[b]=a.h[b]+1|0,!a.h[b]);b++);return a.L.encrypt(a.h)}function B(a,b){return function(){b.apply(a,arguments)}}sjcl.random=new sjcl.prng(6); +a:try{var D,E,F,G;if(G="undefined"!==typeof module&&module.exports){var H;try{H=require("crypto")}catch(a){H=null}G=E=H}if(G&&E.randomBytes)D=E.randomBytes(128),D=new Uint32Array((new Uint8Array(D)).buffer),sjcl.random.addEntropy(D,1024,"crypto['randomBytes']");else if("undefined"!==typeof window&&"undefined"!==typeof Uint32Array){F=new Uint32Array(32);if(window.crypto&&window.crypto.getRandomValues)window.crypto.getRandomValues(F);else if(window.msCrypto&&window.msCrypto.getRandomValues)window.msCrypto.getRandomValues(F); +else break a;sjcl.random.addEntropy(F,1024,"crypto['getRandomValues']")}}catch(a){"undefined"!==typeof window&&window.console&&(console.log("There was an error collecting entropy from the browser:"),console.log(a))} +sjcl.json={defaults:{v:1,iter:1E3,ks:128,ts:64,mode:"ccm",adata:"",cipher:"aes"},ja:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json,g=e.g({iv:sjcl.random.randomWords(4,0)},e.defaults),f;e.g(g,c);c=g.adata;"string"===typeof g.salt&&(g.salt=sjcl.codec.base64.toBits(g.salt));"string"===typeof g.iv&&(g.iv=sjcl.codec.base64.toBits(g.iv));if(!sjcl.mode[g.mode]||!sjcl.cipher[g.cipher]||"string"===typeof a&&100>=g.iter||64!==g.ts&&96!==g.ts&&128!==g.ts||128!==g.ks&&192!==g.ks&&0x100!==g.ks||2>g.iv.length|| +4=b.iter||64!==b.ts&&96!==b.ts&&128!==b.ts||128!==b.ks&&192!==b.ks&&0x100!==b.ks||!b.iv||2>b.iv.length||4