├── LICENSE ├── README.md ├── background.js ├── chat.html ├── chatscript.js ├── config.xml ├── favicon.ico ├── icon.png ├── index.html ├── js-body ├── bodyscript.js ├── filedrop.js ├── initbuttons.js └── screens.js ├── js-head ├── Chromestuff.js ├── SSSS.js ├── colors.js ├── crypto-extra.js ├── crypto-main.js ├── dictionary_en.js ├── imagestego.js ├── keylock.js ├── license.js ├── localdir.js ├── mail&chat.js └── textstego.js ├── js-opensrc ├── ed2curve.js ├── isaac.js ├── jscolor.js ├── jssteg-1.0.js ├── jsstegdecoder-1.0.js ├── jsstegencoder-1.0.js ├── lz-string.js ├── nacl-fast.js ├── nacl-util.js ├── nacl.js ├── purify.js ├── qrcode.js ├── scrypt-async.js └── secrets.js ├── manifest.firefox.json ├── manifest.json ├── passlok-icon128.png ├── passlok-icon48.png ├── passlok-touch-icon.png ├── passlok.appcache ├── phonegap └── screen │ ├── android │ ├── screen-hdpi-portrait.png │ ├── screen-ldpi-portrait.png │ ├── screen-mdpi-portrait.png │ └── screen-xdpi-portrait.png │ └── ios │ ├── screen-ipad-landscape-2x.png │ ├── screen-ipad-landscape.png │ ├── screen-ipad-portrait-2x.png │ ├── screen-ipad-portrait.png │ ├── screen-iphone-portrait-2x.png │ ├── screen-iphone-portrait-568h-2x.png │ └── screen-iphone-portrait.png └── style.css /README.md: -------------------------------------------------------------------------------- 1 | PassLok Privacy 2 | =============== 3 | 4 | PassLok is a toolkit that implements public key cryptography and steganography to supplement ANY communications program. 5 | 6 | These are the principles guiding the design of PassLok: 7 | * Perfect portability. Runs on any computer or mobile device. 8 | * Completely self-contained so it runs offline. No servers. 9 | * Nothing should be installed. No required secrets saved. 10 | * Highest-level security at every step. No compromises. 11 | * Easy to understand and use by novices. Graphical interface, as clean and simple as possible. No crypto jargon. 12 | 13 | Because of this, PassLok is pure html code consisting mostly of JavaScript instructions. Its cryptography code is based on Tweet NaCl, also on GitHub. It uses XSalsa20 for symmetric encryption and elliptic curves (Curve25519 and Ed25519) for public-key functions. 14 | 15 | PassLok was started as URSA, also by F. Ruiz, and developed privately up to version 1.3.03, made on 8/15/13. Commits on GitHub began seriously with this version. The engine was based on the SJCL library up to version 2.1.03, which has been forked out on this repository in order to preserve it. 16 | 17 | These are the open source libraries used in PassLok, which can be found in the js-opensrc directory: 18 | * Shamir Secret Sharing Scheme. Edited so NaCl RNG is used instead of built-in RNG: https://github.com/amper5and/secrets.js 19 | * DOMpurify XSS filter: https://github.com/cure53/DOMPurify 20 | * Tweet NaCl in JavaScript: https://github.com/dchest/tweetnacl-js 21 | * ed2curve-js conversion of curve coordinates: https://github.com/dchest/ed2curve-js 22 | * SCRYPT key stretching, edited to make it synchronous. https://github.com/dchest/scrypt-async-js 23 | * lz-string compression algorithm: https://github.com/pieroxy/lz-string 24 | * jpeg image steganography: https://github.com/owencm/js-steg 25 | * color picker by Jan Odvarko. Edited so images are included, and to make it smaller: https://github.com/odvarko/JSColor 26 | 27 | The PassLok original code is in directories js-head and js-body: 28 | * this only loads two word arrays: wordlist and blacklist: dictionary_en.js 29 | * Key and Lock functions: keylock.js 30 | * cryptographic functions: crypto-main.js 31 | * signatures and other crypto: crypto-extra.js 32 | * extra functions for mail, etc.: mail&chat.js 33 | * Shamir Secret Sharing Scheme: SSSS.js 34 | * text steganograghy: textstego.js 35 | * image steganography: imagestego.js 36 | * local Directory functions: localdir.js 37 | * functions for switching screens, etc.: screens.js 38 | * special functions that work only with Chrome apps and extensions: Chromestuff.js 39 | * window reformatting, special functions: bodyscript.js 40 | * initialization, button connections: initbuttons.js 41 | 42 | One component run inside and iframes and is served from a different source. It is not included here because we use Phonegap so generate automatically some versions from this repo, and it should not contain that code. Those components are: 43 | * PassLok General Directory: https://github.com/fruiz500/PassLok-GenDir 44 | 45 | Full documentation can be found at: including: 46 | * user manual: http://passlok.weebly.com/uploads/2/4/1/8/24187628/passlok_manual.pdf 47 | * technical design document: http://www.weebly.com/uploads/2/4/1/8/24187628/passlok_technical_document.pdf 48 | * and a number of articles and video tutorials. 49 | 50 | License 51 | ------- 52 | 53 | Copyright (C) 2024 Francisco Ruiz 54 | 55 | This program is free software: you can redistribute it and/or modify 56 | it under the terms of the GNU General Public License as published by 57 | the Free Software Foundation, either version 3 of the License, or 58 | (at your option) any later version. 59 | 60 | This program is distributed in the hope that it will be useful, 61 | but WITHOUT ANY WARRANTY; without even the implied warranty of 62 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 63 | GNU General Public License for more details. 64 | 65 | You should have received a copy of the GNU General Public License 66 | along with this program. If not, see . 67 | 68 | Acknowledgements 69 | ---------------- 70 | 71 | PassLok contains and/or links to code from a number of open source 72 | projects on GitHub, including the Tweet NaCl crypto library, and others. 73 | 74 | Cryptography Notice 75 | ------------------- 76 | 77 | This distribution includes cryptographic software. The country in 78 | which you currently reside may have restrictions on the import, 79 | possession, use, and/or re-export to another country, of encryption 80 | software. BEFORE using any encryption software, please check your 81 | country's laws, regulations and policies concerning the import, 82 | possession, or use, and re-export of encryption software, to see if 83 | this is permitted. See for more 84 | information. 85 | 86 | The U.S. Government Department of Commerce, Bureau of Industry and 87 | Security (BIS), has classified this software as Export Commodity 88 | Control Number (ECCN) 5D002.C.1, which includes information security 89 | software using or performing cryptographic functions with asymmetric 90 | algorithms. The form and manner of this distribution makes it 91 | eligible for export under the License Exception ENC Technology 92 | Software Unrestricted (TSU) exception (see the BIS Export 93 | Administration Regulations, Section 740.13) for both object code and 94 | source code. 95 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | //this one opens tabs as directed by the extension 2 | chrome.action.onClicked.addListener(function(){ 3 | chrome.tabs.create({url: 'index.html'}) 4 | } 5 | ); -------------------------------------------------------------------------------- /chat.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | PassLok Chat 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /chatscript.js: -------------------------------------------------------------------------------- 1 | window.onload = function() { 2 | chatFrame.src = "https://passlok.com/chat/chat.html" + chatToken; //includes the # sign 3 | chatFrame.height = document.documentElement.clientHeight 4 | } 5 | 6 | //this is extracted from the loading URL, and passed on to the frame 7 | var chatToken = decodeURI(location.hash) -------------------------------------------------------------------------------- /config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | PassLok 10 | 11 | 12 | High strength accessible cryptography. 13 | 14 | 15 | 16 | Francisco Ruiz 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fruiz500/PassLok-Privacy/7a226a966489b4e4e53daa19e34a9b48fe773d2c/favicon.ico -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fruiz500/PassLok-Privacy/7a226a966489b4e4e53daa19e34a9b48fe773d2c/icon.png -------------------------------------------------------------------------------- /js-body/bodyscript.js: -------------------------------------------------------------------------------- 1 | //this is the part of the javascript code that must be within the body 2 | 3 | //detect browser and device 4 | var isMobile = (typeof window.orientation != 'undefined'), 5 | isChrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1, 6 | isFirefox = typeof InstallTrigger !== 'undefined', // Firefox 1.0+ 7 | isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0, // At least Safari 3+: "[object HTMLElementConstructor]" 8 | isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0, // Opera 8.0+ (UA detection to detect Blink/v8-powered Opera) 9 | isIE = /*@cc_on!@*/false || !!document.documentMode, // At least IE6 10 | isiPad = (navigator.userAgent.match(/iPad/i) != null), 11 | isiPhone = (navigator.userAgent.match(/iPhone|iPod/i) != null), 12 | isiOS = (isiPhone || isiPad), 13 | isAndroid = (isMobile && !isiOS), 14 | isAndroidTablet = (navigator.userAgent.match(/mobile/i) == null && isAndroid), 15 | isAndroidPhone = (navigator.userAgent.match(/mobile/i) != null && isAndroid), 16 | isFile = (window.location.protocol == 'file:'); 17 | textheight(); 18 | 19 | // Clear out "sorry, no JavaScript" warning and display the type of source 20 | showGreeting(); 21 | 22 | //set global variable indicating if there is a Chrome sync data area. Works for Chrome and Firefox apps and extension 23 | var ChromeSyncOn = false; 24 | if(chrome){ 25 | if(chrome.storage){ 26 | if(chrome.storage.sync){ 27 | ChromeSyncOn = true 28 | } 29 | } 30 | } 31 | 32 | //clears the no JavaScript warning and displays an initial message depending on the type of source 33 | function showGreeting(){ 34 | var protocol = window.location.protocol; 35 | var msgStart = "Welcome to PassLok\r\n", 36 | msgEnd = "\r\nSelect your user name and enter your Key. Then click OK"; 37 | if(protocol == 'file:'){ 38 | pwdMsg.textContent = msgStart + 'running from a saved file' + msgEnd 39 | }else if(protocol == 'https:'){ 40 | pwdMsg.textContent = msgStart + 'downloaded from a secure server' + msgEnd 41 | }else if(protocol == 'chrome-extension:' || protocol == 'moz-extension:'){ 42 | pwdMsg.textContent = msgStart + 'running as a Chrome or Firefox addon' + msgEnd 43 | }else{ 44 | mainTab.style.backgroundColor = '#ffd0ff'; 45 | pwdMsg.textContent = msgStart + 'WARNING: running from an insecure source!' + msgEnd 46 | } 47 | } 48 | 49 | //resizes text boxes so they fit within the window 50 | function textheight(){ 51 | var fullheight = document.documentElement.clientHeight, 52 | offsetheight = 392, 53 | toolbarheight = 50; 54 | if(isiPhone) offsetheight = offsetheight - 65; 55 | lockBox.style.height = (fullheight - 300) * (isMobile ? 1 : 0.8) + 'px'; 56 | if(niceEditor){ 57 | mainBox.style.height = fullheight - offsetheight - toolbarheight + 'px' 58 | }else{ 59 | if(isMobile){ 60 | if(isAndroid && !isFile){ 61 | mainBox.style.height = fullheight - offsetheight + 40 + 'px'; 62 | }else if(isiPhone){ 63 | mainBox.style.height = fullheight - offsetheight - 10 + 'px'; 64 | }else{ 65 | mainBox.style.height = fullheight - offsetheight + 'px'; 66 | } 67 | }else{ 68 | mainBox.style.height = fullheight - offsetheight - 10 + 'px'; 69 | } 70 | fileImg.style.height = parseInt(mainBox.style.height.slice(0,-2)) - 90 + 'px' 71 | } 72 | } 73 | 74 | //this one is called by window.onload below 75 | function loadFileAsURL(){ 76 | var fileToLoad = mainFile.files[0], 77 | fileReader = new FileReader(); 78 | fileReader.onload = function(fileLoadedEvent){ 79 | var fileName = fileToLoad.name; 80 | var URLFromFileLoaded = fileLoadedEvent.target.result; 81 | if(URLFromFileLoaded.length > 2000000){ 82 | var reply = confirm("This file is larger than 1.5MB and Chrome won't save it. Do you want to continue loading it?"); 83 | if(!reply){ 84 | mainMsg.textContent = 'File load canceled'; 85 | return 86 | } 87 | } 88 | if(fileToLoad.type.slice(0,4) == "text"){ 89 | if(URLFromFileLoaded.slice(0,2) == '==' && URLFromFileLoaded.slice(-2) == '=='){ 90 | var fileLink = document.createElement("a"); 91 | fileLink.download = fileName; 92 | fileLink.href = "data:," + decryptSanitizer(URLFromFileLoaded); //filter before adding to the DOM 93 | fileLink.textContent = fileName; 94 | mainBox.appendChild(fileLink) 95 | }else{ 96 | var spacer = document.createElement("br"), 97 | textDiv = document.createElement("div"); 98 | textDiv.textContent = decryptSanitizer(URLFromFileLoaded).replace(/ /g,'  '); 99 | mainBox.appendChild(spacer); 100 | mainBox.appendChild(textDiv) 101 | } 102 | }else{ 103 | var fileLink = document.createElement("a"); 104 | fileLink.download = fileName; 105 | fileLink.href = decryptSanitizer(URLFromFileLoaded).replace(/=+$/,''); 106 | fileLink.textContent = fileName; 107 | mainBox.appendChild(fileLink) 108 | } 109 | mainFile.type = ''; 110 | mainFile.type = 'file' //reset file input 111 | }; 112 | if(fileToLoad.type.slice(0,4) == "text"){ 113 | fileReader.readAsText(fileToLoad, "UTF-8"); 114 | mainMsg.textContent = 'This is the content of file: ' + fileToLoad.name; 115 | }else{ 116 | fileReader.readAsDataURL(fileToLoad, "UTF-8"); 117 | mainMsg.textContent = 'The file has been loaded in encoded form. It is NOT ENCRYPTED'; 118 | } 119 | setTimeout(function(){ 120 | updateButtons(); 121 | if(decryptBtn.textContent == 'Decrypt') mainMsg.textContent = 'This file may contain an encrypted item'; 122 | if(verifyBtn.textContent == 'Unseal') mainMsg.textContent = 'This file may contain a sealed item'; 123 | if(secretShareBtn.textContent == 'Join') mainMsg.textContent = 'This file may contain a part. Add more parts if necessary'; 124 | },300); 125 | } 126 | 127 | //to load a file into the directory dialog 128 | function loadLockFile(){ 129 | var fileToLoad = lockFile.files[0], 130 | fileReader = new FileReader(); 131 | fileReader.onload = function(fileLoadedEvent){ 132 | var fileName = fileToLoad.name; 133 | URLFromFileLoaded = fileLoadedEvent.target.result, 134 | escapedName = escapeHTML(fileName); 135 | lockBox.textContent = ''; 136 | var fileLink = document.createElement('a'); 137 | fileLink.download = escapedName; 138 | fileLink.href = decryptSanitizer(URLFromFileLoaded).replace(/=+$/,''); 139 | fileLink.textContent = escapedName; 140 | lockBox.appendChild(fileLink); 141 | lockFile.type = ''; 142 | lockFile.type = 'file' //reset file input 143 | }; 144 | 145 | fileReader.readAsDataURL(fileToLoad, "UTF-8"); 146 | lockMsg.textContent = 'File ' + escapedName + ' has been loaded' 147 | } 148 | 149 | //to load an image into the main box 150 | function loadImage(){ 151 | var fileToLoad = imgFile.files[0], 152 | fileReader = new FileReader(); 153 | fileReader.onload = function(fileLoadedEvent){ 154 | var URLFromFileLoaded = fileLoadedEvent.target.result; 155 | if(URLFromFileLoaded.slice(0,10) != 'data:image'){ 156 | mainMsg.textContent = 'This file is not a recognized image type'; 157 | return 158 | } 159 | var image = document.createElement("img"); 160 | image.src = decryptSanitizer(URLFromFileLoaded).replace(/=+$/,''); 161 | mainBox.appendChild(image) 162 | imgFile.type = ''; 163 | imgFile.type = 'file' //reset file input 164 | }; 165 | 166 | fileReader.readAsDataURL(fileToLoad, "UTF-8"); 167 | } 168 | 169 | //operates when the Save button is clicked 170 | function saveFiles(){ 171 | mainBox.contentEditable = 'false'; 172 | var files = mainBox.querySelectorAll('a'), 173 | length = files.length; //since files will be loaded as links in the main box 174 | for(var i = 0; i < length; i++){ //download all files 175 | if(files[i].href.includes('data:')) files[i].click() 176 | } 177 | mainBox.contentEditable = 'true' 178 | } 179 | -------------------------------------------------------------------------------- /js-body/initbuttons.js: -------------------------------------------------------------------------------- 1 | // initialize things 2 | window.onload = function() { 3 | 4 | if(isMobile){ 5 | mainMsg.style.height = '20px'; 6 | imgSpacer.style.display = 'none'; 7 | // partSpacer1.style.display = 'none'; 8 | // partSpacer2.style.display = 'none'; 9 | // decoyInSpacer1.style.display = 'none'; 10 | // decoyInSpacer2.style.display = 'none'; 11 | // decoyOutSpacer.style.display = 'none'; 12 | qrcodeImg.style.width = '100%'; 13 | qrcodeImg.style.left = '0'; 14 | var bottomButtons = mainbuttonsbot.children; 15 | for(var i = 0; i < bottomButtons.length; i++) bottomButtons[i].classList.add("narrow") 16 | }else{ 17 | previewImg.style.width = "80%" //smaller image on PCs 18 | } 19 | if(isAndroid){ //resize shift buttons on Android 20 | extra2mainBtn.style.padding = '11px'; 21 | main2extraBtn.style.padding = '11px' 22 | } 23 | if(isiOS) encodeJPGBtn.style.display = 'none'; //JPG hide does not work on iOS 24 | if(isiPhone || isAndroidPhone){ //to make things fit on narrow screens 25 | anonLabel.textContent = ' Anon. '; 26 | // modeLabel.style.display = 'none'; 27 | // otherLabel.style.display = 'none'; 28 | greenLabel.textContent = 'Grn'; 29 | customLabel.textContent = 'Cust'; 30 | backgroundLabel.textContent = 'Bg.'; 31 | sentencesLabel.textContent = 'Sentence'; 32 | lockScr.style.top = "5%"; 33 | lockScr.style.left = "5%"; 34 | lockScr.style.width = "90%"; 35 | lockScr.style.height = "90%" 36 | } 37 | 38 | //load field icons 39 | pwdIntroIcon.src = eyeImg; 40 | pwdIcon.src = eyeImg; 41 | newKeyIcon.src = eyeImg; 42 | decoyInIcon.src = eyeImg; 43 | decoyOutIcon.src = eyeImg; 44 | imageIcon.src = eyeImg; 45 | 46 | //event listeners for buttons etc. 47 | window.addEventListener('resize',textheight); 48 | 49 | mainFile.addEventListener('change', loadFileAsURL); 50 | mainFile.addEventListener('click', function(){this.value = '';}); 51 | 52 | imgFile.addEventListener('change', loadImage); 53 | imgFile.addEventListener('click', function(){this.value = '';}); 54 | 55 | imageFile.addEventListener('change', importImage); 56 | imageFile.addEventListener('click', function(){this.value = '';}); 57 | 58 | imageFileEmail.addEventListener('change', importImage); 59 | imageFileEmail.addEventListener('click', function(){this.value = '';}); 60 | 61 | lockFile.addEventListener('change', loadLockFile); 62 | lockFile.addEventListener('click', function(){this.value = '';}); 63 | 64 | encodePNGBtn.addEventListener('click', encodePNG); 65 | 66 | encodeJPGBtn.addEventListener('click', encodeJPG); 67 | 68 | decodeImgBtn.addEventListener('click', decodeImage); 69 | 70 | imageIcon.addEventListener('click',function(){showPwd('image')}); 71 | 72 | showLockBtn.addEventListener('click', showLock); 73 | 74 | selectMainBtn.addEventListener('click', selectMain); 75 | 76 | clearMainBtn.addEventListener('click', clearMain); 77 | 78 | showLockBtnBasic.addEventListener('click', showLock); 79 | 80 | decryptBtn.addEventListener('click', lockBtnAction); 81 | 82 | verifyBtn.addEventListener('click', signVerify); 83 | 84 | main2extraBtn.addEventListener('click', main2extra); 85 | 86 | decryptBtnBasic.addEventListener('click', lockBtnAction); 87 | 88 | decryptBtnEmail.addEventListener('click', lockBtnAction); 89 | 90 | extra2mainBtn.addEventListener('click', main2extra); 91 | 92 | niceEditBtn.addEventListener('click', toggleRichText); 93 | 94 | chatBtn.addEventListener('click', Chat); 95 | 96 | sendSMSBtn.addEventListener('click', sendSMS); 97 | 98 | secretShareBtn.addEventListener('click', splitJoin); 99 | 100 | stegoBtn.addEventListener('click', textStego); 101 | 102 | stegoBtnEmail.addEventListener('click', textStego); 103 | 104 | image2mainBtn.addEventListener('click', image2main); 105 | 106 | clearLocksBtn.addEventListener('click', clearLocks); 107 | 108 | addLockBtn.addEventListener('click', function(){addLock(false)}); 109 | 110 | renameLockBtn.addEventListener('click', renameLock); 111 | 112 | resetPFSBtn.addEventListener('click', resetPFS); 113 | 114 | showLockDBBtn.addEventListener('click', showLockDB); 115 | 116 | mergeLockDBBtn.addEventListener('click', mergeLockDB); 117 | 118 | moveLockDBBtn.addEventListener('click', moveLockDB); 119 | 120 | acceptKeyBtn.addEventListener('click', acceptpwd); 121 | 122 | cancelKeyBtn.addEventListener('click', cancelKey); 123 | 124 | skipIntroBtn.addEventListener('click', cancelKey); 125 | 126 | changeNameBtn.addEventListener('click', showName); 127 | 128 | changeKeyBtn.addEventListener('click', acceptnewKey); 129 | 130 | changeEmailBtn.addEventListener('click', showEmail); 131 | 132 | backupSettingsBtn.addEventListener('click', moveMyself); 133 | 134 | basicMode.addEventListener('click', mode2basic); 135 | 136 | advancedMode.addEventListener('click', mode2adv); 137 | 138 | emailMode.addEventListener('click', mode2email); 139 | 140 | dropMode.addEventListener('click', mode2drop); 141 | 142 | liteStyle.addEventListener('click', selectStyle); 143 | 144 | darkStyle.addEventListener('click', selectStyle); 145 | 146 | redStyle.addEventListener('click', selectStyle); 147 | 148 | greenStyle.addEventListener('click', selectStyle); 149 | 150 | blueStyle.addEventListener('click', selectStyle); 151 | 152 | customStyle.addEventListener('click', selectStyle); 153 | 154 | colorPicker.addEventListener('change',updateColor); 155 | 156 | rndColors.addEventListener('click', randomColors); 157 | 158 | editTabColor.addEventListener('click', initPicker); 159 | 160 | editBgColor.addEventListener('click', initPicker); 161 | 162 | editBtnColor.addEventListener('click', initPicker); 163 | 164 | editBoxColor.addEventListener('click', initPicker); 165 | 166 | anonMode.addEventListener('click', checkboxStore); 167 | 168 | signedMode.addEventListener('click', checkboxStore); 169 | 170 | onceMode.addEventListener('click', checkboxStore); 171 | 172 | learnMode.addEventListener('click', checkboxStore); 173 | 174 | longMode.addEventListener('click', checkboxStore); 175 | 176 | shortMode.addEventListener('click', checkboxStore); 177 | 178 | compatMode.addEventListener('click', checkboxStore); 179 | 180 | decoyMode.addEventListener('click', checkboxStore); 181 | 182 | wordLockMode.addEventListener('click', checkboxStore); 183 | 184 | ezLokMode.addEventListener('click', checkboxStore); 185 | 186 | normalLockMode.addEventListener('click', checkboxStore); 187 | 188 | fileMode.addEventListener('click', checkboxStore); 189 | 190 | binaryMode.addEventListener('click', checkboxStore); 191 | 192 | textMode.addEventListener('click', checkboxStore); 193 | 194 | qrMode.addEventListener('click', checkboxStore); 195 | 196 | includeMode.addEventListener('click', checkboxStore); 197 | 198 | chromeSyncMode.addEventListener('click', checkboxStore); 199 | 200 | sentenceMode.addEventListener('click', checkboxStore); 201 | 202 | letterMode.addEventListener('click', checkboxStore); 203 | 204 | invisibleMode.addEventListener('click', checkboxStore); 205 | 206 | wordMode.addEventListener('click', checkboxStore); 207 | 208 | spaceMode.addEventListener('click', checkboxStore); 209 | 210 | pwdIcon.addEventListener('click',function(){showPwd('pwd')}); 211 | 212 | introRandomBtn.addEventListener('click', randomToken); 213 | 214 | clearIntroRandomBtn.addEventListener('click', clearIntroEmail); 215 | 216 | randomEmailBtn.addEventListener('click', randomToken); 217 | 218 | acceptEmailBtn.addEventListener('click', email2any); 219 | 220 | cancelEmailBtn.addEventListener('click', cancelEmail); 221 | 222 | acceptNameBtn.addEventListener('click', name2any); 223 | 224 | cancelNameBtn.addEventListener('click', cancelName); 225 | 226 | pwdIntroIcon.addEventListener('click', function(){showPwd('pwdIntro')}); 227 | 228 | clearIntroBtn.addEventListener('click', clearIntro); 229 | 230 | suggestIntroBtn.addEventListener('click', suggestIntro); 231 | 232 | showlockIntroBtn.addEventListener('click', initUser); 233 | 234 | decoyInIcon.addEventListener('click', function(){showPwd('decoyIn')}); 235 | 236 | submitDecoyBtn.addEventListener('click', acceptdecoyIn); 237 | 238 | cancelDecoyBtn.addEventListener('click', cancelDecoy); 239 | 240 | decoyOutIcon.addEventListener('click', function(){showPwd('decoyOut')}); 241 | 242 | submitDecoy2Btn.addEventListener('click', acceptdecoyIn); 243 | 244 | cancelDecoy2Btn.addEventListener('click', cancelDecoy); 245 | 246 | submitPartsBtn.addEventListener('click', submitParts); 247 | 248 | cancelPartsBtn.addEventListener('click', cancelPartsIn); 249 | 250 | cancelChatBtn.addEventListener('click', closeBox); 251 | 252 | submitChatBtn.addEventListener('click', makeChat); 253 | 254 | lockList.addEventListener('change', function(){ 255 | if(dropMode.checked){ 256 | updateUsers() //for File Drop mode 257 | }else{ 258 | fillBox() 259 | } 260 | }); 261 | 262 | lockList.addEventListener('click', updateButtons); 263 | 264 | makeKeyBtn.addEventListener('click',makeKeyFile); 265 | 266 | resetListBtn.addEventListener('click', resetList); 267 | 268 | main2lockBtn.addEventListener('click', main2lock); 269 | 270 | lock2mainBtn.addEventListener('click', main2lock); 271 | 272 | newUserBtn.addEventListener('click', newUser); 273 | 274 | submitKeyChangeBtn.addEventListener('click', acceptnewKey); 275 | 276 | cancelKeyChangeBtn.addEventListener('click', cancelKeyChange); 277 | 278 | newKeyIcon.addEventListener('click',function(){showPwd('newKey')}); 279 | 280 | gotointro2.addEventListener('click', go2intro2); 281 | 282 | backtointro1.addEventListener('click', go2intro2); 283 | 284 | gotointro3.addEventListener('click', go2intro3); 285 | 286 | backtointro2.addEventListener('click', go2intro3); 287 | 288 | gotointro4.addEventListener('click', go2intro4); 289 | 290 | backtointro3.addEventListener('click', go2intro4); 291 | 292 | gotointro5.addEventListener('click', go2intro5); 293 | 294 | backtointro4.addEventListener('click', go2intro5); 295 | 296 | mainBox.addEventListener('keyup', charsLeft); 297 | 298 | mainBox.addEventListener('paste', pasteMain); 299 | 300 | decoyText.addEventListener('keyup', charsLeft); 301 | 302 | chatDate.addEventListener('keyup', charsLeft); 303 | 304 | pwdBox.addEventListener('keyup',function(event){boxKeyup('pwd',event)}); 305 | 306 | pwdIntroBox.addEventListener('keyup',function(event){boxKeyup('pwdIntro',event)}); 307 | 308 | decoyInBox.addEventListener('keyup', function(event){boxKeyup('decoyIn',event)}); 309 | 310 | decoyOutBox.addEventListener('keyup', function(event){boxKeyup('decoyOut',event)}); 311 | 312 | partsIn.addEventListener('keyup', function(event) {partsKeyup(event)}, false); 313 | 314 | newKeyBox.addEventListener('keyup', function(event){boxKeyup('newKey',event)}); 315 | 316 | newKey2Box.addEventListener('keyup', function(event) {newKey2up(event)}, false); 317 | 318 | lockBox.addEventListener('keyup', function(event) {lockBoxKeyup(event)}, false); 319 | 320 | lockBox.addEventListener('paste', pasteLock); 321 | 322 | userNameBox.addEventListener('keyup', function(event) {nameKeyup(event)}, false); 323 | 324 | emailBox.addEventListener('keyup', function(event) {emailKeyup(event)}, false); 325 | 326 | imageBox.addEventListener('keyup', function(event){boxKeyup('image',event)}); 327 | 328 | qrcodeImg.addEventListener('click', function(){qrcodeImg.style.display = 'none';mainMsg.textContent = 'QR code canceled'}); 329 | 330 | //for the rich text editor boxes and buttons 331 | formatBlock.addEventListener("change", function() {formatDoc('formatBlock',this[this.selectedIndex].value);this.selectedIndex=0;}); 332 | fontName.addEventListener("change", function() {formatDoc('fontName',this[this.selectedIndex].value);this.selectedIndex=0;}); 333 | fontSize.addEventListener("change", function() {formatDoc('fontSize',this[this.selectedIndex].value);this.selectedIndex=0;}); 334 | foreColor.addEventListener("change", function() {formatDoc('foreColor',this[this.selectedIndex].value);this.selectedIndex=0;}); 335 | backColor.addEventListener("change", function() {formatDoc('backColor',this[this.selectedIndex].value);this.selectedIndex=0;}); 336 | 337 | document.images[0].addEventListener("click", function() {formatDoc('bold')}); 338 | document.images[1].addEventListener("click", function() {formatDoc('italic')}); 339 | document.images[2].addEventListener("click", function() {formatDoc('underline')}); 340 | document.images[3].addEventListener("click", function() {formatDoc('strikethrough')}); 341 | document.images[4].addEventListener("click", function() {formatDoc('subscript')}); 342 | document.images[5].addEventListener("click", function() {formatDoc('superscript')}); 343 | document.images[6].addEventListener("click", function() {formatDoc('justifyleft')}); 344 | document.images[7].addEventListener("click", function() {formatDoc('justifycenter')}); 345 | document.images[8].addEventListener("click", function() {formatDoc('justifyright')}); 346 | document.images[9].addEventListener("click", function() {formatDoc('justifyfull')}); 347 | document.images[10].addEventListener("click", function() {formatDoc('insertorderedlist')}); 348 | document.images[11].addEventListener("click", function() {formatDoc('insertunorderedlist')}); 349 | document.images[12].addEventListener("click", function() {formatDoc('formatBlock','blockquote')}); 350 | document.images[13].addEventListener("click", function() {formatDoc('outdent')}); 351 | document.images[14].addEventListener("click", function() {formatDoc('indent')}); 352 | document.images[15].addEventListener("click", function() {formatDoc('inserthorizontalrule')}); 353 | document.images[16].addEventListener("click", function() {var sLnk=prompt('Write the URL here','http:\/\/');if(sLnk&&sLnk!=''&&sLnk!='http://'){formatDoc('createlink',sLnk)}}); 354 | document.images[17].addEventListener("click", function() {formatDoc('unlink')}); 355 | document.images[18].addEventListener("click", function() {formatDoc('removeFormat')}); 356 | document.images[19].addEventListener("click", function() {formatDoc('undo')}); 357 | document.images[20].addEventListener("click", function() {formatDoc('redo')}); 358 | document.images[23].addEventListener("click", saveFiles); 359 | 360 | //for the help screens 361 | var helpHeaders = document.getElementsByClassName("helpHeading"); //add listeners to all the help headers 362 | 363 | for (var i = 0; i < helpHeaders.length; i++) { 364 | helpHeaders[i].addEventListener('click', openHelp); 365 | } 366 | 367 | var helpHeaders2 = document.getElementsByClassName("helpHeading2"); //2nd level help 368 | 369 | for (var i = 0; i < helpHeaders2.length; i++) { 370 | helpHeaders2[i].addEventListener('click', openHelp2); 371 | } 372 | 373 | //fixes after inline styles were moved to css file 374 | // lockList.style.padding = '4px'; 375 | // lockList.style.width = '30%'; 376 | basicBtnsTop.style.display = 'block'; 377 | // mainMsg.style.minHeight = '20px'; 378 | extraButtonsTop.style.display = 'none' 379 | }; 380 | 381 | //fix for iOS Safari upon startup 382 | if(isMobile){ 383 | if(window.location.hash.match('#a') && window.location.hash.length < 10) window.location.hash = '' 384 | } 385 | 386 | if(!localStorage){newUser();}else if(localStorage.length == 0){newUser();}else if(localStorage.length == 1 && localStorage['randid']){newUser();}; 387 | 388 | //initialize QRcode 389 | var qrcode = new QRCode(document.getElementById("qrcodeImg"), { 390 | width : 290, 391 | height : 290 392 | }); 393 | 394 | fillNameList(); 395 | 396 | initTabs(); //initialize tabs 397 | 398 | //var time10 = hashTime10(); //get milliseconds for 10 wiseHash at iter = 10 399 | var time10 = 200 //valid for core2 duo. Should be smaller for more recent machines 400 | 401 | //end of body script. 402 | -------------------------------------------------------------------------------- /js-head/Chromestuff.js: -------------------------------------------------------------------------------- 1 | //to put Lock into sync storage 2 | function syncChromeLock(name,data) { 3 | var syncName = userName + '.' + name, 4 | jsonfile = {}; 5 | jsonfile[syncName.toLowerCase()] = data; 6 | chrome.storage.sync.set(jsonfile); 7 | 8 | //now update the list, also in Chrome sync 9 | if(name != 'myself') updateChromeSyncList() 10 | } 11 | 12 | //to update the stored list 13 | function updateChromeSyncList(){ 14 | var ChromeSyncList = lockNames.join('|'), 15 | jsonfile2 = {}; 16 | jsonfile2[userName + '.ChromeSyncList'] = ChromeSyncList; 17 | chrome.storage.sync.set(jsonfile2) 18 | } 19 | 20 | //to retrieve Lock from sync storage. The code specifying what to do with the item is here because the get operation is asynchronous 21 | function getChromeLock(name) { 22 | var syncName = userName + '.' + name; 23 | chrome.storage.sync.get(syncName.toLowerCase(), function (obj){ 24 | var lockdata = obj[syncName.toLowerCase()]; 25 | if(lockdata){ 26 | storeChromeLock(name,lockdata) 27 | }else if(name.slice(0,2) != '--'){ 28 | var name2 = '--' + name.toLowerCase() + '--'; //it may be a List, so change the name and try again 29 | lockdata = obj[name2]; 30 | getChromeLock(name2) 31 | }else{ 32 | if (name.slice(0,2) == '--') name = name.slice(2,name.length-2); 33 | lockMsg.textContent = name + ' not found in Chrome sync' 34 | } 35 | }) 36 | } 37 | 38 | //this one is called by the above function 39 | function storeChromeLock(name,lockdata){ 40 | locDir[name] = JSON.parse(lockdata); 41 | lockBox.textContent = removeHTMLtags(locDir[name][0]); //extra precaution, in case something slipped in 42 | lockMsg.textContent = name + ' added from Chrome sync'; 43 | locDir = sortObject(locDir); 44 | localStorage[userName] = JSON.stringify(locDir); 45 | lockNames = Object.keys(locDir); 46 | fillList(); 47 | updateChromeSyncList() 48 | } 49 | 50 | //to completely remove an entry 51 | function remChromeLock(name){ 52 | var syncName = userName + '.' + name; 53 | chrome.storage.sync.remove(syncName.toLowerCase()); 54 | updateChromeSyncList() 55 | } 56 | 57 | //this one controls an asynchronous loop 58 | var asyncLoop = function(o){ 59 | var i=-1; 60 | 61 | var loop = function(){ 62 | i++; 63 | if(i==o.length){o.callback(); return;} 64 | o.functionToLoop(loop, i) 65 | } 66 | loop() //init 67 | } 68 | 69 | //get Lock list from Chrome sync, then call an asynchronous loop to retrieve the data 70 | function retrieveAllSync(){ 71 | var syncName = userName + '.ChromeSyncList'; 72 | chrome.storage.sync.get(syncName, function(obj){ 73 | var lockdata = obj[syncName]; 74 | if(lockdata){ 75 | var ChromeSyncList = lockdata.split('|'); 76 | 77 | //asynchronous loop to fill local directory 78 | asyncLoop({ 79 | length : ChromeSyncList.length, 80 | 81 | functionToLoop : function(loop, i){ 82 | if (ChromeSyncList[i] != 'myself'){ 83 | var syncName2 = userName + '.' + ChromeSyncList[i]; 84 | var lockdata2 = {}; 85 | chrome.storage.sync.get(syncName2.toLowerCase(), function (obj) { 86 | lockdata2 = obj[syncName2.toLowerCase()]; 87 | locDir[ChromeSyncList[i]] = JSON.parse(lockdata2); 88 | locDir = sortObject(locDir); 89 | localStorage[userName] = JSON.stringify(locDir); 90 | lockNames = Object.keys(locDir); 91 | fillList() 92 | }) 93 | } 94 | loop() 95 | }, 96 | 97 | callback : function(){ //not used here 98 | } 99 | }) 100 | //end of asynchronous loop, any code below won't wait for it to be done 101 | 102 | } 103 | }) 104 | } 105 | -------------------------------------------------------------------------------- /js-head/SSSS.js: -------------------------------------------------------------------------------- 1 | //function that starts it all when the Split/Join button is pushed 2 | function splitJoin(){ 3 | blinkMsg(mainMsg); //Get blinking message started 4 | setTimeout(function(){ //the rest after a 20 ms delay 5 | secretshare() 6 | },20) //end of timeout 7 | } 8 | 9 | //this function implements the Shamir Secret Sharing Scheme, taking the secret from the main box and putting the result back there, and vice-versa. 10 | function secretshare(){ 11 | var main = mainBox.innerHTML.trim(), //innerHTML to preserve links 12 | tags = main.match(/PL\d{2}p\d{3}/); 13 | if(tags){ //main box has parts: join parts 14 | if(main.match('href="data:')){ //parts in links 15 | var shares = main.replace(/
/g,'
').replace(/<\div>/g,"").replace(//g,"").split("
").filter(Boolean) //go from newline-containing string to array 16 | }else{ 17 | var shares = mainBox.innerText.split("\n\n").filter(Boolean) //split when double spaced 18 | } 19 | var n = shares.length, 20 | quorum = parseInt(tags[0].slice(-3)); //quorum in tags is "p" plus 3 digits in a row, first instance 21 | if(n < quorum){ //not enough parts 22 | mainMsg.textContent = 'According to the tags, you need ' + (quorum - n) + ' more parts in the box'; 23 | return 24 | } 25 | if(n < quorum){ //not enough parts 26 | mainMsg.textContent = 'According to the tags, you need ' + (quorum - n) + ' more parts in the box'; 27 | return 28 | } 29 | //extract shares from links, condition shares for combination 30 | for (var i = 0; i < shares.length; i++) { 31 | if(shares[i].match('href="data:')){ //share is in link 32 | shares[i] = shares[i].match(/,[a-zA-Z0-9\/+]+"/)[0].slice(1,-1) 33 | }else{ //share as text, just remove tags and extra spaces 34 | shares[i] = stripTags(shares[i].replace(/\s/g,'')) 35 | } 36 | var shareBin = nacl.util.decodeBase64(shares[i]); 37 | if(!shareBin) return false; 38 | shares[i] = "8" + charArray2hex(shareBin) //convert to hex 39 | } 40 | if(learnMode.checked){ 41 | var reply = confirm("The parts in the main box will be joined to retrieve the original item, which will be placed in this box. Please make sure that there are enough parts. Cancel if this is not what you want."); 42 | if(!reply) return 43 | } 44 | if(n === 1){ 45 | mainMsg.textContent = 'Only one part in main box'; 46 | return 47 | } 48 | try{ 49 | var sechex = secrets.combine(shares), 50 | secBin = hex2charArray(sechex); 51 | if(secBin.join().match(",61,34,100,97,116,97,58,")){ 52 | var secret = nacl.util.encodeUTF8(secBin) 53 | }else{ 54 | var secret = LZString.decompressFromUint8Array(secBin) 55 | } 56 | mainBox.innerHTML = decryptSanitizer(secret); //disable non-whitelisted tags and attributes 57 | mainMsg.textContent = 'Join successful' 58 | }catch(err){ 59 | mainMsg.textContent = 'There was an error' //the encodeUTF8 is the likely culprit 60 | } 61 | }else{ //parts not detected, split instead 62 | if(main == "") { 63 | mainMsg.textContent = 'The box is empty'; 64 | return 65 | } 66 | if(learnMode.checked){ 67 | var reply = confirm("The item in the box will be split into several partial items, which will replace the contents of the box. A popup will ask for the total number of parts, and the minimum needed to reconstruct the original item. Cancel if this is not what you want."); 68 | if(!reply) return 69 | } 70 | var number = partsNumber.value; 71 | if(number.trim() == ""){ //stop to display the entry form if it is empty 72 | partsIn.style.display = "block"; 73 | shadow.style.display = "block"; 74 | if(!isMobile) partsNumber.focus(); 75 | return 76 | } 77 | partsIn.style.display = "none"; 78 | shadow.style.display = "none"; 79 | var quorum = partsQuorum.value; //this value defaults to the total number if empty 80 | if (quorum.trim() == ""){ 81 | quorum = number 82 | } 83 | partsNumber.value = ""; //on re-execution, read the box and reset it 84 | partsQuorum.value = ""; 85 | quorum = parseInt(quorum); 86 | number = parseInt(number); 87 | if(number < 2){number = 2} else if(number > 255) {number = 255}; 88 | if (quorum > number) quorum = number; 89 | var secret = mainBox.innerHTML.trim(); 90 | if(secret.match('="data:')){ //no compression if it includes a file 91 | var secBin = nacl.util.decodeUTF8(secret) 92 | }else{ 93 | var secBin = LZString.compressToUint8Array(secret) 94 | } 95 | var sechex = charArray2hex(secBin), 96 | shares = secrets.share(sechex,number,quorum); 97 | displayshare(shares,quorum); 98 | mainMsg.textContent = number + ' parts made. ' + quorum + ' required to reconstruct'; 99 | partsInBox = true 100 | } 101 | setTimeout(function(){charsLeft();},20) 102 | } 103 | 104 | function displayshare(shares,quorum){ 105 | var length = shares[0].length, 106 | quorumStr = "00" + quorum, 107 | output = ""; 108 | quorumStr = quorumStr.substr(quorumStr.length-3); 109 | 110 | mainBox.textContent = ''; 111 | var fragment = document.createElement('div'); 112 | 113 | for (var i = 0; i < shares.length; i++) { 114 | var dataItem = nacl.util.encodeBase64(hex2charArray(shares[i].slice(1,length))).replace(/=+/g, ''); 115 | if(i > 0) output += "

