Discord/Mattermost/Slack end-to-end encryption in different variations (Mostly proof of concept, you have been warned)
scitor 33283f42a2
Update main README.md
8 months ago
dist master is webcrypto aes 11 months ago
docs more images (chat) 11 months ago
firefox_extension Update README.md 8 months ago
src master is webcrypto aes 11 months ago
LICENSE license 11 months ago
README.md Update main README.md 8 months ago
min basic signing (no safety) 11 months ago
patch_mattermost_linux_client.sh minor installer fix 11 months ago

README.md

webchat-end2end

This is end-to-end encryption in different variations (Mostly proof of concept), mostly formatted as userscript for easy install. Just look up the differences if your favorite (chat/crypto) combination isn't available here, which are only about finding the locks (img alt) and the input textfield (id/tagname) on the page. (even that is almost identical)

Usage

  • setup encryption by clicking the 🔒
  • type message, [Enter]

Implementations using Web Crypto API

AES-CBC (pre-shared key)

Install (into desktop client)

There is a patcher for the Mattermost linux desktop client, you can check it's code for the necessary steps

Install for Mattermost (into browser)

You can copy and paste this into your address bar, while on your favorite mattermost chat:

javascript:'use strict';(function(){function C(a){return t.digest("SHA-256",w(z(a))).then(function(a){return A(a)})}function A(a){var b=D(a).replace(/(..)/g,"$1:").slice(0,-1).toUpperCase();return t.importKey("raw",a,p,!1,["encrypt","decrypt"]).then(function(a){return[b,a]})}function E(a,b){var c=Object.assign({iv:new Uint8Array(16)},p);window.crypto.getRandomValues(c.iv);return t.encrypt(c,a,b).then(function(a){return new Uint8Array(q(c.iv).concat(q(new Uint8Array(a))))})}function F(a,b){var c=Object.assign({iv:new Uint8Array(16)},p);c.iv=new Uint8Array(q(b,0,16));return t.decrypt(c,a,new Uint8Array(q(b,16)))}function D(a){return[].map.call(new Uint8Array(a),function(a){a=a.toString(16);return(2>a.length?"0":"")+a}).join("")}function G(a){if(a.length%2)return new Uint8Array(0);var b=new Uint8Array(a.length/2);a.replace(/(..)/g,"$1:").slice(0,-1).split(":").forEach(function(a,e){b[e]=parseInt(a,16)});return b}function q(a,b,c){return[].slice.call(a,b,c)}function B(a){return[].map.call(a,function(a){return String.fromCharCode(a)}).join("")}function w(a){var b=new Uint8Array(a.length);[].map.call(a,function(a,e){b[e]=a.charCodeAt(0)});return b}function z(a){return encodeURIComponent(a).replace(/%([0-9A-F]{2})/g,function(a,c){return String.fromCharCode(parseInt(c,16))})}function H(a){return decodeURIComponent(a.replace(/(.)/g,function(a,c){a=c.charCodeAt(0).toString(16).toUpperCase();return"%"+(2>a.length?"0":"")+a}))}function I(a){if(8>a.target.value.length)return 0<a.target.value.length?x("password too short (<8)",!1):null;var b=m();C(a.target.value).then(function(c){l[b]=c[1];y("keyHash",c[0]);x(p.name+" 256bit key generated",!0);a.target.placeholder="change password";document.getElementById("E2EkeyDiv").innerHTML=c[0].substring(0,47)+"<br>"+c[0].substring(48,96);n=""});a.target.value=""}function m(){return location.href.match(/\/(channel|message)s\/(.*)/)[2]}function J(a){var b=m();a.target.checked?(h[b]={useCTRL:!0,autoSend:!1},document.getElementById("E2Efieldset").style.display="",document.getElementById("E2EpwdInput").disabled=void 0,document.getElementById("E2EpwdInput").placeholder="input password",document.getElementById("E2EctrlCheckbox").disabled=void 0,document.getElementById("E2EctrlCheckbox").checked=!0,document.getElementById("E2EautoCheckbox").disabled=void 0,document.getElementById("E2EautoCheckbox").checked=!1):(delete h[b],delete l[b],document.getElementById("E2Efieldset").style.display="none",document.getElementById("E2EpwdInput").placeholder="disabled",document.getElementById("E2EpwdInput").disabled="disabled",document.getElementById("E2EctrlCheckbox").disabled="disabled",document.getElementById("E2EautoCheckbox").disabled="disabled",n=document.getElementById("E2EkeyDiv").innerHTML="");r&&r.setItem("channelSettings",JSON.stringify(h))}function y(a,b){var c=m();h[c][a]=b;r&&r.setItem("channelSettings",JSON.stringify(h))}function K(){var a=m(),b=h[a],c=document.createElement("div");c.id="E2Ebackground";document.body.insertBefore(c,document.getElementById("root"));document.getElementById("root").style.filter="blur(2px)";c=document.createElement("div");c.id="E2Eoverlay";c.onclick=L;var e=document.createElement("div");e.id="E2Ewindow";f&&(e.style.backgroundColor=f.centerChannelBg,e.style.color=f.centerChannelColor);c.appendChild(e);e.innerHTML='<h3 style="padding: 20px 0;">end2end encryption settings</h3>';var d=e.querySelector("h3");f&&(d.style.backgroundColor=f.sidebarHeaderBg,d.style.color=f.sidebarHeaderTextColor);d=document.createElement("label");d.title="Enables chat encryption so nobody can read this\nconversation without the correct password";e.appendChild(d);var g=document.createElement("input");g.id="E2EenabledCheckbox";g.type="checkbox";b&&(g.checked=!0);g.onchange=J;d.appendChild(g);g="Encrypt "+("message"==location.href.match(/\/(channel|message)s\//)[1]?"Chat with":"Channel");d.appendChild(document.createTextNode(g));g=document.querySelector("#channelHeaderDropdownButton>strong").innerHTML.replace(/<[^>]+>/g,"");var u=document.createElement("p");u.appendChild(document.createTextNode(g+" ("+a+")"));f&&(u.style.backgroundColor=f.mentionBg,u.style.color=f.mentionColor);d.appendChild(u);e.appendChild(document.createElement("br"));d=document.createElement("span");d.id="E2Efieldset";b||(d.style.display="none");e.appendChild(d);g=document.createElement("input");g.type="password";g.id="E2EpwdInput";g.title="Password isn't sent anywhere, only used once to generate encryption key\n(which is being saved in localStorage per channel)";b||(g.disabled=!0);g.placeholder=b?(l[a]?"change":"input")+" password":"disabled";g.onblur=I;g.onfocus=function(){clearTimeout(v);document.getElementById("E2EstatusDiv").innerHTML="";document.getElementById("E2EstatusDiv").style.display="none"};f&&(g.style.border="1px solid "+f.sidebarHeaderTextColor,g.style.backgroundColor=f.sidebarHeaderBg,g.style.color=f.sidebarHeaderTextColor);d.appendChild(g);d.appendChild(document.createElement("br"));a=document.createElement("label");a.title="When disabled, only Enter is required to encrypt & send.\nOnly encrypted chatting is possible then.";d.appendChild(a);d.appendChild(document.createElement("br"));g=document.createElement("input");g.id="E2EctrlCheckbox";g.type="checkbox";if(!b||b.useCTRL)g.checked=!0;b||(g.disabled=!0);g.onchange=function(a){y("useCTRL",a.target.checked)};a.appendChild(g);a.appendChild(document.createTextNode("use CTRL+Enter to encrypt"));a=document.createElement("label");a.title="When enabled, the message is sent after encryption.\nDisable CTRL+Enter for seamless encryption.";d.appendChild(a);d=document.createElement("input");d.id="E2EautoCheckbox";d.type="checkbox";b?b.autoSend&&(d.checked=!0):d.disabled=!0;d.onchange=function(a){y("autoSend",a.target.checked)};a.appendChild(d);a.appendChild(document.createTextNode("auto-send encrypted text"));a=document.createElement("div");a.id="E2EkeyDiv";a.title=p.name+" 256bit key";a.innerHTML="";b&&b.keyHash&&(a.innerHTML=[b.keyHash.substring(0,47),b.keyHash.substring(48,96)].join("<br>"));f&&(a.style.backgroundColor=f.sidebarHeaderBg,a.style.color=f.sidebarHeaderTextColor);e.appendChild(a);k=document.createElement("div");k.id="E2EstatusDiv";k.style.display="none";e.appendChild(k);document.body.insertBefore(c,document.getElementById("root"))}function L(a){if("E2Eoverlay"==a.target.id){a=m();if((a=h[a])&&!a.keyHash)return x("input password",!1);clearTimeout(v);k=void 0;document.getElementById("root").style.filter="";document.body.removeChild(document.getElementById("E2Ebackground"));document.body.removeChild(document.getElementById("E2Eoverlay"))}}function x(a,b){clearTimeout(v);k.innerHTML=a;k.style.color=f?b?f.onlineIndicator:f.errorTextColor:b?"#d0ffd0":"#ffd0d0";k.style.display="";v=setTimeout(function(){k.innerHTML="";k.style.display="none"},5E3)}function M(){var a=document.createElement("style");a.type="text/css";a.innerHTML="\n#E2Ebackground, #E2Eoverlay, #E2EkeyDiv, #E2EstatusDiv {\n\tposition: absolute;\n}\n\n#E2Ebackground, #E2Eoverlay {\n\ttop: 0;\n\tleft: 0;\n\twidth: 100vw;\n\theight: 100vh;\n\tz-index: 10;\n}\n\n#E2Ebackground {\n\tbackground-color: black;\n\topacity: 0.666;\n\tz-index: 9;\n}\n\n#E2Ewindow {\n\twidth: 400px;\n\theight: 290px;\n\tmargin: 200px auto 0;\n\tbackground-color: lightgrey;\n\tcolor: black;\n\tbox-shadow: 2px 2px 10px black;\n\tborder-radius: 4px;\n\ttext-align: center;\n\tposition: relative;\n\tpadding: 2px 0 0;\n}\n\n#E2Ewindow h3 {\n\tbackground-color: grey;\n\tcolor: black;\n\tmargin: 6px 0;\n}\n\n#E2Ewindow label p {\n\tmargin: 6px 0 0;\n\tpadding: 4px 8px;\n\tbackground-color: lightgrey;\n\tcolor: black;\n\tborder-radius: 4px;\n}\n\n#E2EpwdInput {\n\tborder: 1px solid black;\n\tborder-radius: 4px;\n\ttext-align: center;\n\tpadding: 4px 8px;\n\tmargin: 12px 0;\n\tbackground-color: grey;\n\tcolor: black;\n}\n\n#E2EenabledCheckbox,\n#E2EautoCheckbox,\n#E2EctrlCheckbox {\n\tmargin-right: 10px;\n}\n\n#E2EstatusDiv,\n#E2EkeyDiv {\n\tbottom: 0;\n\tleft: 0;\n\tright: 0;\n\theight: 30px;\n\tmargin-bottom: 10px;\n}\n\n#E2EkeyDiv {\n\tfont-size: 80%;\n\tfont-family: monospace;\n\tbackground-color: grey;\n\tcolor: black;\n}\n\n#E2EstatusDiv {\n\tfont-size: 140%;\n\tbackground-color: rgba(0,0,0,.8);\n}".replace(/\s{2,}/g," ");document.getElementsByTagName("head")[0].appendChild(a)}var p={name:"AES-CBC"},t=window.crypto.subtle,r=window.localStorage,h={},l={},f,k,v,n="";try{h=JSON.parse(r.getItem("channelSettings"))||{}}catch(a){}(function b(){if(!document.getElementById("post_textbox"))return setTimeout(b,1E3);M();document.body.addEventListener("keydown",function(c){if("E2EpwdInput"==c.target.id)return 13!=c.keyCode||c.target.blur();if(!(13!=c.keyCode||c.shiftKey||c.altKey||c.target.value.match(/^\ud83d\udd12/))&&["post_textbox","reply_textbox"].includes(c.target.id)){var b=m(),d=h[b];if(d&&l[b]&&d.useCTRL===c.ctrlKey){c.stopPropagation();c.preventDefault();var g=document.getElementById("headerUsername").innerHTML,f=Math.floor(new Date/1E3).toString(16);E(l[b],w(z([c.target.value,g,f].join("\u001f")))).then(function(b){c.target.value="\ud83d\udd12"+btoa(B(b));c.target.dispatchEvent(new Event("input",{bubbles:!0}));d.autoSend&&setTimeout(function(){document.querySelector("a.send-button").click()},100)});c.target.value="encrypting..."}}},!0)})();setInterval(function(){var b=document.getElementById("E2Elock");if(b){var c=m(),e=h[c];if(e&&e.keyHash){if(!l[c]&&e.keyHash)return A(G(e.keyHash.replace(/:/g,""))).then(function(d){l[c]=d[1]});l[c]&&c!=n&&(n=c,b.innerText="\ud83d\udd12",b.style.color=f?f.onlineIndicator:"#008000");q(document.querySelectorAll('img[alt="\ud83d\udd12"]')).forEach(function(d){d.alt="\ud83d\udd13";d.style.float="right";d.style.marginRight="40px";var b=d.nextSibling.nodeValue;F(l[c],w(atob(d.nextSibling.nodeValue))).then(function(b){b=H(B(new Uint8Array(b))).split("\u001f");var c=new Date(1E3*parseInt(b[2],16));d.nextSibling.nodeValue=b[0];d.title=["decrypted with key ",e.keyHash.substr(0,15),"...",e.keyHash.substr(80,96),"\n(encrypted by ",b[1]," at ",c.toLocaleDateString()," @ ",c.toLocaleTimeString(),")"].join("");d.style.backgroundColor=f?f.onlineIndicator:"#d0ffd0"}).catch(function(c){d.title="unable to decrypt (change password and click to try again)";d.nextSibling.nodeValue=b;d.style.backgroundColor=f?f.errorTextColor:"#ffd0d0";d.style.cursor="pointer";d.onclick=function(){d.alt="\ud83d\udd12";d.title="";d.style.backgroundColor="initial";d.style.cursor="initial";d.onclick=void 0}});d.nextSibling.nodeValue="decrypting..."})}else c!=n&&(n=c,b.innerText="\ud83d\udd13",b.style.color=f?f.errorTextColor:"#800000")}},1E3);(function c(){if(!document.getElementById("channel-header"))return setTimeout(c,1E3);var e=document.createElement("button");e.id="E2Elock";e.style.fontSize="140%";e.classList.add("style--none");e.classList.add("channel-header__icon");e.onclick=K;e.appendChild(document.createTextNode("\ud83d\udd13"));var d=document.createElement("div");d.classList.add("flex-child");d.appendChild(e);e=document.querySelector("#channel-header>div");e.insertBefore(d,e.childNodes[1]);try{f=JSON.parse(PreferenceStore.entities["theme--"].value)}catch(g){f=!1}})()})();

Beware: When pasting Chrome removes the javascript: part as a safety measure... so don't forget to add it back.

You can also make a bookmark with this ;)

This uses the mm-aes.min.js, WebCrypto AES-CBC 256bit (source)

UPDATE: There is a new gui for mm-aes.

1. Disabled encryption

encryption disabled

2. open menu to configure (user chat / channel)

menu to setup encryption user initializing encryption channel

3. key is generated from passphrase

initializing encryption channel

4. Encryption enabled

encryption enabled

should fit all themes

theme black theme custom

1. chat (almost) normally

text input

2. CTRL+Enter (just Enter is also possible)

text encrypt

3. Enter (send, can be automated)

text decrypted

without password (mouseover lock)

text encrypted

without plugin

only encrypted

RSA (keypair, signatures)

in the makings

OTR

planned (wanna make PR?)

Chrome/Firefox Extensions

Chrome: none yet...

Firefox: check the firefox_extension directory. (Thanks to Martin W.)

Desktop Client Extension

Check. See above install instructions.

cryptico (RSA, keypair, signing)

This is also a POC, but it's working pretty well. RSA-Key is regenerated on session start as long as the seed is the same. Sends Pubkey and sings messages. Has key cache per channel (remove msgs and reload to clear). It's using cryptico-js as encryption/signing lib

You can access the sources in the rsa_cryptico branch.

XOR cipher (first version)

Discord/Mattermost/Slack end-to-end encryption in 37 lines :D This will actually prevent cleartext chatlogs though this is not meant to be safe in any way ;) you have been warned

You can access the sources in the xor_first_version branch.