"; 116 | if(fileMode.checked){ 117 | if(textMode.checked){ 118 | output += 'PassLok 2.4 Part out of ' + quorumStr + ' as a text file' 119 | }else{ 120 | output += 'PassLok 2.4 Part out of ' + quorumStr + ' as a binary file' 121 | } 122 | }else{ 123 | output += "
" + ("PL24p" + quorumStr + "==" + dataItem + "==PL24p" + quorumStr).match(/.{1,80}/g).join("
") + "
" 124 | } 125 | }; 126 | fragment.innerHTML = output; //must use innerHTML because building the list of links with linefeeds in between with the appendChild method does not work in Chrome (bug?) 127 | mainBox.appendChild(fragment) 128 | } 129 | 130 | //convert an array of 8-bit decimal codes into a hexadecimal string 131 | function charArray2hex(charArray){ 132 | var output = ''; 133 | for(var i = 0;i < charArray.length; i++){ 134 | var newstring = charArray[i].toString(16); 135 | if (newstring.length < 2) newstring = '0' + newstring; 136 | output += newstring 137 | } 138 | return output 139 | } 140 | 141 | //convert a hexadecimal string (two characters per byte) into an array of decimal codes. Wrong codes marked as -1 142 | function hex2charArray(string){ 143 | var output = []; 144 | for(var i = 0;i < string.length; i=i+2){ 145 | var a = parseInt(string.slice(i,i+2),16); 146 | if(isNaN(a)){ 147 | output[i/2] = -1 148 | }else{ 149 | output[i/2] = a 150 | } 151 | } 152 | return output 153 | } 154 | -------------------------------------------------------------------------------- /js-head/colors.js: -------------------------------------------------------------------------------- 1 | //this group creates dummy stylesheets for buttons, tabs, boxes, and background. One sheet for general color and another for text 2 | var btnSheet = (function() { 3 | var style = document.createElement("style"); 4 | style.appendChild(document.createTextNode("")); 5 | style.nonce = '4AEemGb0yJptoIGFP3Nd'; //a nonce added because otherwise the CSP of the extension will refuse next statement 6 | document.head.appendChild(style); 7 | return style.sheet 8 | })(); 9 | var tabSheet = (function() { 10 | var style = document.createElement("style"); 11 | style.appendChild(document.createTextNode("")); 12 | style.nonce = '4AEemGb0yJptoIGFP3Nd'; 13 | document.head.appendChild(style); 14 | return style.sheet 15 | })(); 16 | var boxSheet = (function() { 17 | var style = document.createElement("style"); 18 | style.appendChild(document.createTextNode("")); 19 | style.nonce = '4AEemGb0yJptoIGFP3Nd'; 20 | document.head.appendChild(style); 21 | return style.sheet 22 | })(); 23 | var bgSheet = (function() { 24 | var style = document.createElement("style"); 25 | style.appendChild(document.createTextNode("")); 26 | style.nonce = '4AEemGb0yJptoIGFP3Nd'; 27 | document.head.appendChild(style); 28 | return style.sheet 29 | })(); 30 | var btnTextSheet = (function() { 31 | var style = document.createElement("style"); 32 | style.appendChild(document.createTextNode("")); 33 | style.nonce = '4AEemGb0yJptoIGFP3Nd'; 34 | document.head.appendChild(style); 35 | return style.sheet 36 | })(); 37 | var tabTextSheet = (function() { 38 | var style = document.createElement("style"); 39 | style.appendChild(document.createTextNode("")); 40 | style.nonce = '4AEemGb0yJptoIGFP3Nd'; 41 | document.head.appendChild(style); 42 | return style.sheet 43 | })(); 44 | var boxTextSheet = (function() { 45 | var style = document.createElement("style"); 46 | style.appendChild(document.createTextNode("")); 47 | style.nonce = '4AEemGb0yJptoIGFP3Nd'; 48 | document.head.appendChild(style); 49 | return style.sheet 50 | })(); 51 | var bgTextSheet = (function() { 52 | var style = document.createElement("style"); 53 | style.appendChild(document.createTextNode("")); 54 | style.nonce = '4AEemGb0yJptoIGFP3Nd'; 55 | document.head.appendChild(style); 56 | return style.sheet 57 | })(); 58 | 59 | //these two are to add a rule (usually a changed color) to a dummy stylesheet, or to remove it 60 | function addCSSRule(sheet, selector, rules, index) { 61 | if("insertRule" in sheet) { 62 | sheet.insertRule(selector + "{" + rules + "}", index) 63 | } 64 | else if("addRule" in sheet) { 65 | sheet.addRule(selector, rules, index) 66 | } 67 | } 68 | function removeCSSRule(sheet, selector, rules, index) { 69 | if("deleteRule" in sheet) { 70 | sheet.deleteRule(selector + "{" + rules + "}", index) 71 | } 72 | else if("removeRule" in sheet) { 73 | sheet.removeRule(selector, rules, index) 74 | } 75 | } 76 | 77 | //stores custom colors 78 | function storeColors(){ 79 | if(fullAccess){ 80 | var hexCode = ''; 81 | for(var i=0; i<4; i++) hexCode += bgColorStore[i]; 82 | if(locDir['myself']){ 83 | locDir['myself'][2] = hexCode; 84 | localStorage[userName] = JSON.stringify(locDir); 85 | 86 | if(ChromeSyncOn && chromeSyncMode.checked){ 87 | syncChromeLock('myself',JSON.stringify(locDir['myself'])) 88 | } 89 | } 90 | } 91 | } 92 | 93 | //retrieves custom colors from storage 94 | function getCustomColors(){ 95 | if(locDir['myself'][2]){ 96 | var hexCode = locDir['myself'][2]; 97 | while(hexCode.length < 24) hexCode = '0' + hexCode; 98 | for(var i=0; i<4; i++){ 99 | bgColorStore[i] = hexCode.slice(i*6,(i+1)*6); 100 | bgColor[i] = bgColorStore[i]; 101 | fgColorStore[i] = foregColor(bgColorStore[i]); 102 | fgColor[i] = fgColorStore[i] 103 | } 104 | } 105 | } 106 | 107 | //deduces foreground color (white or black) from hex background color 108 | function foregColor(bgColor){ 109 | var red = parseInt(bgColor.slice(0,2),16), 110 | green = parseInt(bgColor.slice(2,4),16), 111 | blue = parseInt(bgColor.slice(4,6),16); 112 | return 0.213 * red + 0.715 * green + 0.072 * blue < 128 ? 'FFFFFF' : '000000' 113 | } 114 | 115 | //returns a closer to neutral gray version of a hex color, as given by a hex offset (1 byte) 116 | function milder(color, offset){ 117 | var red = parseInt(color.slice(0,2),16), 118 | green = parseInt(color.slice(2,4),16), 119 | blue = parseInt(color.slice(4,6),16), 120 | offset10 = parseInt(offset, 16); 121 | if(0.213 * red + 0.715 * green + 0.072 * blue < 128){ //dark or light condition is the same as for foreground color 122 | red = Math.min((red + offset10),255).toString(16); 123 | green = Math.min((green + offset10),255).toString(16); 124 | blue = Math.min((blue + offset10),255).toString(16) 125 | }else{ 126 | red = Math.max((red - offset10),0).toString(16); 127 | green = Math.max((green - offset10),0).toString(16); 128 | blue = Math.max((blue - offset10),0).toString(16) 129 | } 130 | if(red.length < 2) red = '0' + red; //take care of single-digit colors 131 | if(green.length < 2) green = '0' + green; 132 | if(blue.length < 2) blue = '0' + blue; 133 | return red + green + blue 134 | } 135 | 136 | function updateColor(){ 137 | if(editTabColor.checked) {updateTabColor(true)} 138 | else if(editBgColor.checked) {updateBgColor(true)} 139 | else if(editBtnColor.checked) {updateBtnColor(true)} 140 | else {updateBoxColor(true)} 141 | } 142 | 143 | //global variables containing hex screen colors. Default is Light. Indices: 0 = tabs, 1 = background, 2 = buttons, 3 = boxes 144 | var fgColor = ['202128','000000','666666','000000'], 145 | fgColorStore = ['202128','000000','666666','000000'], 146 | bgColor = ['c6d5c6','ffffff','e6e6e6','fffffd'], 147 | bgColorStore = ['c6d5c6','ffffff','e6e6e6','fffffd']; 148 | 149 | //gets color from picker and puts it into global variables 150 | function getColor(i,isPicker){ 151 | if(customStyle.checked){ 152 | if(isPicker){ 153 | bgColor[i] = colorPicker.value; 154 | fgColor[i] = foregColor(bgColor[i]); 155 | bgColorStore[i] = bgColor[i]; 156 | fgColorStore[i] = fgColor[i] 157 | }else{ 158 | bgColor[i] = bgColorStore[i]; 159 | fgColor[i] = fgColorStore[i] 160 | } 161 | } 162 | } 163 | 164 | //change tab colors (index 0); first delete all rules, then re-make them according to the current color; first background, then text 165 | function updateTabColor(isPicker){ 166 | getColor(0,isPicker); 167 | while(tabSheet.cssRules.length) removeCSSRule(tabSheet,"ul#tabs"); 168 | addCSSRule(tabSheet, "ul#tabs", "background-color:#" + bgColor[0]); 169 | addCSSRule(tabSheet, "ul#tabs li a", "background-color:#" + bgColor[0]); 170 | addCSSRule(tabSheet, "ul#tabs li a:hover", "background-color:#" + milder(bgColor[0],'22')); 171 | while(tabTextSheet.cssRules.length) removeCSSRule(tabTextSheet,"ul#tabs"); 172 | addCSSRule(tabTextSheet, "ul#tabs li a", "color:#" + milder(fgColor[0],'22')) 173 | } 174 | 175 | //same for general background (index 1) 176 | function updateBgColor(isPicker){ 177 | getColor(1,isPicker); 178 | while(bgSheet.cssRules.length){ 179 | removeCSSRule(bgSheet,"body"); 180 | removeCSSRule(bgSheet,".block_page"); 181 | removeCSSRule(bgSheet,".white_content"); 182 | removeCSSRule(bgSheet,"ul#tabs"); 183 | removeCSSRule(bgSheet,"div.tabContent"); 184 | removeCSSRule(bgSheet,".helpHeading:hover") 185 | } 186 | addCSSRule(bgSheet, "body", "background-color:#" + bgColor[1]); 187 | addCSSRule(bgSheet, ".block_page", "background-color:#" + bgColor[1]); 188 | addCSSRule(bgSheet, ".white_content", "background-color:#" + bgColor[1]); 189 | addCSSRule(bgSheet, "ul#tabs li a.selected", "background-color:#" + bgColor[1]); 190 | addCSSRule(bgSheet, "div.tabContent", "background-color:#" + bgColor[1]); 191 | addCSSRule(bgSheet, ".helpHeading:hover", "background-color:#" + milder(bgColor[1],'11')); 192 | while(bgTextSheet.cssRules.length){ 193 | removeCSSRule(bgTextSheet,"body"); 194 | removeCSSRule(bgTextSheet,".block_page"); 195 | removeCSSRule(bgTextSheet,".white_content"); 196 | removeCSSRule(bgTextSheet,"ul#tabs"); 197 | removeCSSRule(bgTextSheet,"div.tabContent") 198 | } 199 | addCSSRule(bgTextSheet, "body", "color:#" + fgColor[1]); 200 | addCSSRule(bgTextSheet, ".block_page", "color:#" + fgColor[1]); 201 | addCSSRule(bgTextSheet, ".white_content", "color:#" + fgColor[1]); 202 | addCSSRule(bgTextSheet, "ul#tabs li a.selected", "color:#" + fgColor[1]); 203 | addCSSRule(bgTextSheet, "div.tabContent", "color:#" + fgColor[1]) 204 | } 205 | 206 | //and for buttons (index 2) 207 | function updateBtnColor(isPicker){ 208 | getColor(2,isPicker); 209 | while(btnSheet.cssRules.length){ 210 | removeCSSRule(btnSheet,".cssbutton"); 211 | removeCSSRule(btnSheet,".cssbutton:hover"); 212 | removeCSSRule(btnSheet,"#toolBar1") 213 | } 214 | addCSSRule(btnSheet, ".cssbutton", "background-color:#" + bgColor[2]); 215 | addCSSRule(btnSheet, ".cssbutton:hover", "background-color:#" + milder(bgColor[2],'22')); 216 | addCSSRule(btnSheet, "#toolBar1", "background-color:#" + bgColor[2]); 217 | while(btnTextSheet.cssRules.length){ 218 | removeCSSRule(btnTextSheet,".cssbutton") 219 | } 220 | addCSSRule(btnTextSheet, ".cssbutton", "color:#" + fgColor[2]); 221 | } 222 | 223 | //and for boxes (index 3) 224 | function updateBoxColor(isPicker){ 225 | getColor(3,isPicker); 226 | while(boxSheet.cssRules.length){ 227 | removeCSSRule(boxSheet,".cssbox"); 228 | removeCSSRule(boxSheet,"select") 229 | } 230 | addCSSRule(boxSheet, ".cssbox", "background-color:#" + bgColor[3]); 231 | addCSSRule(boxSheet, "select", "background-color:#" + bgColor[3]); 232 | while(boxTextSheet.cssRules.length){ 233 | removeCSSRule(boxTextSheet,".cssbox"); 234 | removeCSSRule(boxTextSheet,"select") 235 | } 236 | addCSSRule(boxTextSheet, ".cssbox", "color:#" + fgColor[3]); 237 | addCSSRule(boxTextSheet, "select", "color:#" + fgColor[3]); 238 | } 239 | 240 | //initialize color picker 241 | function initPicker(){ 242 | if(editTabColor.checked){ 243 | colorPicker.color.fromString(bgColor[0]) 244 | }else if(editBgColor.checked){ 245 | colorPicker.color.fromString(bgColor[1]) 246 | }else if(editBtnColor.checked){ 247 | colorPicker.color.fromString(bgColor[2]) 248 | }else { 249 | colorPicker.color.fromString(bgColor[3]) 250 | } 251 | } 252 | 253 | //this loads the pre-selected color schemes from Options 254 | function selectStyle(){ 255 | if(liteStyle.checked){ 256 | customColors.style.display = 'none'; 257 | bgColor[0] = 'c6d5c6'; 258 | fgColor[0] = '202128'; 259 | bgColor[1] = 'ffffff'; 260 | fgColor[1] = '000000'; 261 | bgColor[2] = 'e6e6e6'; 262 | fgColor[2] = '666666'; 263 | bgColor[3] = 'fffffd'; 264 | fgColor[3] = '000000'; 265 | updateTabColor(false); 266 | updateBgColor(false); 267 | updateBtnColor(false); 268 | updateBoxColor(false) 269 | }else if(darkStyle.checked){ 270 | customColors.style.display = 'none'; 271 | bgColor[0] = '455245'; 272 | fgColor[0] = 'e9e9e9'; 273 | bgColor[1] = '000000'; 274 | fgColor[1] = 'ffffff'; 275 | bgColor[2] = '7b7b7b'; 276 | fgColor[2] = 'eeeeee'; 277 | bgColor[3] = '111111'; 278 | fgColor[3] = 'ffffff'; 279 | updateTabColor(false); 280 | updateBgColor(false); 281 | updateBtnColor(false); 282 | updateBoxColor(false) 283 | }else if(redStyle.checked){ 284 | customColors.style.display = 'none'; 285 | bgColor[0] = '000000'; 286 | fgColor[0] = 'efefef'; 287 | bgColor[1] = 'cc3300'; 288 | fgColor[1] = 'ffffff'; 289 | bgColor[2] = '663333'; 290 | fgColor[2] = 'dddddd'; 291 | bgColor[3] = 'ffcc99'; 292 | fgColor[3] = '000000'; 293 | updateTabColor(false); 294 | updateBgColor(false); 295 | updateBtnColor(false); 296 | updateBoxColor(false) 297 | }else if(greenStyle.checked){ 298 | customColors.style.display = 'none'; 299 | bgColor[0] = '9999cc'; 300 | fgColor[0] = 'ffffff'; 301 | bgColor[1] = '99cc99'; 302 | fgColor[1] = '000000'; 303 | bgColor[2] = 'ddddaa'; 304 | fgColor[2] = '666666'; 305 | bgColor[3] = 'ffffcc'; 306 | fgColor[3] = '000000'; 307 | updateTabColor(false); 308 | updateBgColor(false); 309 | updateBtnColor(false); 310 | updateBoxColor(false) 311 | }else if(blueStyle.checked){ 312 | customColors.style.display = 'none'; 313 | bgColor[0] = '003399'; 314 | fgColor[0] = 'efefef'; 315 | bgColor[1] = '0099cc'; 316 | fgColor[1] = 'ffffff'; 317 | bgColor[2] = '1c57cc'; 318 | fgColor[2] = 'dddddd'; 319 | bgColor[3] = 'd7ffd7'; 320 | fgColor[3] = '000000'; 321 | updateTabColor(false); 322 | updateBgColor(false); 323 | updateBtnColor(false); 324 | updateBoxColor(false) 325 | }else{ 326 | if(customColors.style.display == 'none'){ 327 | customColors.style.display = 'block' 328 | }else{ 329 | customColors.style.display = 'none' 330 | } 331 | bgColor[0] = bgColorStore[0]; 332 | fgColor[0] = fgColorStore[0]; 333 | updateTabColor(false); 334 | bgColor[1] = bgColorStore[1]; 335 | fgColor[1] = fgColorStore[1]; 336 | updateBgColor(false); 337 | bgColor[2] = bgColorStore[2]; 338 | fgColor[2] = fgColorStore[2]; 339 | updateBtnColor(false); 340 | bgColor[3] = bgColorStore[3]; 341 | fgColor[3] = fgColorStore[3]; 342 | updateBoxColor(false); 343 | initPicker() 344 | } 345 | checkboxStore() 346 | } 347 | 348 | //selects random colors for the custom setting 349 | function randomColors(){ 350 | editBoxColor.checked = true; 351 | for(var i=0; i<4; i++){ 352 | bgColor[i] = Math.floor(Math.random()*16777216).toString(16); 353 | while(bgColor[i].length < 6) bgColor[i] = '0' + bgColor[i]; 354 | fgColor[i] = foregColor(bgColor[i]); 355 | bgColorStore[i] = bgColor[i]; 356 | fgColorStore[i] = fgColor[i] 357 | } 358 | initPicker(); 359 | updateTabColor(false); 360 | updateBgColor(false); 361 | updateBtnColor(false); 362 | updateBoxColor(false) 363 | } 364 | //The main script in the head ends here. 365 | -------------------------------------------------------------------------------- /js-head/license.js: -------------------------------------------------------------------------------- 1 |  /* 2 | @source: https://passlok.com/app/index.html 3 | 4 | @licstart The following is the entire license notice for the 5 | JavaScript code in this page. 6 | 7 | Copyright (C) 2025 Francisco Ruiz 8 | 9 | The JavaScript code in this page is free software: you can 10 | redistribute it and/or modify it under the terms of the GNU 11 | General Public License (GNU GPL) as published by the Free Software 12 | Foundation, either version 3 of the License, or (at your option) 13 | any later version. The code is distributed WITHOUT ANY WARRANTY; 14 | without even the implied warranty of MERCHANTABILITY or FITNESS 15 | FOR A PARTICULAR PURPOSE. See the GNU GPL for more details. 16 | 17 | As additional permission under GNU GPL version 3 section 7, you 18 | may distribute non-source (e.g., minimized or compacted) forms of 19 | that code without the copy of the GNU GPL normally required by 20 | section 4, provided you include this license notice and a URL 21 | through which recipients can access the Corresponding Source. 22 | 23 | 24 | @licend The above is the entire license notice 25 | for the JavaScript code in this page. 26 | */ 27 | 28 | if (window.location.protocol == "http:") { //force SSL/TLS 29 | 30 | var restOfUrl = window.location.href.substr(5); 31 | window.location = "https:" + restOfUrl; 32 | } 33 | -------------------------------------------------------------------------------- /js-head/mail&chat.js: -------------------------------------------------------------------------------- 1 | //formats results depending on tags present and sends to default email 2 | function sendMail() { 3 | var type = getType(mainBox.innerHTML.trim())[0], 4 | words = mainBox.textContent; //for word Locks 5 | if(words.match('==')) words = words.split('==')[1]; 6 | words = words.trim().split(' '); 7 | if(learnMode.checked){ 8 | if(type.match(/[lckgdhasoprASO]/)){ 9 | var reply = confirm("A new tab will open, including the contents of this box in your default email. You still need to supply the recipient's address and a subject line. Only Locks and encrypted or signed items are allowed. Cancel if this is not what you want.") 10 | }else{ 11 | var reply = confirm("An invitation for others to join PassLok and containing your Lock will open in your default email. You still need to supply the recipient's address. Cancel if this is not what you want.") 12 | } 13 | if(!reply) return 14 | } 15 | 16 | if(mainBox.textContent.match("The gibberish link below contains a message from me that has been encrypted with PassLok")){ //invitation message 17 | var link = "mailto:"+ "?subject=Invitation to PassLok" + "&body=" + encodeURIComponent(mainBox.textContent.trim()) + "%0D%0A%0D%0AYou can get PassLok from https://passlok.com/app and other sources, plus the Chrome, Firefox, and Android app stores." 18 | }else{ 19 | var hashTag = encodeURIComponent(mainBox.textContent.trim().replace(/ /g,'_')); //item ready for link 20 | var linkText = "Click the link below if you wish to process this automatically using the web app (the app will open in a new tab and ask you for your Key), or simply copy it and paste it into your favorite version of PassLok:%0D%0A%0D%0Ahttps://passlok.com/app#" + hashTag + "%0D%0A%0D%0AYou can get PassLok from https://passlok.com/app and other sources, plus the Chrome, Firefox, and Android app stores."; 21 | 22 | if(words.length == 20){ //20 words, so most likely a word Lock 23 | var link = "mailto:"+ "?subject= My PassLok 2.5 Lock" + "&body=This email contains my PassLok v.2.5 Lock as a list of words. Use it to encrypt text or files for me to decrypt, or to verify my seal.%0D%0A%0D%0A" + linkText 24 | }else if(type=="a" || type=="A"){ 25 | if(emailMode.checked){ 26 | var link = "mailto:"+ "?subject= " + "&body=" + encodeURIComponent(mainBox.textContent.trim()) 27 | }else{ 28 | var link = "mailto:"+ "?subject= " + "&body=Anonymous message encrypted with PassLok v.2.5 %0D%0A%0D%0ADecrypt with your secret Key.%0D%0A%0D%0A" + linkText 29 | } 30 | }else if (type=="g" || type=="d" || type=="h"){ 31 | if(emailMode.checked){ 32 | var link = "mailto:"+ "?subject= " + "&body=" + encodeURIComponent(mainBox.textContent.trim()) 33 | }else{ 34 | var link = "mailto:"+ "?subject= " + "&body=Message encrypted with PassLok v.2.5 %0D%0A%0D%0ADecrypt with shared Key.%0D%0A%0D%0A" + linkText 35 | } 36 | }else if (type=="s" || type=="S"){ 37 | if(emailMode.checked){ 38 | var link = "mailto:"+ "?subject= " + "&body=" + encodeURIComponent(mainBox.textContent.trim()) 39 | }else{ 40 | var link = "mailto:"+ "?subject= " + "&body=Signed message encrypted with PassLok v.2.5 %0D%0A%0D%0ADecrypt with your secret Key and my Lock.%0D%0A%0D%0A" + linkText 41 | } 42 | }else if (type && type.match(/[oprO]/)){ 43 | if(emailMode.checked){ 44 | var link = "mailto:"+ "?subject= " + "&body=" + encodeURIComponent(mainBox.textContent.trim()) 45 | }else{ 46 | var link = "mailto:"+ "?subject= " + "&body=Read-once message encrypted with PassLok v.2.5 %0D%0A%0D%0ADecrypt with your secret Key.%0D%0A%0D%0A" + linkText 47 | } 48 | }else if (type=="k"){ 49 | var link = "mailto:"+ "?subject=My PassLok database" + "&body=Database encrypted with PassLok v.2.5 %0D%0A%0D%0ADecrypt with my secret Key.%0D%0A%0D%0A" + linkText 50 | }else if (type=="l"){ 51 | var link = "mailto:"+ "?subject= " + "&body=Text sealed with PassLok v.2.5. It is not encrypted. Extract it and verify my authorship using my Lock.%0D%0A%0D%0A" + linkText; 52 | }else if (type=='c'){ 53 | var link = "mailto:"+ "?subject= My PassLok 2.5 Lock" + "&body=This email contains my PassLok v.2.5 Lock. Use it to encrypt text or files for me to decrypt, or to verify my seal.%0D%0A%0D%0A" + linkText 54 | } 55 | } 56 | if(isMobile){ //new window for PC, same window for mobile 57 | if(isChrome && isAndroid){ 58 | mainMsg.textContent = "Android Chrome does not allow communication with the email app" 59 | }else{ 60 | window.open(link,"_parent") 61 | } 62 | }else{ 63 | window.open(link,"_blank") 64 | } 65 | } 66 | 67 | //encrypt main box with myLock in order to make an invitation 68 | function makeInvitation(){ 69 | if(mainBox.textContent.trim() != ''){ 70 | var reply = confirm('Do you want the contents of the main box to be encrypted and added to an invitation email? This will encourage the recipients to try PassLok, but be aware that the encrypted contents WILL NOT BE SECURE.'); 71 | if (!reply) return; 72 | var text = mainBox.innerHTML.trim(), 73 | nonce = nacl.randomBytes(9), 74 | nonce24 = makeNonce24(nonce), 75 | cipherStr = myezLock + '//////' + nacl.util.encodeBase64(concatUi8([[128],nonce,PLencrypt(text,nonce24,myLock,true)])).replace(/=+$/,''); //this includes compression 76 | mainBox.textContent = ''; 77 | 78 | var prefaceMsg = document.createElement('div'); 79 | prefaceMsg.innerHTML = "

The gibberish link below contains a message from me that has been encrypted with PassLok, a free app that you can get at the Chrome and Firefox web stores. There is also PassLok Privacy, PassLok for Email, and PassLok Universal at the same stores, plus the standalone PassLok web app at https://passlok.com/app.

To decrypt it, install PassLok, reload this page. You will be asked to supply a Master Key, which will not be stored or sent anywhere. You must remember your secret Key, but you can change it later if you want. Then paste in the block of gibberish below. When asked whether to accept my new Lock, go ahead and give it my name, and click OK. Then click Decrypt.

"; 80 | if(emailMode.checked){ 81 | var initialTag = document.createElement('pre'), 82 | invBody = document.createElement('pre'), 83 | finalTag = document.createElement('pre'); 84 | initialTag.textContent = "\r\n\r\n----------begin invitation message encrypted with PassLok--------==\r\n\r\n"; 85 | invBody.textContent = cipherStr.match(/.{1,80}/g).join("\r\n"); 86 | finalTag.textContent = "\r\n\r\n==---------end invitation message encrypted with PassLok-----------"; 87 | mainBox.appendChild(prefaceMsg); 88 | mainBox.appendChild(initialTag); 89 | mainBox.appendChild(invBody); 90 | mainBox.appendChild(finalTag); 91 | }else{ 92 | prefaceMsg.textContent = prefaceMsg.textContent + "\r\n\r\nhttps://passlok.com/app#PL24inv==" + cipherStr + "==PL24inv"; 93 | mainBox.appendChild(prefaceMsg) 94 | } 95 | updateButtons(); 96 | mainMsg.textContent = "Invitation created. Invitations are "; 97 | var blinker = document.createElement('span'); 98 | blinker.className = "blink"; 99 | blinker.textContent = "NOT SECURELY ENCRYPTED"; 100 | mainMsg.appendChild(blinker); 101 | if(emailMode.checked) sendMail() 102 | }else{ 103 | mainMsg.textContent = "Write something not confidential to add to the invitation, then click Invite again" 104 | } 105 | } 106 | 107 | //displays QR code with sender's Lock and main URL 108 | function makeQRcode(){ 109 | if(!refreshKey()) return; 110 | qrcode.makeCode('passlok.com/app#' + myLockStr); 111 | mainMsg.textContent = "This QR code contains your Lock. Tap it to hide"; 112 | qrcodeImg.lastChild.style.margin = "auto"; //center QR code 113 | qrcodeImg.style.display = 'block' 114 | } 115 | 116 | //calls texting app 117 | function sendSMS(){ 118 | if(learnMode.checked){ 119 | var reply = confirm("The default texting app will now open. You need to have copied your short encrypted message to the clipboard before doing this, if you want to send one. This only works on smartphones. Cancel if this is not what you want."); 120 | if(!reply) return 121 | } 122 | if(sendSMSBtn.textContent == 'Save'){ 123 | saveFiles() 124 | }else{ 125 | selectMain(); 126 | window.open("SMS:","_parent") 127 | } 128 | } 129 | 130 | //decrypts a chat invite if found, then opens chat screen, otherwise makes one 131 | function Chat(){ 132 | var text = mainBox.innerHTML.trim(); 133 | 134 | if(text.match('==') && text.split('==')[0].slice(-4) == 'chat'){ //there is already a chat invitation, so open it 135 | var msg = text.split('==')[1], 136 | type = msg.charAt(0); 137 | unlock(type,msg,lockBox.innerHTML.replace(/\n/g,'
').trim()); 138 | return 139 | } 140 | 141 | var listArray = lockBox.innerHTML.replace(/\n/g,'
').trim().split('
').filter(Boolean); 142 | if(learnMode.checked){ 143 | var reply = confirm("A special encrypted item will be made, inviting the selected recipients to a secure chat session. Cancel if this is not what you want."); 144 | if(!reply) return 145 | } 146 | 147 | if(listArray.length == 0 || (listArray.length == 1 && listArray[0] == 'myself')){ 148 | mainMsg.textContent = 'Please select those invited to chat'; 149 | return 150 | } 151 | if(longMode.checked) listArray = listArray.concat('myself'); //make sure 'myself' is on the list, unless it's not a multi-recipient message 152 | listArray = listArray.filter(function(elem, pos, self) {return self.indexOf(elem) == pos;}); //remove duplicates and nulls 153 | listArray = listArray.filter(function(n){return n}); 154 | lockBox.innerText = listArray.join('\n'); 155 | openClose('shadow'); 156 | openClose('chatDialog'); //stop to get chat type 157 | chatDate.value = mainBox.textContent.trim().slice(0,43); 158 | } 159 | 160 | //continues making a chat invite after the user has chosen the chat type 161 | function makeChat(){ 162 | closeBox(); 163 | if(dataChat.checked){ //A to C for Muaz Khan's WebRTC chat, D for Jitsi 164 | var type = 'A' 165 | }else if (audioChat.checked){ 166 | var type = 'B' 167 | }else if (videoChat.checked){ 168 | var type = 'C' 169 | }else{ 170 | var type = 'D' 171 | } 172 | var date = chatDate.value.slice(0,43); //can't do encodeURI here because this will be decrypted by decryptList, which doesn't expect it 173 | if(date.trim() == '') date = 'noDate'; 174 | while(date.length < 43) date += ' '; 175 | var password = nacl.util.encodeBase64(nacl.randomBytes(32)).replace(/=+$/,''), 176 | chatRoom = makeChatRoom(); 177 | lock(lockBox.innerHTML.replace(/\n/g,'
').replace(/
$/,"").trim(),date + type + chatRoom + '?' + password); //date msg + info to be sent to chat page 178 | setTimeout(function(){ 179 | if(emailMode.checked) sendMail() 180 | },50) 181 | } 182 | 183 | //makes a mostly anonymous chatRoom name from four words in the wordlist 184 | function makeChatRoom(){ 185 | var wordlist = wordListExp.toString().slice(1,-2).split('|'), 186 | name = ''; 187 | for(var i = 0; i < 4; i++){ 188 | name += capitalizeFirstLetter(replaceVariants(wordlist[randomIndex()])) 189 | } 190 | return name 191 | } 192 | 193 | //capitalizes first letter, the better to blend into Jitsi 194 | function capitalizeFirstLetter(str) { 195 | return str[0].toUpperCase() + str.slice(1); 196 | } 197 | 198 | //returns a random index for wordlist 199 | function randomIndex(){ 200 | return Math.floor(Math.random()*wordLength) 201 | } 202 | 203 | //detects if there is a chat link in the main box, and opens the Chat window 204 | function openChat(){ 205 | var typetoken = mainBox.textContent.trim(); 206 | if (typetoken.slice(-44,-43) == '?' && !typetoken.slice(43).match(/[^A-Za-z0-9+\/?]/)){ //chat data detected, so open chat 207 | mainBox.textContent = ''; 208 | var date = typetoken.slice(0,43).trim(), //the first 43 characters are for the date and time etc. 209 | chatToken = decodeURI(typetoken.slice(43)); 210 | if(date != 'noDate'){ 211 | var msgStart = "This chat invitation says:\n\n " + date + " \n\n" 212 | }else{ 213 | var msgStart = "" 214 | } 215 | var reply = confirm(msgStart + "If you go ahead, the chat session will open now.\nWARNING: this involves going online, which might give away your location. If you cancel, a link for the chat will be made."); 216 | if(!reply){ 217 | var chatLink = document.createElement('a'); 218 | chatLink.href = 'https://passlok.com/chat/chat.html#' + chatToken; 219 | chatLink.textContent = 'Right-click to open the chat'; 220 | mainBox.textContent = ''; 221 | mainBox.appendChild(chatLink); 222 | return 223 | } 224 | if(isSafari || isIE || isiOS){ 225 | mainMsg.textContent = 'Sorry, but chat is not yet supported by your browser or OS'; 226 | return 227 | } 228 | main2chat(chatToken) 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /js-head/textstego.js: -------------------------------------------------------------------------------- 1 | //The following code is to convert the contents of main box into fake text, and back. This can be useful against email scanners. 2 | 3 | if (typeof code == 'undefined'){ //default text for base64 to words conversion, global variable 4 | var defaultCoverText = "The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your program, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free softwares (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions."; 5 | var coverText = defaultCoverText; 6 | coverText = coverText.replace(/ +/g," ").replace(/ \n/g,"\n") //remove multiple spaces, space before linefeed 7 | } 8 | 9 | //returns true if pure base64 10 | function isBase64(string){ 11 | return !string.match(/[^a-zA-Z0-9+\/]/) 12 | } 13 | 14 | //to remove duplicates in array much more quickly than with array.filter(function(elem, pos, self) {return self.indexOf(elem) == pos;}); 15 | function uniq(a) { 16 | var seen = {}; 17 | return a.filter(function(item) { 18 | return seen.hasOwnProperty(item) ? false : (seen[item] = true); 19 | }) 20 | } 21 | 22 | //This function checks for legal (base64 after tags are removed) PassLok output and calls the currently selected encoder. Otherwise it calls the decoder 23 | function textStego(){ 24 | var text = mainBox.innerHTML.replace(/&[^;]+;/g,'').replace(/(.*?)\/a>$/,'').replace(/
/g,'').replace(/[\r\n]/g,''); 25 | if(text.match('==')) text = text.split('==')[1].replace(/-/g,''); //remove tags and dashes 26 | text = text.replace(/<(.*?)>/gi,''); 27 | if(text == ""){ 28 | mainMsg.textContent = 'No text in the box'; 29 | return 30 | } 31 | 32 | blinkMsg(mainMsg); 33 | setTimeout(function(){ //the rest after a 20 ms delay 34 | if(isBase64(text)){ //pure base64 found: encode it 35 | if(sentenceMode.checked){ 36 | toPhrases(text) 37 | }else if(wordMode.checked){ 38 | toWords(text) 39 | }else if(spaceMode.checked){ 40 | toSpaces(text) 41 | }else if(invisibleMode.checked){ 42 | toInvisible(text) 43 | }else{ 44 | toLetters(text) 45 | } 46 | }else{ //no legal item found: try to decode 47 | if(text.match('\u00ad')){ //detect soft hyphen 48 | fromInvisible(text); 49 | mainMsg.textContent = 'Message extracted from Invisible encoding' 50 | }else if(text.match('\u200c')){ //detect zero width non-joiner 51 | fromSpaces(text); 52 | mainMsg.textContent = 'Message extracted from Spaces encoding' 53 | }else if(text.match('\u2004') || text.match('\u2005') || text.match('\u2006')){ //detect special characters used in Letters encoding 54 | fromLetters(text); 55 | mainMsg.textContent = 'Message extracted from Letters' 56 | }else if(text.match(':') != null){ //detect colons and if there are any invoke Sentences decoder 57 | fromPhrases(text); 58 | mainMsg.textContent = 'Message extracted from Sentences' 59 | }else{ //no special characters detected: words decoder 60 | fromWords(text); 61 | mainMsg.textContent = 'Message extracted from Words encoding' 62 | } 63 | } 64 | charsLeft() 65 | },20); //end of timeout 66 | } 67 | 68 | //makes array with words of 8 different lengths taken from the cover 69 | function makeWordMatrix(cover){ 70 | var wordArray = uniq(cover.replace(/[\.,!?\*;:{}_()\[\]"…“”‘’„‚«»‹›—–―¿¡]|_/g, "").replace(/[\s\n]+/g,' ').slice(0,-1).split(/ /)), 71 | bins = [[],[],[],[],[],[],[],[]]; 72 | for(var i = 0; i < wordArray.length; i++){ 73 | var lengthMod8 = wordArray[i].length % 8; 74 | bins[lengthMod8] = bins[lengthMod8].concat(wordArray[i]) 75 | } 76 | for(var i = 0; i < 8; i++){ 77 | if(bins[i].length == 0){ 78 | mainMsg.textContent = 'Please use a Cover text with more variation'; 79 | return 80 | } 81 | } 82 | return bins 83 | } 84 | 85 | //words encoder: each character is given a 2-digit base9 code, and replaced by two words of those lengths 86 | function toWords(text){ 87 | if(learnMode.checked){ 88 | var reply = confirm("The contents of the main box will be replaced with encoded text which contains the original text as words of varying length. Cancel if this is not what you want."); 89 | if(!reply) return 90 | } 91 | var wordArray = makeWordMatrix(coverText), 92 | out = new Array(text.length * 2); 93 | 94 | //now get a 2-digit code for each character in the text; each digit goes from 0 to 7, so we have 64 possibilities to encode base64 95 | for(var i = 0; i < text.length; i++){ 96 | var index = base64.indexOf(text[i]), 97 | word1Choices = wordArray[Math.floor(index / 8)], 98 | word2Choices = wordArray[index % 8]; 99 | out[2*i] = word1Choices[Math.floor(Math.random()*word1Choices.length)] + randompunct(); 100 | out[2*i+1] = word2Choices[Math.floor(Math.random()*word2Choices.length)] + randompunct() 101 | } 102 | var outStr = out.join(''); 103 | 104 | //capitalize initial and after period, add final period. 105 | outStr = outStr.toLowerCase().replace(/[.][\s\n][a-z]/g,function(a){return a.toUpperCase();}); 106 | outStr = outStr.replace(/[a-z]/,function(a){return a.toUpperCase();}); 107 | outStr = outStr.slice(0,-1) + '.'; 108 | mainBox.textContent = outStr.trim(); 109 | mainMsg.textContent = 'Message encoded as words of varying length' 110 | } 111 | 112 | //words decoder. Takes groups of 2 words. Their lengths is the index of each characters, in base9 113 | function fromWords(text){ 114 | if(learnMode.checked){ 115 | var reply = confirm("The encoded text in the main box will be replaced with the original text from which it came. Cancel if this is not what you want."); 116 | if(!reply) return 117 | } 118 | text = text.replace(/ /g,'').replace(/[.,\n]/g,''); //remove extra spaces and punctuation 119 | var textArray = text.split(/ /), 120 | out = new Array(textArray.length / 2); 121 | for(var i = 0; i < textArray.length; i=i+2){ 122 | var index1 = textArray[i].trim().length % 8, 123 | index2 = textArray[i+1].trim().length % 8; 124 | out[i/2] = base64[index1 * 8 + index2] 125 | } 126 | mainBox.textContent = out.join('').trim() 127 | } 128 | 129 | //This is to generate random periods, commans and newlines, per the percentage brackets below, plus spaces when appropriate 130 | function randompunct(){ 131 | var percent = Math.ceil(Math.random() * 100); 132 | if (percent < 8) { 133 | return ". " 134 | } else if(percent < 14) { 135 | return ", " 136 | } else { 137 | return " " 138 | } 139 | } 140 | 141 | //the following functions are to hide text into a text cover, as binary double spaces. It needs a little under 6 cover words for each base64 character, 36 for non-ASCII 142 | function spacesEncoder(bin){ 143 | var textsplit = coverText.split(" "), 144 | stegospace = [' ', ' \u200c'], 145 | spaces = textsplit.length - 1, 146 | turns = 0; 147 | if (spaces < bin.length){ //repeat cover text if too short 148 | while (spaces < bin.length){ 149 | textsplit = textsplit.concat(coverText.split(" ")); 150 | spaces = textsplit.length - 1; 151 | turns++ 152 | } 153 | mainMsg.textContent = 'Message encoded into spaces of this text. It was repeated ' + turns + ' times.' 154 | }else{ 155 | mainMsg.textContent = 'Message encoded into spaces of this text' 156 | } 157 | textsplit = textsplit.slice(0,bin.length + 1); //take a sufficiently long piece of cover text 158 | var newtext = new Array(bin.length + 1); 159 | newtext[0] = textsplit[0]; 160 | for(var i = 0; i < bin.length; i++){ 161 | newtext[i+1] = stegospace[bin[i]] + textsplit[i+1] 162 | } 163 | return newtext.join('') 164 | } 165 | 166 | function spacesDecoder(text){ 167 | var textsplit = text.split(" "), 168 | bin = new Array(textsplit.length - 1); 169 | 170 | for(var i = 0; i < textsplit.length - 1; i++){ 171 | if (textsplit[i + 1].match('\u200c')){ 172 | bin[i] = 1 173 | }else{ 174 | bin[i] = 0 175 | } 176 | } 177 | return bin 178 | } 179 | 180 | //retrieves base64 string from binary array. No error checking 181 | function fromBin(input){ 182 | var length = input.length - (input.length % 6), 183 | output = new Array(length / 6), 184 | index = 0; 185 | 186 | for(var i = 0; i < length; i = i+6) { 187 | index = 0; 188 | for(var j = 0; j < 6; j++){ 189 | index = 2 * index + input[i+j] 190 | } 191 | output[i / 6] = base64.charAt(index) 192 | } 193 | return output.join('') 194 | } 195 | 196 | //makes the binary equivalent (array) of a base64 string. No error checking 197 | function toBin(input){ 198 | var output = new Array(input.length * 6), 199 | code = 0, 200 | digit = 0, 201 | divider = 32; 202 | 203 | for(var i = 0; i < input.length; i++) { 204 | code = base64.indexOf(input.charAt(i)); 205 | divider = 32; 206 | for(var j = 0; j < 5; j++){ 207 | digit = code >= divider ? 1 : 0; 208 | code -= digit * divider; 209 | divider = divider / 2; 210 | output[6 * i + j] = digit 211 | } 212 | output[6 * i + 5] = code; 213 | } 214 | return output 215 | } 216 | 217 | function toSpaces(text) { 218 | if(learnMode.checked){ 219 | var reply = confirm("The contents of the main box will be replaced with encoded text which contains the original text as formatted spacing. Cancel if this is not what you want."); 220 | if(!reply) return 221 | } 222 | mainBox.textContent = spacesEncoder(toBin(text)) 223 | } 224 | 225 | function fromSpaces(text) { 226 | if(learnMode.checked){ 227 | var reply = confirm("The encoded text in the main box will be replaced with the original text from which it came. Cancel if this is not what you want."); 228 | if(!reply) return 229 | } 230 | mainBox.textContent = fromBin(spacesDecoder(text)).replace(/\x00/g,'').trim(); //take out nulls, in case text was added to finish the last sentence. 231 | } 232 | 233 | //the following functions are to hide text between two letters, as a binary string made of invisible characters. 234 | function toInvisible(text) { 235 | if(learnMode.checked){ 236 | var reply = confirm("The contents of the main box will be invisibly encoded (to a human) at the end of a sentence. Cancel if this is not what you want."); 237 | if(!reply) return 238 | } 239 | mainBox.textContent = 'Dear friend,' + invisibleEncoder(toBin(text)) + '\r\n\r\nBody of the message.' 240 | } 241 | 242 | function fromInvisible(text) { 243 | if(learnMode.checked){ 244 | var reply = confirm("The encoded text in the main box will be replaced with the original text from which it came. Cancel if this is not what you want."); 245 | if(!reply) return 246 | } 247 | mainBox.textContent = fromBin(invisibleDecoder(text)).trim() 248 | } 249 | 250 | function invisibleEncoder(bin){ 251 | var stegospace = ['\u00ad', '\u200c'], 252 | newtext = new Array(bin.length); 253 | 254 | for(var i = 0; i < bin.length; i++){ 255 | newtext[i+1] = stegospace[bin[i]] 256 | } 257 | mainMsg.textContent = 'Message invisibly encoded at the end of the introduction in the box. Edit as needed'; 258 | return newtext.join('') 259 | } 260 | 261 | function invisibleDecoder(text){ 262 | var binStr = text.replace(/\u00ad/g,'0').replace(/\u200c/g,'1'); 263 | binStr = binStr.match(/[01]+/)[0]; //keep binary part 264 | var length = binStr.length, 265 | bin = new Array(length); 266 | for(var i = 0; i < length; i++){ 267 | if (binStr.charAt(i) == '0'){ 268 | bin[i] = 0 269 | }else{ 270 | bin[i] = 1 271 | } 272 | } 273 | return bin 274 | } 275 | 276 | //makes phrase matrix for a given cover text, where sentences are catalogued by length mod 11 277 | function makePhraseMatrix(cover){ 278 | var phraseArray = cover.replace(/["…“”‘’„‚«»‹›¿¡\_\*\^\(\)]+/g,'').slice(0,-1).split(/[.,;:?!][ \-\—]/), 279 | bins = [[],[],[],[],[],[],[],[],[],[],[]]; 280 | for(var i = 0; i < phraseArray.length; i++){ 281 | var lengthMod12 = phraseArray[i].length % 11; 282 | bins[lengthMod12] = bins[lengthMod12].concat(phraseArray[i]) 283 | } 284 | for(var i = 0; i < 11; i++){ 285 | if(bins[i].length == 0){ 286 | mainMsg.textContent = 'Please use a Cover text with more variation.'; 287 | return 288 | } 289 | } 290 | return bins 291 | } 292 | 293 | //encodes text as sentences of varying length (11 different values) 294 | function toPhrases(text){ 295 | if(learnMode.checked){ 296 | var reply = confirm("The contents of the main box will be replaced with encoded text which contains the original text as sentences of varying length. Cancel if this is not what you want."); 297 | if(!reply) return 298 | } 299 | var phraseArray = makePhraseMatrix(coverText), 300 | punct = '.,;:!?', 301 | outArray = new Array(text.length); 302 | 303 | //now split the base64-encoded string into a base11 code to select sentence length, and a base6 code to select punctuation 304 | for(var i = 0; i < text.length; i++){ 305 | var index = base64.indexOf(text[i]), 306 | phraseChoices = phraseArray[index % 11]; 307 | outArray[i] = phraseChoices[Math.floor(Math.random()*phraseChoices.length)] + punct[Math.floor(index/11)] + ' ' 308 | } 309 | var out = outArray.join(''); 310 | out = out.replace(/[.!?][\s\n][a-z]/g,function(a){return a.toUpperCase();}).replace(/[,;:][\s\n][A-Z]/g,function(a){return a.toLowerCase();}).trim(); //capitalization 311 | out = out.charAt(0).toUpperCase() + out.slice(1); 312 | mainBox.textContent = out.trim(); 313 | mainMsg.textContent = 'Message encoded as sentences of varying length' 314 | } 315 | 316 | //decodes text encoded as sentences of varying length 317 | function fromPhrases(text){ 318 | if(learnMode.checked){ 319 | var reply = confirm("The encoded text in the main box will be replaced with the original text from which it came. Cancel if this is not what you want."); 320 | if(!reply) return 321 | } 322 | text = text.replace(/ /g,'').replace(/\n/g,'').trim() + ' '; 323 | var textArray = text.split(/[.,;:!?] /g).slice(0,-1), 324 | punctArray = text.match(/[.,;:!?]/g), 325 | punct = ".,;:!?", 326 | outArray = new Array(textArray.length); 327 | 328 | for(var i = 0; i < textArray.length; i++){ 329 | var index11 = textArray[i].trim().length % 11, 330 | index6 = punct.indexOf(punctArray[i]); 331 | outArray[i] = base64[index6 * 11 + index11] 332 | } 333 | mainBox.textContent = outArray.join('').trim() 334 | } 335 | 336 | //Letters encoding is based on code at: http://www.irongeek.com/i.php?page=security/unicode-steganography-homoglyph-encoder, by Adrian Crenshaw, 2013 337 | //first the object containing the Unicode character substitutions 338 | var charMappings = {//Aa 339 | "a":"0", "a0":"a", "\u0430":"1", "a1":"\u0430", 340 | "A":"0", "A0":"A", "\u0391":"1", "A1":"\u0391", 341 | //Bb 342 | "B":"0", "B0":"B", "\u0392":"1", "B1":"\u0392", 343 | //Cc 344 | "c":"0", "c0":"c", "\u0441":"1", "c1":"\u0441", 345 | "C":"0", "C0":"C", "\u0421":"1", "C1":"\u0421", 346 | //Ee 347 | "e":"0", "e0":"e", "\u0435":"1", "e1":"\u0435", 348 | "E":"0", "E0":"E", "\u0415":"1", "E1":"\u0415", 349 | //Gg 350 | "g":"0", "g0":"g", "\u0261":"1", "g1":"\u0261", 351 | //Hh 352 | "H":"0", "H0":"H", "\u041D":"1", "H1":"\u041D", 353 | //Ii 354 | "i":"0", "i0":"i", "\u0456":"1", "i1":"\u0456", 355 | "I":"0", "I0":"I", "\u0406":"1", "I1":"\u0406", 356 | //Jj 357 | "j":"0", "j0":"j", "\u03F3":"1", "j1":"\u03F3", 358 | "J":"0", "J0":"J", "\u0408":"1", "J1":"\u0408", 359 | //Kk 360 | "K":"0", "K0":"K", "\u039A":"1", "K1":"\u039A", 361 | //Mm 362 | "M":"0", "M0":"M", "\u039C":"1", "M1":"\u039C", 363 | //Nn 364 | "N":"0", "N0":"N", "\u039D":"1", "N1":"\u039D", 365 | //Oo 366 | "o":"0", "o0":"o", "\u03BF":"1", "o1":"\u03BF", 367 | "O":"0", "O0":"O", "\u039F":"1", "O1":"\u039F", 368 | //Pp 369 | "p":"0", "p0":"p", "\u0440":"1", "p1":"\u0440", 370 | "P":"0", "P0":"P", "\u03A1":"1", "P1":"\u03A1", 371 | //Ss 372 | "s":"0", "s0":"s", "\u0455":"1", "s1":"\u0455", 373 | "S":"0", "S0":"S", "\u0405":"1", "S1":"\u0405", 374 | //Tt 375 | "T":"0", "T0":"T", "\u03A4":"1", "T1":"\u03A4", 376 | //Xx 377 | "x":"0", "x0":"x", "\u0445":"1", "x1":"\u0445", 378 | "X":"0", "X0":"X", "\u03A7":"1", "X1":"\u03A7", 379 | //Yy 380 | "y":"0", "y0":"y", "\u0443":"1", "y1":"\u0443", 381 | "Y":"0", "Y0":"Y", "\u03A5":"1", "Y1":"\u03A5", 382 | //Zz 383 | "Z":"0", "Z0":"Z", "\u0396":"1", "Z1":"\u0396", 384 | //Spaces 385 | " ":"000", 386 | " 000":" ", 387 | "\u2004":"001", 388 | " 001":"\u2004", 389 | "\u2005":"010", 390 | " 010":"\u2005", 391 | "\u2006":"011", 392 | " 011":"\u2006", 393 | "\u2008":"100", 394 | " 100":"\u2008", 395 | "\u2009":"101", 396 | " 101":"\u2009", 397 | "\u202f":"110", 398 | " 110":"\u202F", 399 | "\u205F":"111", 400 | " 111":"\u205F" 401 | }; 402 | 403 | //counts the number of encodable bits in the cover text 404 | function encodableBits(cover){ 405 | var bitcount = 0; 406 | for (var i = 0; i < cover.length; i++){ 407 | if (charMappings[cover[i]] !== undefined){ 408 | bitcount = bitcount + charMappings[cover[i]].length 409 | } 410 | } 411 | return bitcount 412 | } 413 | 414 | //encodes text as special letters and spaces in the cover text, which replace the original ones 415 | function toLetters(text){ 416 | if(learnMode.checked){ 417 | var reply = confirm("The contents of the main box will be replaced with encoded text which contains the original text as formatted special characters and spaces. Cancel if this is not what you want."); 418 | if(!reply) return 419 | } 420 | var textBin = toBin(text).join(''), //string containing 1's and 0's 421 | cover = coverText, 422 | capacity = encodableBits(cover); 423 | if (capacity < textBin.length){ //repeat the cover text if it's too short 424 | var turns = Math.ceil(textBin.length / capacity), 425 | index = 0; 426 | while (index < turns){ 427 | cover += ' ' + coverText; 428 | index++ 429 | } 430 | mainMsg.textContent = 'Message encoded into letters of this text. It was repeated ' + turns + ' times.' 431 | } 432 | var finalString = "", 433 | bitsIndex = 0, 434 | i = 0, 435 | doneBits = ''; 436 | while(doneBits.length < textBin.length){ 437 | if (charMappings[cover[i]] === undefined){ 438 | finalString = finalString + cover[i] 439 | }else{ 440 | var tempBits = textBin.substring(bitsIndex,bitsIndex + charMappings[cover[i]].length); 441 | while(tempBits.length < charMappings[cover[i]].length){tempBits = tempBits + "0";} //Got to pad it out 442 | finalString += charMappings[cover[i] + tempBits]; 443 | bitsIndex += charMappings[cover[i]].length; 444 | doneBits += tempBits 445 | } 446 | i++ 447 | } 448 | mainBox.textContent = finalString + '.'; //period needed because there could be spaces at the end 449 | mainMsg.textContent = 'Message encoded into letters of this text.' 450 | } 451 | 452 | //gets the original text from Letters encoded text 453 | function fromLetters(text){ 454 | if(learnMode.checked){ 455 | var reply = confirm("The encoded text in the main box will be replaced with the original text from which it came. Cancel if this is not what you want."); 456 | if(!reply) return 457 | } 458 | var bintemp = [], 459 | tempchar = ""; 460 | for (var i = 0; i < text.length; i++){ 461 | if (charMappings[text[i]] === undefined ){ 462 | }else{ 463 | tempchar = charMappings[text[i]]; 464 | bintemp.push(tempchar) 465 | } 466 | } 467 | var binStr = bintemp.join(''), 468 | bin = new Array(binStr.length); 469 | for(var i = 0; i < binStr.length; i++) bin[i] = parseInt(binStr.charAt(i)); 470 | mainBox.textContent = fromBin(bin.slice(0,bin.length-(bin.length % 6))) 471 | } 472 | 473 | //this one is to display the cover text or change it as requested 474 | function newCover(string){ 475 | coverText = addSpaces(string.replace(/[\n\s-]+/g,' ')); //newlines, dashes, and multiple spaces do weird things, so remove them 476 | mainMsg.textContent = 'Cover text changed' 477 | } 478 | 479 | //adds spaces that can be encoded if Chinese, Korean, or Japanese 480 | function addSpaces(string){ 481 | if (string.match(/[\u3400-\u9FBF]/) != null) string = string.split('').join(' ').replace(/\s+/g, ' '); 482 | return string 483 | } 484 | -------------------------------------------------------------------------------- /js-opensrc/ed2curve.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ed2curve: convert Ed25519 signing key pair into Curve25519 3 | * key pair suitable for Diffie-Hellman key exchange. 4 | * 5 | * Written by Dmitry Chestnykh in 2014. Public domain. 6 | */ 7 | /* jshint newcap: false */ 8 | (function(root, f) { 9 | 'use strict'; 10 | if (typeof module !== 'undefined' && module.exports) module.exports = f(require('tweetnacl/nacl-fast')); 11 | else root.ed2curve = f(root.nacl); 12 | }(this, function(nacl) { 13 | 'use strict'; 14 | if (!nacl) throw new Error('tweetnacl not loaded'); 15 | 16 | // -- Operations copied from TweetNaCl.js. -- 17 | 18 | var gf = function(init) { 19 | var i, r = new Float64Array(16); 20 | if (init) for (i = 0; i < init.length; i++) r[i] = init[i]; 21 | return r; 22 | }; 23 | 24 | var gf0 = gf(), 25 | gf1 = gf([1]), 26 | D = gf([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203]), 27 | I = gf([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83]); 28 | 29 | 30 | 31 | function car25519(o) { 32 | var c; 33 | var i; 34 | for (i = 0; i < 16; i++) { 35 | o[i] += 65536; 36 | c = Math.floor(o[i] / 65536); 37 | o[(i+1)*(i<15?1:0)] += c - 1 + 37 * (c-1) * (i===15?1:0); 38 | o[i] -= (c * 65536); 39 | } 40 | } 41 | 42 | function sel25519(p, q, b) { 43 | var t, c = ~(b-1); 44 | for (var i = 0; i < 16; i++) { 45 | t = c & (p[i] ^ q[i]); 46 | p[i] ^= t; 47 | q[i] ^= t; 48 | } 49 | } 50 | 51 | function unpack25519(o, n) { 52 | var i; 53 | for (i = 0; i < 16; i++) o[i] = n[2*i] + (n[2*i+1] << 8); 54 | o[15] &= 0x7fff; 55 | } 56 | 57 | // addition 58 | function A(o, a, b) { 59 | var i; 60 | for (i = 0; i < 16; i++) o[i] = (a[i] + b[i])|0; 61 | } 62 | 63 | // subtraction 64 | function Z(o, a, b) { 65 | var i; 66 | for (i = 0; i < 16; i++) o[i] = (a[i] - b[i])|0; 67 | } 68 | 69 | // multiplication 70 | function M(o, a, b) { 71 | var i, j, t = new Float64Array(31); 72 | for (i = 0; i < 31; i++) t[i] = 0; 73 | for (i = 0; i < 16; i++) { 74 | for (j = 0; j < 16; j++) { 75 | t[i+j] += a[i] * b[j]; 76 | } 77 | } 78 | for (i = 0; i < 15; i++) { 79 | t[i] += 38 * t[i+16]; 80 | } 81 | for (i = 0; i < 16; i++) o[i] = t[i]; 82 | car25519(o); 83 | car25519(o); 84 | } 85 | 86 | // squaring 87 | function S(o, a) { 88 | M(o, a, a); 89 | } 90 | 91 | // inversion 92 | function inv25519(o, i) { 93 | var c = gf(); 94 | var a; 95 | for (a = 0; a < 16; a++) c[a] = i[a]; 96 | for (a = 253; a >= 0; a--) { 97 | S(c, c); 98 | if(a !== 2 && a !== 4) M(c, c, i); 99 | } 100 | for (a = 0; a < 16; a++) o[a] = c[a]; 101 | } 102 | 103 | function pack25519(o, n) { 104 | var i, j, b; 105 | var m = gf(), t = gf(); 106 | for (i = 0; i < 16; i++) t[i] = n[i]; 107 | car25519(t); 108 | car25519(t); 109 | car25519(t); 110 | for (j = 0; j < 2; j++) { 111 | m[0] = t[0] - 0xffed; 112 | for (i = 1; i < 15; i++) { 113 | m[i] = t[i] - 0xffff - ((m[i-1]>>16) & 1); 114 | m[i-1] &= 0xffff; 115 | } 116 | m[15] = t[15] - 0x7fff - ((m[14]>>16) & 1); 117 | b = (m[15]>>16) & 1; 118 | m[14] &= 0xffff; 119 | sel25519(t, m, 1-b); 120 | } 121 | for (i = 0; i < 16; i++) { 122 | o[2*i] = t[i] & 0xff; 123 | o[2*i+1] = t[i] >> 8; 124 | } 125 | } 126 | 127 | 128 | function par25519(a) { 129 | var d = new Uint8Array(32); 130 | pack25519(d, a); 131 | return d[0] & 1; 132 | } 133 | 134 | 135 | 136 | function vn(x, xi, y, yi, n) { 137 | var i, d = 0; 138 | for (i = 0; i < n; i++) d |= x[xi + i] ^ y[yi + i]; 139 | return (1 & ((d - 1) >>> 8)) - 1; 140 | } 141 | 142 | 143 | function crypto_verify_32(x, xi, y, yi) { 144 | return vn(x, xi, y, yi, 32); 145 | } 146 | 147 | function neq25519(a, b) { 148 | var c = new Uint8Array(32), d = new Uint8Array(32); 149 | pack25519(c, a); 150 | pack25519(d, b); 151 | return crypto_verify_32(c, 0, d, 0); 152 | } 153 | 154 | 155 | function pow2523(o, i) { 156 | var c = gf(); 157 | var a; 158 | for (a = 0; a < 16; a++) c[a] = i[a]; 159 | for (a = 250; a >= 0; a--) { 160 | S(c, c); 161 | if (a !== 1) M(c, c, i); 162 | } 163 | for (a = 0; a < 16; a++) o[a] = c[a]; 164 | } 165 | 166 | function set25519(r, a) { 167 | var i; 168 | for (i = 0; i < 16; i++) r[i] = a[i] | 0; 169 | } 170 | 171 | function unpackneg(r, p) { 172 | var t = gf(), chk = gf(), num = gf(), 173 | den = gf(), den2 = gf(), den4 = gf(), 174 | den6 = gf(); 175 | 176 | set25519(r[2], gf1); 177 | unpack25519(r[1], p); 178 | S(num, r[1]); 179 | M(den, num, D); 180 | Z(num, num, r[2]); 181 | A(den, r[2], den); 182 | 183 | S(den2, den); 184 | S(den4, den2); 185 | M(den6, den4, den2); 186 | M(t, den6, num); 187 | M(t, t, den); 188 | 189 | pow2523(t, t); 190 | M(t, t, num); 191 | M(t, t, den); 192 | M(t, t, den); 193 | M(r[0], t, den); 194 | 195 | S(chk, r[0]); 196 | M(chk, chk, den); 197 | if (neq25519(chk, num)) M(r[0], r[0], I); 198 | 199 | S(chk, r[0]); 200 | M(chk, chk, den); 201 | if (neq25519(chk, num)) return -1; 202 | 203 | if (par25519(r[0]) === (p[31] >> 7)) Z(r[0], gf0, r[0]); 204 | 205 | M(r[3], r[0], r[1]); 206 | return 0; 207 | } 208 | 209 | // ---- 210 | 211 | // Converts Ed25519 public key to Curve25519 public key. 212 | // montgomeryX = (edwardsY + 1)*inverse(1 - edwardsY) mod p 213 | function convertPublicKey(pk) { 214 | var z = new Uint8Array(32), 215 | q = [gf(), gf(), gf(), gf()], 216 | a = gf(), b = gf(); 217 | 218 | if (unpackneg(q, pk)) return null; // reject invalid key 219 | 220 | var y = q[1]; 221 | 222 | A(a, gf1, y); 223 | Z(b, gf1, y); 224 | inv25519(b, b); 225 | M(a, a, b); 226 | 227 | pack25519(z, a); 228 | return z; 229 | } 230 | 231 | // Converts Ed25519 secret key to Curve25519 secret key. 232 | function convertSecretKey(sk) { 233 | var d = new Uint8Array(64), o = new Uint8Array(32), i; 234 | nacl.lowlevel.crypto_hash(d, sk, 32); 235 | d[0] &= 248; 236 | d[31] &= 127; 237 | d[31] |= 64; 238 | for (i = 0; i < 32; i++) o[i] = d[i]; 239 | for (i = 0; i < 64; i++) d[i] = 0; 240 | return o; 241 | } 242 | 243 | function convertKeyPair(edKeyPair) { 244 | var publicKey = convertPublicKey(edKeyPair.publicKey); 245 | if (!publicKey) return null; 246 | return { 247 | publicKey: publicKey, 248 | secretKey: convertSecretKey(edKeyPair.secretKey) 249 | }; 250 | } 251 | 252 | return { 253 | convertPublicKey: convertPublicKey, 254 | convertSecretKey: convertSecretKey, 255 | convertKeyPair: convertKeyPair, 256 | }; 257 | 258 | })); 259 | -------------------------------------------------------------------------------- /js-opensrc/isaac.js: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------------------------------------- 2 | * Copyright (c) 2012 Yves-Marie K. Rinquin 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * ---------------------------------------------------------------------- 24 | * 25 | * ISAAC is a cryptographically secure pseudo-random number generator 26 | * (or CSPRNG for short) designed by Robert J. Jenkins Jr. in 1996 and 27 | * based on RC4. It is designed for speed and security. 28 | * 29 | * ISAAC's informations & analysis: 30 | * http://burtleburtle.net/bob/rand/isaac.html 31 | * ISAAC's implementation details: 32 | * http://burtleburtle.net/bob/rand/isaacafa.html 33 | * 34 | * ISAAC succesfully passed TestU01 35 | * 36 | * ---------------------------------------------------------------------- 37 | * 38 | * Usage: 39 | * var random_number = isaac.random(); 40 | * 41 | * Output: [ 0x00000000; 0xffffffff] 42 | * [-2147483648; 2147483647] 43 | * 44 | */ 45 | 46 | /* js string (ucs-2/utf16) to a 32-bit integer (utf-8 chars, little-endian) array */ 47 | String.prototype.toIntArray = function() { 48 | var w1, w2, u, r4 = [], r = [], i = 0; 49 | var s = this + '\0\0\0'; // pad string to avoid discarding last chars 50 | var l = s.length - 1; 51 | 52 | while(i < l) { 53 | w1 = s.charCodeAt(i++); 54 | w2 = s.charCodeAt(i+1); 55 | if (w1 < 0x0080) { 56 | // 0x0000 - 0x007f code point: basic ascii 57 | r4.push(w1); 58 | } else if(w1 < 0x0800) { 59 | // 0x0080 - 0x07ff code point 60 | r4.push(((w1 >>> 6) & 0x1f) | 0xc0); 61 | r4.push(((w1 >>> 0) & 0x3f) | 0x80); 62 | } else if((w1 & 0xf800) != 0xd800) { 63 | // 0x0800 - 0xd7ff / 0xe000 - 0xffff code point 64 | r4.push(((w1 >>> 12) & 0x0f) | 0xe0); 65 | r4.push(((w1 >>> 6) & 0x3f) | 0x80); 66 | r4.push(((w1 >>> 0) & 0x3f) | 0x80); 67 | } else if(((w1 & 0xfc00) == 0xd800) 68 | && ((w2 & 0xfc00) == 0xdc00)) { 69 | // 0xd800 - 0xdfff surrogate / 0x10ffff - 0x10000 code point 70 | u = ((w2 & 0x3f) | ((w1 & 0x3f) << 10)) + 0x10000; 71 | r4.push(((u >>> 18) & 0x07) | 0xf0); 72 | r4.push(((u >>> 12) & 0x3f) | 0x80); 73 | r4.push(((u >>> 6) & 0x3f) | 0x80); 74 | r4.push(((u >>> 0) & 0x3f) | 0x80); 75 | i++; 76 | } else { 77 | // invalid char 78 | } 79 | /* add integer (four utf-8 value) to array */ 80 | if(r4.length > 3) { 81 | // little endian 82 | r.push((r4.shift() << 0) | (r4.shift() << 8) | 83 | (r4.shift() << 16) | (r4.shift() << 24)); 84 | } 85 | } 86 | 87 | return r; 88 | } 89 | 90 | /* isaac module pattern */ 91 | var isaac = (function(){ 92 | 93 | /* private: internal states */ 94 | var m = Array(256), // internal memory 95 | acc = 0, // accumulator 96 | brs = 0, // last result 97 | cnt = 0, // counter 98 | r = Array(256), // result array 99 | gnt = 0; // generation counter 100 | 101 | seed(Math.random() * 0xffffffff); 102 | 103 | /* private: 32-bit integer safe adder */ 104 | function add(x, y) { 105 | var lsb = (x & 0xffff) + (y & 0xffff); 106 | var msb = (x >>> 16) + (y >>> 16) + (lsb >>> 16); 107 | return (msb << 16) | (lsb & 0xffff); 108 | } 109 | 110 | /* public: initialisation */ 111 | function reset() { 112 | acc = brs = cnt = 0; 113 | for(var i = 0; i < 256; ++i) 114 | m[i] = r[i] = 0; 115 | gnt = 0; 116 | } 117 | 118 | /* public: seeding function */ 119 | function seed(s) { 120 | var a, b, c, d, e, f, g, h, i; 121 | 122 | /* seeding the seeds of love */ 123 | a = b = c = d = 124 | e = f = g = h = 0x9e3779b9; /* the golden ratio */ 125 | 126 | if(s && typeof(s) === 'string') 127 | s = s.toIntArray(); 128 | 129 | if(s && typeof(s) === 'number') { 130 | s = [s]; 131 | } 132 | 133 | if(s instanceof Array) { 134 | reset(); 135 | for(i = 0; i < s.length; i++) 136 | r[i & 0xff] += (typeof(s[i]) === 'number') ? s[i] : 0; 137 | } 138 | 139 | /* private: seed mixer */ 140 | function seed_mix() { 141 | a ^= b << 11; d = add(d, a); b = add(b, c); 142 | b ^= c >>> 2; e = add(e, b); c = add(c, d); 143 | c ^= d << 8; f = add(f, c); d = add(d, e); 144 | d ^= e >>> 16; g = add(g, d); e = add(e, f); 145 | e ^= f << 10; h = add(h, e); f = add(f, g); 146 | f ^= g >>> 4; a = add(a, f); g = add(g, h); 147 | g ^= h << 8; b = add(b, g); h = add(h, a); 148 | h ^= a >>> 9; c = add(c, h); a = add(a, b); 149 | } 150 | 151 | for(i = 0; i < 4; i++) /* scramble it */ 152 | seed_mix(); 153 | 154 | for(i = 0; i < 256; i += 8) { 155 | if(s) { /* use all the information in the seed */ 156 | a = add(a, r[i + 0]); b = add(b, r[i + 1]); 157 | c = add(c, r[i + 2]); d = add(d, r[i + 3]); 158 | e = add(e, r[i + 4]); f = add(f, r[i + 5]); 159 | g = add(g, r[i + 6]); h = add(h, r[i + 7]); 160 | } 161 | seed_mix(); 162 | /* fill in m[] with messy stuff */ 163 | m[i + 0] = a; m[i + 1] = b; m[i + 2] = c; m[i + 3] = d; 164 | m[i + 4] = e; m[i + 5] = f; m[i + 6] = g; m[i + 7] = h; 165 | } 166 | if(s) { 167 | /* do a second pass to make all of the seed affect all of m[] */ 168 | for(i = 0; i < 256; i += 8) { 169 | a = add(a, m[i + 0]); b = add(b, m[i + 1]); 170 | c = add(c, m[i + 2]); d = add(d, m[i + 3]); 171 | e = add(e, m[i + 4]); f = add(f, m[i + 5]); 172 | g = add(g, m[i + 6]); h = add(h, m[i + 7]); 173 | seed_mix(); 174 | /* fill in m[] with messy stuff (again) */ 175 | m[i + 0] = a; m[i + 1] = b; m[i + 2] = c; m[i + 3] = d; 176 | m[i + 4] = e; m[i + 5] = f; m[i + 6] = g; m[i + 7] = h; 177 | } 178 | } 179 | 180 | prng(); /* fill in the first set of results */ 181 | gnt = 256; /* prepare to use the first set of results */; 182 | } 183 | 184 | /* public: isaac generator, n = number of run */ 185 | function prng(n){ 186 | var i, x, y; 187 | 188 | n = (n && typeof(n) === 'number') 189 | ? Math.abs(Math.floor(n)) : 1; 190 | 191 | while(n--) { 192 | cnt = add(cnt, 1); 193 | brs = add(brs, cnt); 194 | 195 | for(i = 0; i < 256; i++) { 196 | switch(i & 3) { 197 | case 0: acc ^= acc << 13; break; 198 | case 1: acc ^= acc >>> 6; break; 199 | case 2: acc ^= acc << 2; break; 200 | case 3: acc ^= acc >>> 16; break; 201 | } 202 | acc = add(m[(i + 128) & 0xff], acc); x = m[i]; 203 | m[i] = y = add(m[(x >>> 2) & 0xff], add(acc, brs)); 204 | r[i] = brs = add(m[(y >>> 10) & 0xff], x); 205 | } 206 | } 207 | } 208 | 209 | /* public: return a random number between */ 210 | function rand() { 211 | if(!gnt--) { 212 | prng(); gnt = 255; 213 | } 214 | return r[gnt]; 215 | } 216 | 217 | /* public: return internals in an object*/ 218 | function internals(){ 219 | return {a: acc, b: brs, c: cnt, m: m, r: r}; 220 | } 221 | 222 | /* return class object */ 223 | return { 224 | 'reset': reset, 225 | 'seed': seed, 226 | 'prng': prng, 227 | 'rand': rand, 228 | 'internals': internals 229 | }; 230 | })(); /* declare and execute */ 231 | 232 | /* public: output*/ 233 | isaac.random = function() { 234 | return 0.5 + this.rand() * 2.3283064365386963e-10; // 2^-32 235 | } -------------------------------------------------------------------------------- /js-opensrc/jssteg-1.0.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jsSteg Javascript Library v1.0 3 | * https://github.com/owencm/js-steg 4 | * Copyright 2014, Owen Campbell-Moore and other contributors 5 | * Released under the MIT license 6 | * 7 | * Usage: 8 | * jsSteg provides two public functions, getCoefficients and reEncodeWithModifications. 9 | * Refer to their documentation below to understand their usage. 10 | * 11 | * Note: 12 | * This library depends on jsstegdecoder-1.0.js and jsstegencoder-1.0.js which have different 13 | * licences and must be included before this library. 14 | */ 15 | var jsSteg = (function() { 16 | /** 17 | * Use the JPEG decoding library and pass on the coefficients to coeffReader 18 | * - url: the blob URL from which to read the image 19 | * - coeffReader: a function which will be called with the coefficients as an argument 20 | */ 21 | var getCoefficients = function(url, coeffReader) { 22 | var image; 23 | image = new JpegImage(); 24 | image.onload = function(coefficients) { 25 | return coeffReader(coefficients); 26 | }; 27 | return image.load(url, true); 28 | }; 29 | 30 | /** 31 | * Convert an image in any format to bmp data for encoding 32 | * - url: the blob URL to convert to bmp 33 | * - callback: called with the resulting data 34 | */ 35 | var getImageDataFromURL = function(url, callback) { 36 | var img; 37 | img = document.createElement("img"); 38 | img.onload = function() { 39 | var ctx, cvs; 40 | cvs = document.createElement("canvas"); 41 | cvs.width = img.width; 42 | cvs.height = img.height; 43 | ctx = cvs.getContext("2d"); 44 | ctx.drawImage(img, 0, 0); 45 | return callback(ctx.getImageData(0, 0, cvs.width, cvs.height)); 46 | }; 47 | return img.src = url; 48 | }; 49 | 50 | /** 51 | * Decode the provided JPEG to raw data and then re-encode it with the JPEG encoding library, 52 | * running coefficientModifier on the coefficients while encoding 53 | * - url: the blob URL from which to 're-encode' 54 | * - coefficientModifier: this will be called with the coefficients as an argument which it can 55 | * modify before the encoding is completed 56 | */ 57 | var reEncodeWithModifications = function(url, coefficientModifier, callback) { 58 | getImageDataFromURL(url, function(data) { 59 | var encoder = new JPEGEncoder(); 60 | var jpegURI = encoder.encodeAndModifyCoefficients(data, 75, modifyCoefficients); 61 | callback(jpegURI); 62 | }); 63 | } 64 | 65 | return { 66 | getCoefficients: getCoefficients, 67 | reEncodeWithModifications: reEncodeWithModifications 68 | }; 69 | })(); -------------------------------------------------------------------------------- /js-opensrc/jsstegencoder-1.0.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Based on Basic Blocking JPEG Encoder v 0.9a, ported and optimised by 4 | Andreas Ritter (www.bytestrom.eu, 11/2009). 5 | 6 | Modified by Owen Campbell-Moore (www.owencampbellmoore.com, 03/13) for 7 | easy DCT modification. 8 | 9 | Released by Andreas Ritter carrying both of the following licences: 10 | 11 | Copyright (c) 2008, Adobe Systems Incorporated 12 | All rights reserved. 13 | 14 | Redistribution and use in source and binary forms, with or without 15 | modification, are permitted provided that the following conditions are 16 | met: 17 | 18 | * Redistributions of source code must retain the above copyright notice, 19 | this list of conditions and the following disclaimer. 20 | 21 | * Redistributions in binary form must reproduce the above copyright 22 | notice, this list of conditions and the following disclaimer in the 23 | documentation and/or other materials provided with the distribution. 24 | 25 | * Neither the name of Adobe Systems Incorporated nor the names of its 26 | contributors may be used to endorse or promote products derived from 27 | this software without specific prior written permission. 28 | 29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 30 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 31 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 32 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 33 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 34 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 35 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 36 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 37 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 38 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 39 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 40 | 41 | ------------- 42 | 43 | Licensed under the MIT License 44 | 45 | Copyright (c) 2009 Andreas Ritter 46 | 47 | Permission is hereby granted, free of charge, to any person obtaining a copy 48 | of this software and associated documentation files (the "Software"), to deal 49 | in the Software without restriction, including without limitation the rights 50 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 51 | copies of the Software, and to permit persons to whom the Software is 52 | furnished to do so, subject to the following conditions: 53 | 54 | The above copyright notice and this permission notice shall be included in 55 | all copies or substantial portions of the Software. 56 | 57 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 58 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 59 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 60 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 61 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 62 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 63 | THE SOFTWARE. 64 | 65 | */ 66 | function JPEGEncoder() { 67 | var self = this; 68 | var fround = Math.round; 69 | var ffloor = Math.floor; 70 | var YTable = new Array(64); 71 | var UVTable = new Array(64); 72 | var fdtbl_Y = new Array(64); 73 | var fdtbl_UV = new Array(64); 74 | var YDC_HT; 75 | var UVDC_HT; 76 | var YAC_HT; 77 | var UVAC_HT; 78 | 79 | var bitcode = new Array(65535); 80 | var category = new Array(65535); 81 | var outputfDCTQuant = new Array(64); 82 | var DU = new Array(64); 83 | var byteout = []; 84 | var bytenew = 0; 85 | var bytepos = 7; 86 | 87 | var YDU = new Array(64); 88 | var UDU = new Array(64); 89 | var VDU = new Array(64); 90 | var clt = new Array(256); 91 | var RGB_YUV_TABLE = new Array(2048); 92 | var currentQuality; 93 | 94 | var ZigZag = [ 95 | 0, 1, 5, 6,14,15,27,28, 96 | 2, 4, 7,13,16,26,29,42, 97 | 3, 8,12,17,25,30,41,43, 98 | 9,11,18,24,31,40,44,53, 99 | 10,19,23,32,39,45,52,54, 100 | 20,22,33,38,46,51,55,60, 101 | 21,34,37,47,50,56,59,61, 102 | 35,36,48,49,57,58,62,63 103 | ]; 104 | 105 | var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]; 106 | var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; 107 | var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]; 108 | var std_ac_luminance_values = [ 109 | 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12, 110 | 0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07, 111 | 0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, 112 | 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, 113 | 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16, 114 | 0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, 115 | 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39, 116 | 0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, 117 | 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, 118 | 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69, 119 | 0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79, 120 | 0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, 121 | 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98, 122 | 0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7, 123 | 0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, 124 | 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, 125 | 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4, 126 | 0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, 127 | 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea, 128 | 0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, 129 | 0xf9,0xfa 130 | ]; 131 | 132 | var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]; 133 | var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; 134 | var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]; 135 | var std_ac_chrominance_values = [ 136 | 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21, 137 | 0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71, 138 | 0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, 139 | 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, 140 | 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34, 141 | 0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, 142 | 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38, 143 | 0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, 144 | 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, 145 | 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68, 146 | 0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78, 147 | 0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, 148 | 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96, 149 | 0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5, 150 | 0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, 151 | 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, 152 | 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2, 153 | 0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, 154 | 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9, 155 | 0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, 156 | 0xf9,0xfa 157 | ]; 158 | 159 | function initQuantTables(sf){ 160 | var YQT = [ 161 | 16, 11, 10, 16, 24, 40, 51, 61, 162 | 12, 12, 14, 19, 26, 58, 60, 55, 163 | 14, 13, 16, 24, 40, 57, 69, 56, 164 | 14, 17, 22, 29, 51, 87, 80, 62, 165 | 18, 22, 37, 56, 68,109,103, 77, 166 | 24, 35, 55, 64, 81,104,113, 92, 167 | 49, 64, 78, 87,103,121,120,101, 168 | 72, 92, 95, 98,112,100,103, 99 169 | ]; 170 | 171 | for (var i = 0; i < 64; i++) { 172 | var t = ffloor((YQT[i]*sf+50)/100); 173 | if (t < 1) { 174 | t = 1; 175 | } else if (t > 255) { 176 | t = 255; 177 | } 178 | YTable[ZigZag[i]] = t; 179 | } 180 | 181 | var UVQT = [ 182 | 17, 18, 24, 47, 99, 99, 99, 99, 183 | 18, 21, 26, 66, 99, 99, 99, 99, 184 | 24, 26, 56, 99, 99, 99, 99, 99, 185 | 47, 66, 99, 99, 99, 99, 99, 99, 186 | 99, 99, 99, 99, 99, 99, 99, 99, 187 | 99, 99, 99, 99, 99, 99, 99, 99, 188 | 99, 99, 99, 99, 99, 99, 99, 99, 189 | 99, 99, 99, 99, 99, 99, 99, 99 190 | ]; 191 | for (var j = 0; j < 64; j++) { 192 | var u = ffloor((UVQT[j]*sf+50)/100); 193 | if (u < 1) { 194 | u = 1; 195 | } else if (u > 255) { 196 | u = 255; 197 | } 198 | UVTable[ZigZag[j]] = u; 199 | } 200 | var aasf = [ 201 | 1.0, 1.387039845, 1.306562965, 1.175875602, 202 | 1.0, 0.785694958, 0.541196100, 0.275899379 203 | ]; 204 | var k = 0; 205 | for (var row = 0; row < 8; row++) 206 | { 207 | for (var col = 0; col < 8; col++) 208 | { 209 | fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); 210 | fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); 211 | k++; 212 | } 213 | } 214 | } 215 | 216 | function computeHuffmanTbl(nrcodes, std_table){ 217 | var codevalue = 0; 218 | var pos_in_table = 0; 219 | var HT = new Array(); 220 | for (var k = 1; k <= 16; k++) { 221 | for (var j = 1; j <= nrcodes[k]; j++) { 222 | HT[std_table[pos_in_table]] = []; 223 | HT[std_table[pos_in_table]][0] = codevalue; 224 | HT[std_table[pos_in_table]][1] = k; 225 | pos_in_table++; 226 | codevalue++; 227 | } 228 | codevalue*=2; 229 | } 230 | return HT; 231 | } 232 | 233 | function initHuffmanTbl() 234 | { 235 | YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values); 236 | UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values); 237 | YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values); 238 | UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values); 239 | } 240 | 241 | function initCategoryNumber() 242 | { 243 | var nrlower = 1; 244 | var nrupper = 2; 245 | for (var cat = 1; cat <= 15; cat++) { 246 | //Positive numbers 247 | for (var nr = nrlower; nr>0] = 38470 * i; 269 | RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000; 270 | RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i; 271 | RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i; 272 | RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF; 273 | RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i; 274 | RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i; 275 | } 276 | } 277 | 278 | // IO functions 279 | function writeBits(bs) 280 | { 281 | var value = bs[0]; 282 | var posval = bs[1]-1; 283 | while ( posval >= 0 ) { 284 | if (value & (1 << posval) ) { 285 | bytenew |= (1 << bytepos); 286 | } 287 | posval--; 288 | bytepos--; 289 | if (bytepos < 0) { 290 | if (bytenew == 0xFF) { 291 | writeByte(0xFF); 292 | writeByte(0); 293 | } 294 | else { 295 | writeByte(bytenew); 296 | } 297 | bytepos=7; 298 | bytenew=0; 299 | } 300 | } 301 | } 302 | 303 | function writeByte(value) 304 | { 305 | byteout.push(clt[value]); // write char directly instead of converting later 306 | } 307 | 308 | function writeWord(value) 309 | { 310 | writeByte((value>>8)&0xFF); 311 | writeByte((value )&0xFF); 312 | } 313 | 314 | // DCT & quantization core 315 | function fDCTQuant(data, fdtbl) 316 | { 317 | var d0, d1, d2, d3, d4, d5, d6, d7; 318 | /* Pass 1: process rows. */ 319 | var dataOff=0; 320 | var i; 321 | const I8 = 8; 322 | const I64 = 64; 323 | for (i=0; i 0.0) ? ((fDCTQuantVar + 0.5)|0) : ((fDCTQuantVar - 0.5)|0); 443 | //outputfDCTQuant[i] = fround(fDCTQuant); 444 | 445 | } 446 | 447 | return outputfDCTQuant; 448 | } 449 | 450 | function writeAPP0() 451 | { 452 | writeWord(0xFFE0); // marker 453 | writeWord(16); // length 454 | writeByte(0x4A); // J 455 | writeByte(0x46); // F 456 | writeByte(0x49); // I 457 | writeByte(0x46); // F 458 | writeByte(0); // = "JFIF",'\0' 459 | writeByte(1); // versionhi 460 | writeByte(1); // versionlo 461 | writeByte(0); // xyunits 462 | writeWord(1); // xdensity 463 | writeWord(1); // ydensity 464 | writeByte(0); // thumbnwidth 465 | writeByte(0); // thumbnheight 466 | } 467 | 468 | function writeSOF0(width, height) 469 | { 470 | writeWord(0xFFC0); // marker 471 | writeWord(17); // length, truecolor YUV JPG 472 | writeByte(8); // precision 473 | writeWord(height); 474 | writeWord(width); 475 | writeByte(3); // nrofcomponents 476 | writeByte(1); // IdY 477 | writeByte(0x11); // HVY 478 | writeByte(0); // QTY 479 | writeByte(2); // IdU 480 | writeByte(0x11); // HVU 481 | writeByte(1); // QTU 482 | writeByte(3); // IdV 483 | writeByte(0x11); // HVV 484 | writeByte(1); // QTV 485 | } 486 | 487 | function writeDQT() 488 | { 489 | writeWord(0xFFDB); // marker 490 | writeWord(132); // length 491 | writeByte(0); 492 | for (var i=0; i<64; i++) { 493 | writeByte(YTable[i]); 494 | } 495 | writeByte(1); 496 | for (var j=0; j<64; j++) { 497 | writeByte(UVTable[j]); 498 | } 499 | } 500 | 501 | function writeDHT() 502 | { 503 | writeWord(0xFFC4); // marker 504 | writeWord(0x01A2); // length 505 | 506 | writeByte(0); // HTYDCinfo 507 | for (var i=0; i<16; i++) { 508 | writeByte(std_dc_luminance_nrcodes[i+1]); 509 | } 510 | for (var j=0; j<=11; j++) { 511 | writeByte(std_dc_luminance_values[j]); 512 | } 513 | 514 | writeByte(0x10); // HTYACinfo 515 | for (var k=0; k<16; k++) { 516 | writeByte(std_ac_luminance_nrcodes[k+1]); 517 | } 518 | for (var l=0; l<=161; l++) { 519 | writeByte(std_ac_luminance_values[l]); 520 | } 521 | 522 | writeByte(1); // HTUDCinfo 523 | for (var m=0; m<16; m++) { 524 | writeByte(std_dc_chrominance_nrcodes[m+1]); 525 | } 526 | for (var n=0; n<=11; n++) { 527 | writeByte(std_dc_chrominance_values[n]); 528 | } 529 | 530 | writeByte(0x11); // HTUACinfo 531 | for (var o=0; o<16; o++) { 532 | writeByte(std_ac_chrominance_nrcodes[o+1]); 533 | } 534 | for (var p=0; p<=161; p++) { 535 | writeByte(std_ac_chrominance_values[p]); 536 | } 537 | } 538 | 539 | function writeSOS() 540 | { 541 | writeWord(0xFFDA); // marker 542 | writeWord(12); // length 543 | writeByte(3); // nrofcomponents 544 | writeByte(1); // IdY 545 | writeByte(0); // HTY 546 | writeByte(2); // IdU 547 | writeByte(0x11); // HTU 548 | writeByte(3); // IdV 549 | writeByte(0x11); // HTV 550 | writeByte(0); // Ss 551 | writeByte(0x3f); // Se 552 | writeByte(0); // Bf 553 | } 554 | 555 | function processDU(DU_DCT, DC, HTDC, HTAC){ 556 | var EOB = HTAC[0x00]; 557 | var M16zeroes = HTAC[0xF0]; 558 | var pos; 559 | const I16 = 16; 560 | const I63 = 63; 561 | const I64 = 64; 562 | 563 | //ZigZag reorder 564 | for (var j=0;j0)&&(DU[end0pos]==0); end0pos--) {}; 579 | //end0pos = first element in reverse order !=0 580 | if ( end0pos == 0) { 581 | writeBits(EOB); 582 | return DC; 583 | } 584 | var i = 1; 585 | var lng; 586 | while ( i <= end0pos ) { 587 | var startpos = i; 588 | for (; (DU[i]==0) && (i<=end0pos); ++i) {} 589 | var nrzeroes = i-startpos; 590 | if ( nrzeroes >= I16 ) { 591 | lng = nrzeroes>>4; 592 | for (var nrmarker=1; nrmarker <= lng; ++nrmarker) 593 | writeBits(M16zeroes); 594 | nrzeroes = nrzeroes&0xF; 595 | } 596 | pos = 32767+DU[i]; 597 | writeBits(HTAC[(nrzeroes<<4)+category[pos]]); 598 | writeBits(bitcode[pos]); 599 | i++; 600 | } 601 | if ( end0pos != I63 ) { 602 | writeBits(EOB); 603 | } 604 | return DC; 605 | } 606 | 607 | function initCharLookupTable(){ 608 | var sfcc = String.fromCharCode; 609 | for(var i=0; i < 256; i++){ ///// ACHTUNG // 255 610 | clt[i] = sfcc(i); 611 | } 612 | } 613 | 614 | this.encodeAndModifyCoefficients = function(image, quality, coefficientModifier) // image data object 615 | { 616 | var time_start = new Date().getTime(); 617 | 618 | setQuality(quality); 619 | 620 | // Initialize bit writer 621 | byteout = new Array(); 622 | bytenew=0; 623 | bytepos=7; 624 | 625 | // Add JPEG headers 626 | writeWord(0xFFD8); // SOI 627 | writeAPP0(); 628 | writeDQT(); 629 | writeSOF0(image.width,image.height); 630 | writeDHT(); 631 | writeSOS(); 632 | 633 | 634 | // Encode 8x8 macroblocks 635 | var DCY=0; 636 | var DCU=0; 637 | var DCV=0; 638 | 639 | bytenew=0; 640 | bytepos=7; 641 | 642 | var imageData = image.data; 643 | var width = image.width; 644 | var height = image.height; 645 | 646 | var quadWidth = width*4; 647 | var tripleWidth = width*3; 648 | 649 | var x, y = 0; 650 | var j = 0; 651 | var r, g, b; 652 | var start,p, col,row,pos; 653 | var DU_DCT_ARRAY = new Array(); 654 | DU_DCT_ARRAY[0] = new Array(); 655 | DU_DCT_ARRAY[1] = new Array(); 656 | DU_DCT_ARRAY[2] = new Array(); 657 | while(y < height){ 658 | x = 0; 659 | while(x < quadWidth){ 660 | start = quadWidth * y + x; 661 | p = start; 662 | col = -1; 663 | row = 0; 664 | 665 | for(pos=0; pos < 64; pos++){ 666 | row = pos >> 3;// /8 667 | col = ( pos & 7 ) * 4; // %8 668 | p = start + ( row * quadWidth ) + col; 669 | 670 | if(y+row >= height){ // padding bottom 671 | p-= (quadWidth*(y+1+row-height)); 672 | } 673 | 674 | if(x+col >= quadWidth){ // padding right 675 | p-= ((x+col) - quadWidth +4) 676 | } 677 | 678 | r = imageData[ p++ ]; 679 | g = imageData[ p++ ]; 680 | b = imageData[ p++ ]; 681 | 682 | 683 | /* // calculate YUV values dynamically 684 | YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80 685 | UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b)); 686 | VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b)); 687 | */ 688 | 689 | // use lookup table (slightly faster) 690 | YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256)>>0] + RGB_YUV_TABLE[(b + 512)>>0]) >> 16)-128; 691 | UDU[pos] = ((RGB_YUV_TABLE[(r + 768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128; 692 | VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128; 693 | } 694 | 695 | DU_DCT_ARRAY[0][j] = fDCTQuant(YDU, fdtbl_Y); 696 | DU_DCT_ARRAY[1][j] = fDCTQuant(UDU, fdtbl_UV); 697 | DU_DCT_ARRAY[2][j] = fDCTQuant(VDU, fdtbl_UV); 698 | 699 | x+=32; 700 | j++; 701 | } 702 | y+=8; 703 | } 704 | 705 | //This is where the passed in function gets to fiddle with the coefficients. 706 | coefficientModifier(DU_DCT_ARRAY); 707 | 708 | for (var i = 0; i < j; i++){ 709 | DCY = processDU(DU_DCT_ARRAY[0][i], DCY, YDC_HT, YAC_HT); 710 | DCU = processDU(DU_DCT_ARRAY[1][i], DCU, UVDC_HT, UVAC_HT); 711 | DCV = processDU(DU_DCT_ARRAY[2][i], DCV, UVDC_HT, UVAC_HT); 712 | } 713 | 714 | // Do the bit alignment of the EOI marker 715 | if ( bytepos >= 0 ) { 716 | var fillbits = []; 717 | fillbits[1] = bytepos+1; 718 | fillbits[0] = (1<<(bytepos+1))-1; 719 | writeBits(fillbits); 720 | } 721 | 722 | writeWord(0xFFD9); //EOI 723 | 724 | var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join('')); 725 | 726 | byteout = []; 727 | 728 | // benchmarking 729 | var duration = new Date().getTime() - time_start; 730 | console.log('Encoding time: '+ duration + 'ms'); 731 | // 732 | 733 | return jpegDataUri 734 | } 735 | 736 | function setQuality(quality){ 737 | if (quality <= 0) { 738 | quality = 1; 739 | } 740 | if (quality > 100) { 741 | quality = 100; 742 | } 743 | 744 | if(currentQuality == quality) return // don't recalc if unchanged 745 | 746 | var sf = 0; 747 | if (quality < 50) { 748 | sf = Math.floor(5000 / quality); 749 | } else { 750 | sf = Math.floor(200 - quality*2); 751 | } 752 | 753 | initQuantTables(sf); 754 | currentQuality = quality; 755 | console.log('Quality set to: '+quality +'%'); 756 | } 757 | 758 | function init(){ 759 | var time_start = new Date().getTime(); 760 | // Create tables 761 | initCharLookupTable() 762 | initHuffmanTbl(); 763 | initCategoryNumber(); 764 | initRGBYUVTable(); 765 | 766 | var duration = new Date().getTime() - time_start; 767 | console.log('Initialization '+ duration + 'ms'); 768 | } 769 | 770 | init(); 771 | 772 | }; -------------------------------------------------------------------------------- /js-opensrc/lz-string.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Pieroxy 2 | // This work is free. You can redistribute it and/or modify it 3 | // under the terms of the WTFPL, Version 2 4 | // For more information see LICENSE.txt or http://www.wtfpl.net/ 5 | // 6 | // For more information, the home page: 7 | // http://pieroxy.net/blog/pages/lz-string/testing.html 8 | // 9 | // LZ-based compression algorithm, version 1.4.4 10 | var LZString = (function() { 11 | 12 | // private property 13 | var f = String.fromCharCode; 14 | var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 15 | var keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$"; 16 | var baseReverseDic = {}; 17 | 18 | function getBaseValue(alphabet, character) { 19 | if (!baseReverseDic[alphabet]) { 20 | baseReverseDic[alphabet] = {}; 21 | for (var i=0 ; i>> 8; 66 | buf[i*2+1] = current_value % 256; 67 | } 68 | return buf; 69 | }, 70 | 71 | //decompress from uint8array (UCS-2 big endian format) 72 | decompressFromUint8Array:function (compressed) { 73 | if (compressed===null || compressed===undefined){ 74 | return LZString.decompress(compressed); 75 | } else { 76 | var buf=new Array(compressed.length/2); // 2 bytes per character 77 | for (var i=0, TotalLen=buf.length; i> 1; 159 | } 160 | } else { 161 | value = 1; 162 | for (i=0 ; i> 1; 184 | } 185 | } 186 | context_enlargeIn--; 187 | if (context_enlargeIn == 0) { 188 | context_enlargeIn = Math.pow(2, context_numBits); 189 | context_numBits++; 190 | } 191 | delete context_dictionaryToCreate[context_w]; 192 | } else { 193 | value = context_dictionary[context_w]; 194 | for (i=0 ; i> 1; 204 | } 205 | 206 | 207 | } 208 | context_enlargeIn--; 209 | if (context_enlargeIn == 0) { 210 | context_enlargeIn = Math.pow(2, context_numBits); 211 | context_numBits++; 212 | } 213 | // Add wc to the dictionary. 214 | context_dictionary[context_wc] = context_dictSize++; 215 | context_w = String(context_c); 216 | } 217 | } 218 | 219 | // Output the code for w. 220 | if (context_w !== "") { 221 | if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) { 222 | if (context_w.charCodeAt(0)<256) { 223 | for (i=0 ; i> 1; 244 | } 245 | } else { 246 | value = 1; 247 | for (i=0 ; i> 1; 269 | } 270 | } 271 | context_enlargeIn--; 272 | if (context_enlargeIn == 0) { 273 | context_enlargeIn = Math.pow(2, context_numBits); 274 | context_numBits++; 275 | } 276 | delete context_dictionaryToCreate[context_w]; 277 | } else { 278 | value = context_dictionary[context_w]; 279 | for (i=0 ; i> 1; 289 | } 290 | 291 | 292 | } 293 | context_enlargeIn--; 294 | if (context_enlargeIn == 0) { 295 | context_enlargeIn = Math.pow(2, context_numBits); 296 | context_numBits++; 297 | } 298 | } 299 | 300 | // Mark the end of the stream 301 | value = 2; 302 | for (i=0 ; i> 1; 312 | } 313 | 314 | // Flush the last char 315 | while (true) { 316 | context_data_val = (context_data_val << 1); 317 | if (context_data_position == bitsPerChar-1) { 318 | context_data.push(getCharFromInt(context_data_val)); 319 | break; 320 | } 321 | else context_data_position++; 322 | } 323 | return context_data.join(''); 324 | }, 325 | 326 | decompress: function (compressed) { 327 | if (compressed == null) return ""; 328 | if (compressed == "") return null; 329 | return LZString._decompress(compressed.length, 32768, function(index) { return compressed.charCodeAt(index); }); 330 | }, 331 | 332 | _decompress: function (length, resetValue, getNextValue) { 333 | var dictionary = [], 334 | next, 335 | enlargeIn = 4, 336 | dictSize = 4, 337 | numBits = 3, 338 | entry = "", 339 | result = [], 340 | i, 341 | w, 342 | bits, resb, maxpower, power, 343 | c, 344 | data = {val:getNextValue(0), position:resetValue, index:1}; 345 | 346 | for (i = 0; i < 3; i += 1) { 347 | dictionary[i] = i; 348 | } 349 | 350 | bits = 0; 351 | maxpower = Math.pow(2,2); 352 | power=1; 353 | while (power!=maxpower) { 354 | resb = data.val & data.position; 355 | data.position >>= 1; 356 | if (data.position == 0) { 357 | data.position = resetValue; 358 | data.val = getNextValue(data.index++); 359 | } 360 | bits |= (resb>0 ? 1 : 0) * power; 361 | power <<= 1; 362 | } 363 | 364 | switch (next = bits) { 365 | case 0: 366 | bits = 0; 367 | maxpower = Math.pow(2,8); 368 | power=1; 369 | while (power!=maxpower) { 370 | resb = data.val & data.position; 371 | data.position >>= 1; 372 | if (data.position == 0) { 373 | data.position = resetValue; 374 | data.val = getNextValue(data.index++); 375 | } 376 | bits |= (resb>0 ? 1 : 0) * power; 377 | power <<= 1; 378 | } 379 | c = f(bits); 380 | break; 381 | case 1: 382 | bits = 0; 383 | maxpower = Math.pow(2,16); 384 | power=1; 385 | while (power!=maxpower) { 386 | resb = data.val & data.position; 387 | data.position >>= 1; 388 | if (data.position == 0) { 389 | data.position = resetValue; 390 | data.val = getNextValue(data.index++); 391 | } 392 | bits |= (resb>0 ? 1 : 0) * power; 393 | power <<= 1; 394 | } 395 | c = f(bits); 396 | break; 397 | case 2: 398 | return ""; 399 | } 400 | dictionary[3] = c; 401 | w = c; 402 | result.push(c); 403 | while (true) { 404 | if (data.index > length) { 405 | return ""; 406 | } 407 | 408 | bits = 0; 409 | maxpower = Math.pow(2,numBits); 410 | power=1; 411 | while (power!=maxpower) { 412 | resb = data.val & data.position; 413 | data.position >>= 1; 414 | if (data.position == 0) { 415 | data.position = resetValue; 416 | data.val = getNextValue(data.index++); 417 | } 418 | bits |= (resb>0 ? 1 : 0) * power; 419 | power <<= 1; 420 | } 421 | 422 | switch (c = bits) { 423 | case 0: 424 | bits = 0; 425 | maxpower = Math.pow(2,8); 426 | power=1; 427 | while (power!=maxpower) { 428 | resb = data.val & data.position; 429 | data.position >>= 1; 430 | if (data.position == 0) { 431 | data.position = resetValue; 432 | data.val = getNextValue(data.index++); 433 | } 434 | bits |= (resb>0 ? 1 : 0) * power; 435 | power <<= 1; 436 | } 437 | 438 | dictionary[dictSize++] = f(bits); 439 | c = dictSize-1; 440 | enlargeIn--; 441 | break; 442 | case 1: 443 | bits = 0; 444 | maxpower = Math.pow(2,16); 445 | power=1; 446 | while (power!=maxpower) { 447 | resb = data.val & data.position; 448 | data.position >>= 1; 449 | if (data.position == 0) { 450 | data.position = resetValue; 451 | data.val = getNextValue(data.index++); 452 | } 453 | bits |= (resb>0 ? 1 : 0) * power; 454 | power <<= 1; 455 | } 456 | dictionary[dictSize++] = f(bits); 457 | c = dictSize-1; 458 | enlargeIn--; 459 | break; 460 | case 2: 461 | return result.join(''); 462 | } 463 | 464 | if (enlargeIn == 0) { 465 | enlargeIn = Math.pow(2, numBits); 466 | numBits++; 467 | } 468 | 469 | if (dictionary[c]) { 470 | entry = dictionary[c]; 471 | } else { 472 | if (c === dictSize) { 473 | entry = w + w.charAt(0); 474 | } else { 475 | return null; 476 | } 477 | } 478 | result.push(entry); 479 | 480 | // Add w+entry[0] to the dictionary. 481 | dictionary[dictSize++] = w + entry.charAt(0); 482 | enlargeIn--; 483 | 484 | w = entry; 485 | 486 | if (enlargeIn == 0) { 487 | enlargeIn = Math.pow(2, numBits); 488 | numBits++; 489 | } 490 | 491 | } 492 | } 493 | }; 494 | return LZString; 495 | })(); 496 | 497 | if (typeof define === 'function' && define.amd) { 498 | define(function () { return LZString; }); 499 | } else if( typeof module !== 'undefined' && module != null ) { 500 | module.exports = LZString 501 | } 502 | -------------------------------------------------------------------------------- /js-opensrc/nacl-util.js: -------------------------------------------------------------------------------- 1 | // taken from an old version of TweetNaCl, which dropped this code eventually 2 | 3 | nacl.util = {}; 4 | 5 | nacl.util.decodeUTF8 = function(s) { 6 | var i, d = unescape(encodeURIComponent(s)), b = new Uint8Array(d.length); 7 | for (i = 0; i < d.length; i++) b[i] = d.charCodeAt(i); 8 | return b; 9 | }; 10 | 11 | nacl.util.encodeUTF8 = function(arr) { 12 | var i, s = []; 13 | for (i = 0; i < arr.length; i++) s.push(String.fromCharCode(arr[i])); 14 | return decodeURIComponent(escape(s.join(''))); 15 | }; 16 | 17 | nacl.util.encodeBase64 = function(arr) { 18 | if (typeof btoa === 'undefined') { 19 | return (new Buffer(arr)).toString('base64'); 20 | } else { 21 | var i, s = [], len = arr.length; 22 | for (i = 0; i < len; i++) s.push(String.fromCharCode(arr[i])); 23 | return btoa(s.join('')); 24 | } 25 | }; 26 | 27 | nacl.util.decodeBase64 = function(s) { 28 | if (typeof atob === 'undefined') { 29 | return new Uint8Array(Array.prototype.slice.call(new Buffer(s, 'base64'), 0)); 30 | } else { 31 | try{ //added for PassLok because atob may fail 32 | var i, d = atob(s), b = new Uint8Array(d.length); 33 | }catch(error){ 34 | return false 35 | } 36 | for (i = 0; i < d.length; i++) b[i] = d.charCodeAt(i); 37 | return b; 38 | } 39 | }; -------------------------------------------------------------------------------- /js-opensrc/scrypt-async.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Fast "async" scrypt implementation in JavaScript. 3 | * Copyright (c) 2013-2016 Dmitry Chestnykh | BSD License 4 | * https://github.com/dchest/scrypt-async-js 5 | */ 6 | 7 | /** 8 | * scrypt(password, salt, options, callback) 9 | * 10 | * where 11 | * 12 | * password and salt are strings or arrays of bytes (Array of Uint8Array) 13 | * options is 14 | * 15 | * { 16 | * N: // CPU/memory cost parameter, must be power of two 17 | * // (alternatively, you can specify logN) 18 | * r: // block size 19 | * p: // parallelization parameter 20 | * dkLen: // length of derived key, default = 32 21 | * encoding: // optional encoding: 22 | * "base64" - standard Base64 encoding 23 | * "hex" — hex encoding, 24 | * "binary" — Uint8Array, 25 | * undefined/null - Array of bytes 26 | * interruptStep: // optional, steps to split calculations (default is 0) 27 | * } 28 | * 29 | * Derives a key from password and salt and calls callback 30 | * with derived key as the only argument. 31 | * 32 | * Calculations are interrupted with setImmediate (or zero setTimeout) at the 33 | * given interruptSteps to avoid freezing the browser. If it's undefined or zero, 34 | * the callback is called immediately after the calculation, avoiding setImmediate. 35 | * 36 | * Legacy way (only supports p = 1) to call this function is: 37 | * 38 | * scrypt(password, salt, logN, r, dkLen, [interruptStep], callback, [encoding]) 39 | * 40 | * In legacy API, if interruptStep is not given, it defaults to 1000. 41 | * Pass 0 to have callback called immediately. 42 | * 43 | */ 44 | function scrypt(password, salt, logN, r, dkLen, interruptStep, callback, encoding) { 45 | 'use strict'; 46 | 47 | function SHA256(m) { 48 | /** @const */ var K = [ 49 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 50 | 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 51 | 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 52 | 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 53 | 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 54 | 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 55 | 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 56 | 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 57 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 58 | 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 59 | 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 60 | 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 61 | 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 62 | ]; 63 | 64 | var h0 = 0x6a09e667, h1 = 0xbb67ae85, h2 = 0x3c6ef372, h3 = 0xa54ff53a, 65 | h4 = 0x510e527f, h5 = 0x9b05688c, h6 = 0x1f83d9ab, h7 = 0x5be0cd19, 66 | w = new Array(64); 67 | 68 | function blocks(p) { 69 | var off = 0, len = p.length; 70 | while (len >= 64) { 71 | var a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7, 72 | u, i, j, t1, t2; 73 | 74 | for (i = 0; i < 16; i++) { 75 | j = off + i*4; 76 | w[i] = ((p[j] & 0xff)<<24) | ((p[j+1] & 0xff)<<16) | 77 | ((p[j+2] & 0xff)<<8) | (p[j+3] & 0xff); 78 | } 79 | 80 | for (i = 16; i < 64; i++) { 81 | u = w[i-2]; 82 | t1 = ((u>>>17) | (u<<(32-17))) ^ ((u>>>19) | (u<<(32-19))) ^ (u>>>10); 83 | 84 | u = w[i-15]; 85 | t2 = ((u>>>7) | (u<<(32-7))) ^ ((u>>>18) | (u<<(32-18))) ^ (u>>>3); 86 | 87 | w[i] = (((t1 + w[i-7]) | 0) + ((t2 + w[i-16]) | 0)) | 0; 88 | } 89 | 90 | for (i = 0; i < 64; i++) { 91 | t1 = ((((((e>>>6) | (e<<(32-6))) ^ ((e>>>11) | (e<<(32-11))) ^ 92 | ((e>>>25) | (e<<(32-25)))) + ((e & f) ^ (~e & g))) | 0) + 93 | ((h + ((K[i] + w[i]) | 0)) | 0)) | 0; 94 | 95 | t2 = ((((a>>>2) | (a<<(32-2))) ^ ((a>>>13) | (a<<(32-13))) ^ 96 | ((a>>>22) | (a<<(32-22)))) + ((a & b) ^ (a & c) ^ (b & c))) | 0; 97 | 98 | h = g; 99 | g = f; 100 | f = e; 101 | e = (d + t1) | 0; 102 | d = c; 103 | c = b; 104 | b = a; 105 | a = (t1 + t2) | 0; 106 | } 107 | 108 | h0 = (h0 + a) | 0; 109 | h1 = (h1 + b) | 0; 110 | h2 = (h2 + c) | 0; 111 | h3 = (h3 + d) | 0; 112 | h4 = (h4 + e) | 0; 113 | h5 = (h5 + f) | 0; 114 | h6 = (h6 + g) | 0; 115 | h7 = (h7 + h) | 0; 116 | 117 | off += 64; 118 | len -= 64; 119 | } 120 | } 121 | 122 | blocks(m); 123 | 124 | var i, bytesLeft = m.length % 64, 125 | bitLenHi = (m.length / 0x20000000) | 0, 126 | bitLenLo = m.length << 3, 127 | numZeros = (bytesLeft < 56) ? 56 : 120, 128 | p = m.slice(m.length - bytesLeft, m.length); 129 | 130 | p.push(0x80); 131 | for (i = bytesLeft + 1; i < numZeros; i++) p.push(0); 132 | p.push((bitLenHi>>>24) & 0xff); 133 | p.push((bitLenHi>>>16) & 0xff); 134 | p.push((bitLenHi>>>8) & 0xff); 135 | p.push((bitLenHi>>>0) & 0xff); 136 | p.push((bitLenLo>>>24) & 0xff); 137 | p.push((bitLenLo>>>16) & 0xff); 138 | p.push((bitLenLo>>>8) & 0xff); 139 | p.push((bitLenLo>>>0) & 0xff); 140 | 141 | blocks(p); 142 | 143 | return [ 144 | (h0>>>24) & 0xff, (h0>>>16) & 0xff, (h0>>>8) & 0xff, (h0>>>0) & 0xff, 145 | (h1>>>24) & 0xff, (h1>>>16) & 0xff, (h1>>>8) & 0xff, (h1>>>0) & 0xff, 146 | (h2>>>24) & 0xff, (h2>>>16) & 0xff, (h2>>>8) & 0xff, (h2>>>0) & 0xff, 147 | (h3>>>24) & 0xff, (h3>>>16) & 0xff, (h3>>>8) & 0xff, (h3>>>0) & 0xff, 148 | (h4>>>24) & 0xff, (h4>>>16) & 0xff, (h4>>>8) & 0xff, (h4>>>0) & 0xff, 149 | (h5>>>24) & 0xff, (h5>>>16) & 0xff, (h5>>>8) & 0xff, (h5>>>0) & 0xff, 150 | (h6>>>24) & 0xff, (h6>>>16) & 0xff, (h6>>>8) & 0xff, (h6>>>0) & 0xff, 151 | (h7>>>24) & 0xff, (h7>>>16) & 0xff, (h7>>>8) & 0xff, (h7>>>0) & 0xff 152 | ]; 153 | } 154 | 155 | function PBKDF2_HMAC_SHA256_OneIter(password, salt, dkLen) { 156 | // compress password if it's longer than hash block length 157 | if(password.length > 64) { 158 | // SHA256 expects password to be an Array. If it's not 159 | // (i.e. it doesn't have .push method), convert it to one. 160 | password = SHA256(password.push ? password : Array.prototype.slice.call(password, 0)); 161 | } 162 | 163 | var i, innerLen = 64 + salt.length + 4, 164 | inner = new Array(innerLen), 165 | outerKey = new Array(64), 166 | dk = []; 167 | 168 | // inner = (password ^ ipad) || salt || counter 169 | for (i = 0; i < 64; i++) inner[i] = 0x36; 170 | for (i = 0; i < password.length; i++) inner[i] ^= password[i]; 171 | for (i = 0; i < salt.length; i++) inner[64+i] = salt[i]; 172 | for (i = innerLen - 4; i < innerLen; i++) inner[i] = 0; 173 | 174 | // outerKey = password ^ opad 175 | for (i = 0; i < 64; i++) outerKey[i] = 0x5c; 176 | for (i = 0; i < password.length; i++) outerKey[i] ^= password[i]; 177 | 178 | // increments counter inside inner 179 | function incrementCounter() { 180 | for (var i = innerLen-1; i >= innerLen-4; i--) { 181 | inner[i]++; 182 | if (inner[i] <= 0xff) return; 183 | inner[i] = 0; 184 | } 185 | } 186 | 187 | // output blocks = SHA256(outerKey || SHA256(inner)) ... 188 | while (dkLen >= 32) { 189 | incrementCounter(); 190 | dk = dk.concat(SHA256(outerKey.concat(SHA256(inner)))); 191 | dkLen -= 32; 192 | } 193 | if (dkLen > 0) { 194 | incrementCounter(); 195 | dk = dk.concat(SHA256(outerKey.concat(SHA256(inner))).slice(0, dkLen)); 196 | } 197 | return dk; 198 | } 199 | 200 | function salsaXOR(tmp, B, bin, bout) { 201 | var j0 = tmp[0] ^ B[bin++], 202 | j1 = tmp[1] ^ B[bin++], 203 | j2 = tmp[2] ^ B[bin++], 204 | j3 = tmp[3] ^ B[bin++], 205 | j4 = tmp[4] ^ B[bin++], 206 | j5 = tmp[5] ^ B[bin++], 207 | j6 = tmp[6] ^ B[bin++], 208 | j7 = tmp[7] ^ B[bin++], 209 | j8 = tmp[8] ^ B[bin++], 210 | j9 = tmp[9] ^ B[bin++], 211 | j10 = tmp[10] ^ B[bin++], 212 | j11 = tmp[11] ^ B[bin++], 213 | j12 = tmp[12] ^ B[bin++], 214 | j13 = tmp[13] ^ B[bin++], 215 | j14 = tmp[14] ^ B[bin++], 216 | j15 = tmp[15] ^ B[bin++], 217 | u, i; 218 | 219 | var x0 = j0, x1 = j1, x2 = j2, x3 = j3, x4 = j4, x5 = j5, x6 = j6, x7 = j7, 220 | x8 = j8, x9 = j9, x10 = j10, x11 = j11, x12 = j12, x13 = j13, x14 = j14, 221 | x15 = j15; 222 | 223 | for (i = 0; i < 8; i += 2) { 224 | u = x0 + x12; x4 ^= u<<7 | u>>>(32-7); 225 | u = x4 + x0; x8 ^= u<<9 | u>>>(32-9); 226 | u = x8 + x4; x12 ^= u<<13 | u>>>(32-13); 227 | u = x12 + x8; x0 ^= u<<18 | u>>>(32-18); 228 | 229 | u = x5 + x1; x9 ^= u<<7 | u>>>(32-7); 230 | u = x9 + x5; x13 ^= u<<9 | u>>>(32-9); 231 | u = x13 + x9; x1 ^= u<<13 | u>>>(32-13); 232 | u = x1 + x13; x5 ^= u<<18 | u>>>(32-18); 233 | 234 | u = x10 + x6; x14 ^= u<<7 | u>>>(32-7); 235 | u = x14 + x10; x2 ^= u<<9 | u>>>(32-9); 236 | u = x2 + x14; x6 ^= u<<13 | u>>>(32-13); 237 | u = x6 + x2; x10 ^= u<<18 | u>>>(32-18); 238 | 239 | u = x15 + x11; x3 ^= u<<7 | u>>>(32-7); 240 | u = x3 + x15; x7 ^= u<<9 | u>>>(32-9); 241 | u = x7 + x3; x11 ^= u<<13 | u>>>(32-13); 242 | u = x11 + x7; x15 ^= u<<18 | u>>>(32-18); 243 | 244 | u = x0 + x3; x1 ^= u<<7 | u>>>(32-7); 245 | u = x1 + x0; x2 ^= u<<9 | u>>>(32-9); 246 | u = x2 + x1; x3 ^= u<<13 | u>>>(32-13); 247 | u = x3 + x2; x0 ^= u<<18 | u>>>(32-18); 248 | 249 | u = x5 + x4; x6 ^= u<<7 | u>>>(32-7); 250 | u = x6 + x5; x7 ^= u<<9 | u>>>(32-9); 251 | u = x7 + x6; x4 ^= u<<13 | u>>>(32-13); 252 | u = x4 + x7; x5 ^= u<<18 | u>>>(32-18); 253 | 254 | u = x10 + x9; x11 ^= u<<7 | u>>>(32-7); 255 | u = x11 + x10; x8 ^= u<<9 | u>>>(32-9); 256 | u = x8 + x11; x9 ^= u<<13 | u>>>(32-13); 257 | u = x9 + x8; x10 ^= u<<18 | u>>>(32-18); 258 | 259 | u = x15 + x14; x12 ^= u<<7 | u>>>(32-7); 260 | u = x12 + x15; x13 ^= u<<9 | u>>>(32-9); 261 | u = x13 + x12; x14 ^= u<<13 | u>>>(32-13); 262 | u = x14 + x13; x15 ^= u<<18 | u>>>(32-18); 263 | } 264 | 265 | B[bout++] = tmp[0] = (x0 + j0) | 0; 266 | B[bout++] = tmp[1] = (x1 + j1) | 0; 267 | B[bout++] = tmp[2] = (x2 + j2) | 0; 268 | B[bout++] = tmp[3] = (x3 + j3) | 0; 269 | B[bout++] = tmp[4] = (x4 + j4) | 0; 270 | B[bout++] = tmp[5] = (x5 + j5) | 0; 271 | B[bout++] = tmp[6] = (x6 + j6) | 0; 272 | B[bout++] = tmp[7] = (x7 + j7) | 0; 273 | B[bout++] = tmp[8] = (x8 + j8) | 0; 274 | B[bout++] = tmp[9] = (x9 + j9) | 0; 275 | B[bout++] = tmp[10] = (x10 + j10) | 0; 276 | B[bout++] = tmp[11] = (x11 + j11) | 0; 277 | B[bout++] = tmp[12] = (x12 + j12) | 0; 278 | B[bout++] = tmp[13] = (x13 + j13) | 0; 279 | B[bout++] = tmp[14] = (x14 + j14) | 0; 280 | B[bout++] = tmp[15] = (x15 + j15) | 0; 281 | } 282 | 283 | function blockCopy(dst, di, src, si, len) { 284 | while (len--) dst[di++] = src[si++]; 285 | } 286 | 287 | function blockXOR(dst, di, src, si, len) { 288 | while (len--) dst[di++] ^= src[si++]; 289 | } 290 | 291 | function blockMix(tmp, B, bin, bout, r) { 292 | blockCopy(tmp, 0, B, bin + (2*r-1)*16, 16); 293 | for (var i = 0; i < 2*r; i += 2) { 294 | salsaXOR(tmp, B, bin + i*16, bout + i*8); 295 | salsaXOR(tmp, B, bin + i*16 + 16, bout + i*8 + r*16); 296 | } 297 | } 298 | 299 | function integerify(B, bi, r) { 300 | return B[bi+(2*r-1)*16]; 301 | } 302 | 303 | function stringToUTF8Bytes(s) { 304 | var arr = []; 305 | for (var i = 0; i < s.length; i++) { 306 | var c = s.charCodeAt(i); 307 | if (c < 0x80) { 308 | arr.push(c); 309 | } else if (c < 0x800) { 310 | arr.push(0xc0 | c >> 6); 311 | arr.push(0x80 | c & 0x3f); 312 | } else if (c < 0xd800) { 313 | arr.push(0xe0 | c >> 12); 314 | arr.push(0x80 | (c >> 6) & 0x3f); 315 | arr.push(0x80 | c & 0x3f); 316 | } else { 317 | if (i >= s.length - 1) { 318 | throw new Error('invalid string'); 319 | } 320 | i++; // get one more character 321 | c = (c & 0x3ff) << 10; 322 | c |= s.charCodeAt(i) & 0x3ff; 323 | c += 0x10000; 324 | 325 | arr.push(0xf0 | c >> 18); 326 | arr.push(0x80 | (c >> 12) & 0x3f); 327 | arr.push(0x80 | (c >> 6) & 0x3f); 328 | arr.push(0x80 | c & 0x3f); 329 | } 330 | } 331 | return arr; 332 | } 333 | 334 | function bytesToHex(p) { 335 | /** @const */ 336 | var enc = '0123456789abcdef'.split(''); 337 | 338 | var len = p.length, 339 | arr = [], 340 | i = 0; 341 | 342 | for (; i < len; i++) { 343 | arr.push(enc[(p[i]>>>4) & 15]); 344 | arr.push(enc[(p[i]>>>0) & 15]); 345 | } 346 | return arr.join(''); 347 | } 348 | 349 | function bytesToBase64(p) { 350 | /** @const */ 351 | var enc = ('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + 352 | '0123456789+/').split(''); 353 | 354 | var len = p.length, 355 | arr = [], 356 | i = 0, 357 | a, b, c, t; 358 | 359 | while (i < len) { 360 | a = i < len ? p[i++] : 0; 361 | b = i < len ? p[i++] : 0; 362 | c = i < len ? p[i++] : 0; 363 | t = (a << 16) + (b << 8) + c; 364 | arr.push(enc[(t >>> 3 * 6) & 63]); 365 | arr.push(enc[(t >>> 2 * 6) & 63]); 366 | arr.push(enc[(t >>> 1 * 6) & 63]); 367 | arr.push(enc[(t >>> 0 * 6) & 63]); 368 | } 369 | if (len % 3 > 0) { 370 | arr[arr.length-1] = '='; 371 | if (len % 3 === 1) arr[arr.length-2] = '='; 372 | } 373 | return arr.join(''); 374 | } 375 | 376 | 377 | // Generate key. 378 | 379 | var MAX_UINT = (-1)>>>0, 380 | p = 1; 381 | 382 | if (typeof logN === "object") { 383 | // Called as: scrypt(password, salt, opts, callback) 384 | if (arguments.length > 4) { 385 | throw new Error('scrypt: incorrect number of arguments'); 386 | } 387 | 388 | var opts = logN; 389 | 390 | callback = r; 391 | logN = opts.logN; 392 | if (typeof logN === 'undefined') { 393 | if (typeof opts.N !== 'undefined') { 394 | if (opts.N < 2 || opts.N > MAX_UINT) 395 | throw new Error('scrypt: N is out of range'); 396 | 397 | if ((opts.N & (opts.N - 1)) !== 0) 398 | throw new Error('scrypt: N is not a power of 2'); 399 | 400 | logN = Math.log(opts.N) / Math.LN2; 401 | } else { 402 | throw new Error('scrypt: missing N parameter'); 403 | } 404 | } 405 | 406 | // XXX: If opts.p or opts.dkLen is 0, it will be set to the default value 407 | // instead of throwing due to incorrect value. To avoid breaking 408 | // compatibility, this will only be changed in the next major version. 409 | p = opts.p || 1; 410 | r = opts.r; 411 | dkLen = opts.dkLen || 32; 412 | interruptStep = opts.interruptStep || 0; 413 | encoding = opts.encoding; 414 | } 415 | 416 | if (p < 1) 417 | throw new Error('scrypt: invalid p'); 418 | 419 | if (r <= 0) 420 | throw new Error('scrypt: invalid r'); 421 | 422 | if (logN < 1 || logN > 31) 423 | throw new Error('scrypt: logN must be between 1 and 31'); 424 | 425 | 426 | var N = (1<>>0, 427 | XY, V, B, tmp; 428 | 429 | if (r*p >= 1<<30 || r > MAX_UINT/128/p || r > MAX_UINT/256 || N > MAX_UINT/128/r) 430 | throw new Error('scrypt: parameters are too large'); 431 | 432 | // Decode strings. 433 | if (typeof password === 'string') 434 | password = stringToUTF8Bytes(password); 435 | if (typeof salt === 'string') 436 | salt = stringToUTF8Bytes(salt); 437 | 438 | if (typeof Int32Array !== 'undefined') { 439 | //XXX We can use Uint32Array, but Int32Array is faster in Safari. 440 | XY = new Int32Array(64*r); 441 | V = new Int32Array(32*N*r); 442 | tmp = new Int32Array(16); 443 | } else { 444 | XY = []; 445 | V = []; 446 | tmp = new Array(16); 447 | } 448 | B = PBKDF2_HMAC_SHA256_OneIter(password, salt, p*128*r); 449 | 450 | var xi = 0, yi = 32 * r; 451 | 452 | function smixStart(pos) { 453 | for (var i = 0; i < 32*r; i++) { 454 | var j = pos + i*4; 455 | XY[xi+i] = ((B[j+3] & 0xff)<<24) | ((B[j+2] & 0xff)<<16) | 456 | ((B[j+1] & 0xff)<<8) | ((B[j+0] & 0xff)<<0); 457 | } 458 | } 459 | 460 | function smixStep1(start, end) { 461 | for (var i = start; i < end; i += 2) { 462 | blockCopy(V, i*(32*r), XY, xi, 32*r); 463 | blockMix(tmp, XY, xi, yi, r); 464 | 465 | blockCopy(V, (i+1)*(32*r), XY, yi, 32*r); 466 | blockMix(tmp, XY, yi, xi, r); 467 | } 468 | } 469 | 470 | function smixStep2(start, end) { 471 | for (var i = start; i < end; i += 2) { 472 | var j = integerify(XY, xi, r) & (N-1); 473 | blockXOR(XY, xi, V, j*(32*r), 32*r); 474 | blockMix(tmp, XY, xi, yi, r); 475 | 476 | j = integerify(XY, yi, r) & (N-1); 477 | blockXOR(XY, yi, V, j*(32*r), 32*r); 478 | blockMix(tmp, XY, yi, xi, r); 479 | } 480 | } 481 | 482 | function smixFinish(pos) { 483 | for (var i = 0; i < 32*r; i++) { 484 | var j = XY[xi+i]; 485 | B[pos + i*4 + 0] = (j>>>0) & 0xff; 486 | B[pos + i*4 + 1] = (j>>>8) & 0xff; 487 | B[pos + i*4 + 2] = (j>>>16) & 0xff; 488 | B[pos + i*4 + 3] = (j>>>24) & 0xff; 489 | } 490 | } 491 | 492 | var nextTick = (typeof setImmediate !== 'undefined') ? setImmediate : setTimeout; 493 | 494 | function interruptedFor(start, end, step, fn, donefn) { 495 | (function performStep() { 496 | nextTick(function() { 497 | fn(start, start + step < end ? start + step : end); 498 | start += step; 499 | if (start < end) 500 | performStep(); 501 | else 502 | donefn(); 503 | }); 504 | })(); 505 | } 506 | 507 | function getResult(enc) { 508 | var result = PBKDF2_HMAC_SHA256_OneIter(password, B, dkLen); 509 | if (enc === 'base64') 510 | return bytesToBase64(result); 511 | else if (enc === 'hex') 512 | return bytesToHex(result); 513 | else if (enc === 'binary') 514 | return new Uint8Array(result); 515 | else 516 | return result; 517 | } 518 | 519 | // Blocking variant. 520 | function calculateSync() { 521 | for (var i = 0; i < p; i++) { 522 | smixStart(i*128*r); 523 | smixStep1(0, N); 524 | smixStep2(0, N); 525 | smixFinish(i*128*r); 526 | } 527 | callback(getResult(encoding)); 528 | } 529 | 530 | // Async variant. 531 | function calculateAsync(i) { 532 | smixStart(i*128*r); 533 | interruptedFor(0, N, interruptStep*2, smixStep1, function() { 534 | interruptedFor(0, N, interruptStep*2, smixStep2, function () { 535 | smixFinish(i*128*r); 536 | if (i + 1 < p) { 537 | nextTick(function() { calculateAsync(i + 1); }); 538 | } else { 539 | callback(getResult(encoding)); 540 | } 541 | }); 542 | }); 543 | } 544 | 545 | if (typeof interruptStep === 'function') { 546 | // Called as: scrypt(..., callback, [encoding]) 547 | // shifting: scrypt(..., interruptStep, callback, [encoding]) 548 | encoding = callback; 549 | callback = interruptStep; 550 | interruptStep = 1000; 551 | } 552 | 553 | if (interruptStep <= 0) { 554 | calculateSync(); 555 | } else { 556 | calculateAsync(0); 557 | } 558 | } 559 | 560 | if (typeof module !== 'undefined') module.exports = scrypt; 561 | -------------------------------------------------------------------------------- /js-opensrc/secrets.js: -------------------------------------------------------------------------------- 1 | //edit in lines 153-156 by F. Ruiz in order to use NaCl RNG function rather than the built-in RNG. Chunks of code commented out as a consequence. Another edit in 341-346 because the original padLeft function didn't work on iPad 2 | // secrets.js - by Alexander Stetsyuk - released under MIT License 3 | (function(exports, global){ 4 | var defaults = { 5 | bits: 8, // default number of bits 6 | radix: 16, // work with HEX by default 7 | minBits: 3, 8 | maxBits: 20, // this permits 1,048,575 shares, though going this high is NOT recommended in JS! 9 | 10 | bytesPerChar: 2, 11 | maxBytesPerChar: 6, // Math.pow(256,7) > Math.pow(2,53) 12 | 13 | // Primitive polynomials (in decimal form) for Galois Fields GF(2^n), for 2 <= n <= 30 14 | // The index of each term in the array corresponds to the n for that polynomial 15 | // i.e. to get the polynomial for n=16, use primitivePolynomials[16] 16 | primitivePolynomials: [null,null,1,3,3,5,3,3,29,17,9,5,83,27,43,3,45,9,39,39,9,5,3,33,27,9,71,39,9,5,83], 17 | 18 | // warning for insecure PRNG 19 | warning: 'WARNING:\nA secure random number generator was not found.\nUsing Math.random(), which is NOT cryptographically strong!' 20 | }; 21 | 22 | // Protected settings object 23 | var config = {}; 24 | 25 | /** @expose **/ 26 | /*edit by F. Ruiz. This function is turned off since it does not get called 27 | exports.getConfig = function(){ 28 | return { 29 | 'bits': config.bits, 30 | 'unsafePRNG': config.unsafePRNG 31 | }; 32 | }; 33 | */ 34 | 35 | function init(bits){ 36 | if(bits && (typeof bits !== 'number' || bits%1 !== 0 || bitsdefaults.maxBits)){ 37 | throw new Error('Number of bits must be an integer between ' + defaults.minBits + ' and ' + defaults.maxBits + ', inclusive.') 38 | } 39 | 40 | config.radix = defaults.radix; 41 | config.bits = bits || defaults.bits; 42 | config.size = Math.pow(2, config.bits); 43 | config.max = config.size - 1; 44 | 45 | // Construct the exp and log tables for multiplication. 46 | var logs = [], exps = [], x = 1, primitive = defaults.primitivePolynomials[config.bits]; 47 | for(var i=0; i= config.size){ 52 | x ^= primitive; 53 | x &= config.max; 54 | } 55 | } 56 | 57 | config.logs = logs; 58 | config.exps = exps; 59 | }; 60 | 61 | /** @expose **/ 62 | exports.init = init; 63 | 64 | function isInited(){ 65 | if(!config.bits || !config.size || !config.max || !config.logs || !config.exps || config.logs.length !== config.size || config.exps.length !== config.size){ 66 | return false; 67 | } 68 | return true; 69 | }; 70 | 71 | //Edit by F. Ruiz. The RNG code in this library is removed and the one in SJCL is used instead 72 | 73 | //about 100 lines removed here 74 | 75 | //End of F. Ruiz edits for this block. There are three more edits: 1. a RNG check that is removed. 2. built-in RNG call replaced by SJCL RNG call. 3. new version of padLeft function 76 | 77 | 78 | // Divides a `secret` number String str expressed in radix `inputRadix` (optional, default 16) 79 | // into `numShares` shares, each expressed in radix `outputRadix` (optional, default to `inputRadix`), 80 | // requiring `threshold` number of shares to reconstruct the secret. 81 | // Optionally, zero-pads the secret to a length that is a multiple of padLength before sharing. 82 | /** @expose **/ 83 | exports.share = function(secret, numShares, threshold, padLength, withoutPrefix){ 84 | if(!isInited()){ 85 | this.init(); 86 | } 87 | /* if(!isSetRNG()){ RNG check removed by F. Ruiz 88 | this.setRNG(); 89 | } 90 | */ 91 | padLength = padLength || 0; 92 | 93 | if(typeof secret !== 'string'){ 94 | throw new Error('Secret must be a string.'); 95 | } 96 | if(typeof numShares !== 'number' || numShares%1 !== 0 || numShares < 2){ 97 | throw new Error('Number of shares must be an integer between 2 and 2^bits-1 (' + config.max + '), inclusive.') 98 | } 99 | if(numShares > config.max){ 100 | var neededBits = Math.ceil(Math.log(numShares +1)/Math.LN2); 101 | throw new Error('Number of shares must be an integer between 2 and 2^bits-1 (' + config.max + '), inclusive. To create ' + numShares + ' shares, use at least ' + neededBits + ' bits.') 102 | } 103 | if(typeof threshold !== 'number' || threshold%1 !== 0 || threshold < 2){ 104 | throw new Error('Threshold number of shares must be an integer between 2 and 2^bits-1 (' + config.max + '), inclusive.'); 105 | } 106 | if(threshold > config.max){ 107 | var neededBits = Math.ceil(Math.log(threshold +1)/Math.LN2); 108 | throw new Error('Threshold number of shares must be an integer between 2 and 2^bits-1 (' + config.max + '), inclusive. To use a threshold of ' + threshold + ', use at least ' + neededBits + ' bits.'); 109 | } 110 | if(typeof padLength !== 'number' || padLength%1 !== 0 ){ 111 | throw new Error('Zero-pad length must be an integer greater than 1.'); 112 | } 113 | 114 | if(config.unsafePRNG){ 115 | warn(); 116 | } 117 | 118 | secret = '1' + hex2bin(secret); // append a 1 so that we can preserve the correct number of leading zeros in our secret 119 | secret = split(secret, padLength); 120 | var x = new Array(numShares), y = new Array(numShares); 121 | for(var i=0, len = secret.length; i exp(log(fx) + log(x)) + coeff[i], 169 | // so if fx===0, just set fx to coeff[i] because 170 | // using the exp/log form will result in incorrect value 171 | function horner(x, coeffs){ 172 | var logx = config.logs[x]; 173 | var fx = 0; 174 | for(var i=coeffs.length-1; i>=0; i--){ 175 | if(fx === 0){ 176 | fx = coeffs[i]; 177 | continue; 178 | } 179 | fx = config.exps[ (logx + config.logs[fx]) % config.max ] ^ coeffs[i]; 180 | } 181 | return fx; 182 | }; 183 | 184 | function inArray(arr,val){ 185 | for(var i = 0,len=arr.length; i < len; i++) { 186 | if(arr[i] === val){ 187 | return true; 188 | } 189 | } 190 | return false; 191 | }; 192 | 193 | function processShare(share){ 194 | 195 | var bits = parseInt(share[0], 36); 196 | if(bits && (typeof bits !== 'number' || bits%1 !== 0 || bitsdefaults.maxBits)){ 197 | throw new Error('Number of bits must be an integer between ' + defaults.minBits + ' and ' + defaults.maxBits + ', inclusive.') 198 | } 199 | 200 | var max = Math.pow(2, bits) - 1; 201 | var idLength = max.toString(config.radix).length; 202 | 203 | var id = parseInt(share.substr(1, idLength), config.radix); 204 | if(typeof id !== 'number' || id%1 !== 0 || id<1 || id>max){ 205 | throw new Error('Share id must be an integer between 1 and ' + config.max + ', inclusive.'); 206 | } 207 | share = share.substr(idLength + 1); 208 | if(!share.length){ 209 | throw new Error('Invalid share: zero-length share.') 210 | } 211 | return { 212 | 'bits': bits, 213 | 'id': id, 214 | 'value': share 215 | }; 216 | }; 217 | 218 | /** @expose **/ 219 | exports._processShare = processShare; 220 | 221 | // Protected method that evaluates the Lagrange interpolation 222 | // polynomial at x=`at` for individual config.bits-length 223 | // segments of each share in the `shares` Array. 224 | // Each share is expressed in base `inputRadix`. The output 225 | // is expressed in base `outputRadix' 226 | function combine(at, shares){ 227 | var setBits, share, x = [], y = [], result = '', idx; 228 | 229 | for(var i=0, len = shares.length; imax){ 283 | throw new Error('Share id must be an integer between 1 and ' + config.max + ', inclusive.'); 284 | } 285 | 286 | var padding = max.toString(config.radix).length; 287 | return config.bits.toString(36).toUpperCase() + padLeft(id.toString(config.radix), padding) + combine(id, shares); 288 | }; 289 | 290 | // Evaluate the Lagrange interpolation polynomial at x = `at` 291 | // using x and y Arrays that are of the same length, with 292 | // corresponding elements constituting points on the polynomial. 293 | function lagrange(at, x, y){ 294 | var sum = 0, 295 | product, 296 | i, j; 297 | 298 | for(var i=0, len = x.length; iconfig.bits; i-=config.bits){ 332 | parts.push(parseInt(str.slice(i-config.bits, i), 2)); 333 | } 334 | parts.push(parseInt(str.slice(0, i), 2)); 335 | return parts; 336 | }; 337 | 338 | // Pads a string `str` with zeros on the left so that its length is a multiple of `bits` 339 | function padLeft(str, bits){ 340 | bits = bits || config.bits 341 | //edit by F. Ruiz. This function didn't work well on iPad, so changed it with a different algorithm 342 | // var missing = str.length % bits; 343 | // return (missing ? new Array(bits - missing + 1).join('0') : '') + str; 344 | while(str.length % bits) str = '0' + str; 345 | return str; 346 | //end of F. Ruiz edit 347 | }; 348 | 349 | function hex2bin(str){ 350 | var bin = '', num; 351 | for(var i=str.length - 1; i>=0; i--){ 352 | num = parseInt(str[i], 16) 353 | if(isNaN(num)){ 354 | throw new Error('Invalid hex character.') 355 | } 356 | bin = padLeft(num.toString(2), 4) + bin; 357 | } 358 | return bin; 359 | } 360 | 361 | function bin2hex(str){ 362 | var hex = '', num; 363 | str = padLeft(str, 4); 364 | for(var i=str.length; i>=4; i-=4){ 365 | num = parseInt(str.slice(i-4, i), 2); 366 | if(isNaN(num)){ 367 | throw new Error('Invalid binary character.') 368 | } 369 | hex = num.toString(16) + hex; 370 | } 371 | return hex; 372 | } 373 | 374 | // Converts a given UTF16 character string to the HEX representation. 375 | // Each character of the input string is represented by 376 | // `bytesPerChar` bytes in the output string. 377 | /** @expose **/ 378 | exports.str2hex = function(str, bytesPerChar){ 379 | if(typeof str !== 'string'){ 380 | throw new Error('Input must be a character string.'); 381 | } 382 | bytesPerChar = bytesPerChar || defaults.bytesPerChar; 383 | 384 | if(typeof bytesPerChar !== 'number' || bytesPerChar%1 !== 0 || bytesPerChar<1 || bytesPerChar > defaults.maxBytesPerChar){ 385 | throw new Error('Bytes per character must be an integer between 1 and ' + defaults.maxBytesPerChar + ', inclusive.') 386 | } 387 | 388 | var hexChars = 2*bytesPerChar; 389 | var max = Math.pow(16, hexChars) - 1; 390 | var out = '', num; 391 | for(var i=0, len=str.length; i max){ 396 | var neededBytes = Math.ceil(Math.log(num+1)/Math.log(256)); 397 | throw new Error('Invalid character code (' + num +'). Maximum allowable is 256^bytes-1 (' + max + '). To convert this character, use at least ' + neededBytes + ' bytes.') 398 | }else{ 399 | out = padLeft(num.toString(16), hexChars) + out; 400 | } 401 | } 402 | return out; 403 | }; 404 | 405 | // Converts a given HEX number string to a UTF16 character string. 406 | /** @expose **/ 407 | exports.hex2str = function(str, bytesPerChar){ 408 | if(typeof str !== 'string'){ 409 | throw new Error('Input must be a hexadecimal string.'); 410 | } 411 | bytesPerChar = bytesPerChar || defaults.bytesPerChar; 412 | 413 | if(typeof bytesPerChar !== 'number' || bytesPerChar%1 !== 0 || bytesPerChar<1 || bytesPerChar > defaults.maxBytesPerChar){ 414 | throw new Error('Bytes per character must be an integer between 1 and ' + defaults.maxBytesPerChar + ', inclusive.') 415 | } 416 | 417 | var hexChars = 2*bytesPerChar; 418 | var out = ''; 419 | str = padLeft(str, hexChars); 420 | for(var i=0, len = str.length; i 396 | #mainMsg { 397 | min-height: 20px; 398 | } 399 | #lockList { 400 | padding: 4px; 401 | width: 100%; 402 | } 403 | #resetListBtn { 404 | font-size: 14px; 405 | padding: 4px; 406 | } 407 | #main2lockBtn { 408 | font-size: 14px; 409 | padding: 4px; 410 | padding-left: 10px; 411 | padding-right: 12px; 412 | } 413 | #basicBtnsTop { 414 | display: block; 415 | } 416 | #decryptBtnBasic { 417 | font-size: 22px; 418 | padding: 14px; 419 | } 420 | #showLockBtnBasic { 421 | font-size: 22px; 422 | padding: 14px; 423 | } 424 | #emailBtnsTop { 425 | display: none; 426 | } 427 | #decryptBtnEmail { 428 | padding-right: 7px; 429 | padding-left: 7px; 430 | } 431 | #stegoBtnEmail { 432 | padding-right: 6px; 433 | padding-left: 6px; 434 | } 435 | #imageFileEmailBtn { 436 | padding-right: 6px; 437 | padding-left: 6px; 438 | padding-bottom: 13px; 439 | } 440 | #imageFileEmail { 441 | display: none; 442 | } 443 | #mainBtnsTop { 444 | display: none; 445 | } 446 | #decryptBtn { 447 | padding-right: 7px; 448 | padding-left: 7px; 449 | } 450 | #verifyBtn { 451 | padding-right: 7px; 452 | padding-left: 7px; 453 | } 454 | #showLockBtn { 455 | padding-right: 7px; 456 | padding-left: 7px; 457 | } 458 | #main2extraBtn { 459 | color: orange; 460 | padding-right: 9px; 461 | padding-left: 9px; 462 | } 463 | #extraButtonsTop { 464 | display: none; 465 | } 466 | #stegoBtn { 467 | padding-right: 9px; 468 | padding-left: 9px; 469 | } 470 | #imageFileBtn { 471 | padding-bottom: 13px; 472 | } 473 | #imageFile { 474 | display: none; 475 | } 476 | #secretShareBtn { 477 | padding-right: 9px; 478 | padding-left: 9px; 479 | } 480 | #extra2mainBtn { 481 | color: orange; 482 | padding-right: 9px; 483 | padding-left: 9px; 484 | } 485 | #imgFile { 486 | display: none; 487 | } 488 | #mainFile { 489 | display: none; 490 | } 491 | 492 | #optionsTab { 493 | max-width: 500px; 494 | } 495 | #customColors { 496 | display: block; 497 | } 498 | #colorPicker { 499 | width: 50px; 500 | padding: 12px; 501 | } 502 | #rndColors { 503 | font-size: medium; 504 | padding: 9px; 505 | } 506 | #specialEncryptModes { 507 | display: none; 508 | } 509 | #syncCheck { 510 | display: none; 511 | } 512 | #basicHideModes { 513 | display: none; 514 | } 515 | #advancedModes { 516 | display: none; 517 | } 518 | #advancedBtns { 519 | display: none; 520 | } 521 | 522 | #advancedHelp { 523 | display: none; 524 | } 525 | #canary { 526 | float: left; 527 | } 528 | 529 | #lockScr { 530 | display: none; 531 | } 532 | #lock2mainBtn { 533 | float: left; 534 | } 535 | #lockMsg { 536 | height: 25px; 537 | } 538 | #lockNameBox { 539 | width: 95%; 540 | } 541 | #lockBox { 542 | width: 95%; 543 | } 544 | #lockBtnsBottom { 545 | display: none; 546 | } 547 | #lockFile{ 548 | display: none; 549 | } 550 | 551 | #keyScr { 552 | display: block; 553 | } 554 | #pwdMsg { 555 | height: 40px; 556 | } 557 | #javascriptOff { 558 | color: red; 559 | font-weight: bold; 560 | } 561 | #nameList { 562 | padding: 4px; 563 | width: 30%; 564 | } 565 | #newUserBtn { 566 | font-size: 14px; 567 | padding: 4px; 568 | } 569 | #pwdBox { 570 | width: 95%; 571 | } 572 | 573 | #introscr { 574 | display: none; 575 | } 576 | #introWelcome { 577 | color: green; 578 | } 579 | #intro1 { 580 | width: 98%; 581 | max-width: 500px; 582 | } 583 | #skipIntroBtn { 584 | float: right; 585 | } 586 | #introscr2 { 587 | display: none; 588 | } 589 | #intro2 { 590 | width: 98%; 591 | max-width: 500px; 592 | } 593 | #nameIntro { 594 | width: 90%; 595 | } 596 | #pwdIntroMsg { 597 | color: green; 598 | } 599 | #introscr3 { 600 | display: none; 601 | } 602 | #intro3 { 603 | width: 98%; 604 | max-width: 500px; 605 | } 606 | #pwdIntroBox { 607 | width: 90%; 608 | } 609 | #introscr4 { 610 | display: none; 611 | } 612 | #intro4 { 613 | width: 98%; 614 | max-width: 500px; 615 | } 616 | #emailIntro { 617 | width: 90%; 618 | } 619 | #introscr5 { 620 | display: none; 621 | } 622 | #intro5 { 623 | width: 98%; 624 | max-width: 500px; 625 | } 626 | #showlockIntroBtn { 627 | color: blue; 628 | } 629 | #intromsg2 { 630 | color: red; 631 | } 632 | 633 | #nameScr { 634 | display: none; 635 | } 636 | #namechangemsg { 637 | height: 30px; 638 | } 639 | #userNameBox { 640 | width: 95%; 641 | } 642 | #newKeyMsg { 643 | height: 30px; 644 | } 645 | #newKeyBox { 646 | width: 95%; 647 | } 648 | #newKey2Box { 649 | width: 95%; 650 | } 651 | #emailScr { 652 | display: none; 653 | } 654 | #emailBox { 655 | width: 95%; 656 | } 657 | 658 | #decoyText { 659 | width: 95%; 660 | } 661 | #decoyInBox { 662 | width: 95%; 663 | } 664 | #decoyOutBox { 665 | width: 95%; 666 | } 667 | 668 | #partsNumber { 669 | width: 30%; 670 | } 671 | #partsQuorum { 672 | width: 30%; 673 | } 674 | 675 | #chatDate { 676 | width: 95%; 677 | } 678 | 679 | #imageScr { 680 | display: none; 681 | } 682 | #image2mainBtn { 683 | float: left; 684 | } 685 | #imageBox { 686 | width: 25%; 687 | padding: 5px; 688 | } 689 | #imageMsg { 690 | height: 40px; 691 | } 692 | 693 | #lockdirContent { 694 | max-width: 1200px; 695 | margin-left: auto; 696 | margin-right: auto; 697 | } 698 | 699 | #qrcodeImg { 700 | top: 100px; 701 | } 702 | --------------------------------------------------------------------------------